Rust PostgreSQL扩展开发库pgrx-pg-sys的使用,实现高性能PostgreSQL插件与系统交互

Rust PostgreSQL扩展开发库pgrx-pg-sys的使用,实现高性能PostgreSQL插件与系统交互

安装

在项目目录中运行以下Cargo命令:

cargo add pgrx-pg-sys

或者在Cargo.toml中添加以下行:

pgrx-pg-sys = "0.16.0"

文档

官方文档和代码仓库提供了详细的使用说明和API参考。

示例代码

以下是一个使用pgrx-pg-sys开发PostgreSQL扩展的完整示例:

// 引入必要的pgrx-pg-sys模块
use pgrx_pg_sys::{Datum, Oid, PG_FUNCTION_INFO_V1, PG_MODULE_MAGIC, Pg_magic_struct};

// 这是PostgreSQL模块必须包含的魔术结构体
PG_MODULE_MAGIC;

// 定义我们的函数信息结构
PG_FUNCTION_INFO_V1!(add_one);

// 实际的函数实现
#[no_mangle]
pub extern "C" fn add_one(fcinfo: pgrx_pg_sys::FunctionCallInfo) -> Datum {
    // 获取输入参数
    let arg = unsafe { pgrx_pg_sys::PG_GETARG_INT32(fcinfo, 0) };
    
    // 执行计算
    let result = arg + 1;
    
    // 返回结果
    unsafe { pgrx_pg_sys::PG_RETURN_INT32(result) }
}

// 另一个示例函数,计算字符串长度
PG_FUNCTION_INFO_V1!(string_length);

#[no_mangle]
pub extern "C" fn string_length(fcinfo: pgrx_pg_sys::FunctionCallInfo) -> Datum {
    // 获取文本输入参数
    let text_ptr = unsafe { pgrx_pg_sys::PG_GETARG_TEXT_PP(fcinfo, 0) };
    
    // 计算长度
    let len = unsafe { pgrx_pg_sys::VARSIZE_ANY_EXHDR(text_ptr) as i32 };
    
    // 返回结果
    unsafe { pgrx_pg_sys::PG_RETURN_INT32(len) }
}

// 更复杂的示例:聚合函数
PG_FUNCTION_INFO_V1!(int8_sum_state);
PG_FUNCTION_INFO_V1!(int8_sum_final);

#[no_mangle]
pub extern "C" fn int8_sum_state(
    fcinfo: pgrx_pg_sys::FunctionCallInfo,
) -> pgrx_pg_sys::Datum {
    // 获取状态值和输入值
    let state = unsafe { pgrx_pg_sys::PG_GETARG_INT64(fcinfo, 0) };
    let value = unsafe { pgrx_pg_sys::PG_GETARG_INT64(fcinfo, 1) };
    
    // 更新状态
    let new_state = state + value;
    
    // 返回新状态
    unsafe { pgrx_pg_sys::PG_RETURN_INT64(new_state) }
}

#[no_mangle]
pub extern "C" fn int8_sum_final(
    fcinfo: pgrx_pg_sys::FunctionCallInfo,
) -> pgrx_pg_sys::Datum {
    // 获取最终状态值
    let state = unsafe { pgrx_pg_sys::PG_GETARG_INT64(fcinfo, 0) };
    
    // 返回结果
    unsafe { pgrx_pg_sys::PG_RETURN_INT64(state) }
}

完整示例代码

以下是一个更完整的PostgreSQL扩展示例,包含多种函数类型和错误处理:

//! 一个完整的PostgreSQL扩展示例

use pgrx_pg_sys::{
    Datum, Oid, PG_FUNCTION_INFO_V1, PG_MODULE_MAGIC, Pg_magic_struct, 
    ERROR, ereport, WARNING, ERRCODE_INVALID_PARAMETER_VALUE
};

// PostgreSQL模块魔术结构体
PG_MODULE_MAGIC;

// 1. 简单标量函数示例:计算平方
PG_FUNCTION_INFO_V1!(square);

#[no_mangle]
pub extern "C" fn square(fcinfo: pgrx_pg_sys::FunctionCallInfo) -> Datum {
    // 检查参数个数
    unsafe {
        if pgrx_pg_sys::PG_NARGS() != 1 {
            ereport(
                ERROR,
                pgrx_pg_sys::errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                pgrx_pg_sys::errmsg("square函数需要一个参数")
            );
        }
    }

    // 获取输入参数
    let arg = unsafe { pgrx_pg_sys::PG_GETARG_FLOAT8(fcinfo, 0) };
    
    // 计算平方
    let result = arg * arg;
    
    // 返回结果
    unsafe { pgrx_pg_sys::PG_RETURN_FLOAT8(result) }
}

// 2. 文本处理函数示例:字符串反转
PG_FUNCTION_INFO_V1!(reverse_string);

#[no_mangle]
pub extern "C" fn reverse_string(fcinfo: pgrx_pg_sys::FunctionCallInfo) -> Datum {
    // 获取文本输入参数
    let text_ptr = unsafe { pgrx_pg_sys::PG_GETARG_TEXT_PP(fcinfo, 0) };
    
    // 获取文本长度和数据指针
    let len = unsafe { pgrx_pg_sys::VARSIZE_ANY_EXHDR(text_ptr) };
    let data_ptr = unsafe { pgrx_pg_sys::VARDATA_ANY(text_ptr) };
    
    // 将文本转换为Rust字符串切片
    let text_slice = unsafe { std::slice::from_raw_parts(data_ptr as *const u8, len) };
    
    // 反转字符串
    let mut reversed = text_slice.to_vec();
    reversed.reverse();
    
    // 创建新的文本值
    let result = unsafe {
        let result_ptr = pgrx_pg_sys::palloc(len + pgrx_pg_sys::VARHDRSZ) as *mut pgrx_pg_sys::text;
        pgrx_pg_sys::SET_VARSIZE(result_ptr, len + pgrx_pg_sys::VARHDRSZ);
        std::ptr::copy_nonoverlapping(
            reversed.as_ptr(),
            pgrx_pg_sys::VARDATA(result_ptr),
            len
        );
        result_ptr
    };
    
    // 返回结果
    unsafe { pgrx_pg_sys::PG_RETURN_TEXT_P(result) }
}

// 3. 聚合函数示例:计算平均值
PG_FUNCTION_INFO_V1!(avg_state);
PG_FUNCTION_INFO_V1!(avg_final);

// 聚合状态结构体
#[repr(C)]
struct AvgState {
    sum: f64,
    count: i64,
}

#[no_mangle]
pub extern "C" fn avg_state(
    fcinfo: pgrx_pg_sys::FunctionCallInfo,
) -> pgrx_pg_sys::Datum {
    // 获取状态指针
    let state_ptr = if unsafe { pgrx_pg_sys::PG_ARGISNULL(0) } {
        // 初始化新状态
        let state_ptr = unsafe {
            pgrx_pg_sys::palloc(std::mem::size_of::<AvgState>()) as *mut AvgState
        };
        unsafe {
            (*state_ptr).sum = 0.0;
            (*state_ptr).count = 0;
        }
        state_ptr
    } else {
        unsafe { pgrx_pg_sys::PG_GETARG_POINTER(0) as *mut AvgState }
    };

    // 获取输入值
    if unsafe { !pgrx_pg_sys::PG_ARGISNULL(1) } {
        let value = unsafe { pgrx_pg_sys::PG_GETARG_FLOAT8(1) };
        unsafe {
            (*state_ptr).sum += value;
            (*state_ptr).count += 1;
        }
    }

    // 返回状态
    unsafe { pgrx_pg_sys::PG_RETURN_POINTER(state_ptr as *mut std::ffi::c_void) }
}

#[no_mangle]
pub extern "C" fn avg_final(
    fcinfo: pgrx_pg_sys::FunctionCallInfo,
) -> pgrx_pg_sys::Datum {
    // 获取状态
    let state_ptr = unsafe { pgrx_pg_sys::PG_GETARG_POINTER(0) as *mut AvgState };
    
    // 计算平均值
    let result = if unsafe { (*state_ptr).count } > 0 {
        unsafe { (*state_ptr).sum / (*state_ptr).count as f64 }
    } else {
        // 如果没有输入,返回NULL
        unsafe { pgrx_pg_sys::PG_RETURN_NULL() }
    };
    
    // 返回结果
    unsafe { pgrx_pg_sys::PG_RETURN_FLOAT8(result) }
}

// 4. 触发函数示例
PG_FUNCTION_INFO_V1!(log_trigger);

#[no_mangle]
pub extern "C" fn log_trigger(fcinfo: pgrx_pg_sys::FunctionCallInfo) -> Datum {
    unsafe {
        // 获取触发器数据
        let trigdata = pgrx_pg_sys::PG_GETARG_TRIGGER(fcinfo);
        
        // 记录日志消息
        ereport(
            WARNING,
            pgrx_pg_sys::errmsg("触发器 %s 在表 %s 上执行",
                (*trigdata).tgname,
                pgrx_pg_sys::get_rel_name((*trigdata).tgrelid)
            )
        );
        
        // 返回触发器结果
        pgrx_pg_sys::PointerGetDatum(std::ptr::null_mut())
    }
}

使用说明

  1. 首先需要安装PostgreSQL开发头文件和pgrx工具链
  2. 使用cargo pgrx init初始化项目
  3. 编写扩展代码,如上面的示例
  4. 使用cargo pgrx run测试扩展
  5. 使用cargo pgrx package打包扩展

注意事项

  • 使用pgrx-pg-sys需要处理大量unsafe代码
  • 必须遵循PostgreSQL扩展开发的规范
  • 内存管理需要特别注意,避免内存泄漏
  • 错误处理需要符合PostgreSQL的机制

这个库提供了与PostgreSQL内部API的直接绑定,适合需要高性能或访问PostgreSQL内部功能的扩展开发。


1 回复

Rust PostgreSQL扩展开发库pgrx-pg-sys的使用

概述

pgrx-pg-sys 是一个用于开发 PostgreSQL 扩展的 Rust 库,它提供了与 PostgreSQL 系统交互的低级接口。这个库是 pgrx 框架的一部分,专门用于构建高性能的 PostgreSQL 扩展。

主要特性

  • 提供 PostgreSQL C API 的 Rust 绑定
  • 支持开发自定义函数、操作符、数据类型等
  • 内存安全地与 PostgreSQL 系统交互
  • 高性能的 Rust 实现
  • 支持 PostgreSQL 9.5 到最新版本

安装与配置

首先,在 Cargo.toml 中添加依赖:

[dependencies]
pgrx-pg-sys = "0.7"

完整示例

以下是基于内容中提供的示例整合的一个完整 PostgreSQL 扩展开发示例:

// lib.rs
use pgrx_pg_sys::{Datum, Oid, PG_FUNCTION_ARGS, PG_RETURN_DATUM, PG_RETURN_TEXT_P};
use pgrx_pg_sys::{Pg_magic_func, pg_magic};
use pgrx_pg_sys::{ereport, ERROR, ERRCODE_INVALID_PARAMETER_VALUE};

// 基本函数示例:整数加1
#[no_mangle]
pub unsafe extern "C" fn add_one(fcinfo: PG_FUNCTION_ARGS) -> Datum {
    let arg = pgrx_pg_sys::PG_GETARG_INT32(fcinfo, 0);
    let result = arg + 1;
    PG_RETURN_DATUM(result as Datum)
}

// 处理文本数据:字符串反转
#[no_mangle]
pub unsafe extern "C" fn reverse_text(fcinfo: PG_FUNCTION_ARGS) -> Datum {
    let text_ptr = pgrx_pg_sys::PG_GETARG_TEXT_PP(fcinfo, 0);
    let text_cstr = pgrx_pg_sys::text_to_cstring(text_ptr);
    let text_str = std::ffi::CStr::from_ptr(text_cstr).to_str().unwrap();
    
    let reversed: String = text_str.chars().rev().collect();
    let reversed_cstr = std::ffi::CString::new(reversed).unwrap();
    
    PG_RETURN_TEXT_P(pgrx_pg_sys::cstring_to_text(reversed_cstr.as_ptr()))
}

// 安全除法函数,带错误处理
#[no_mangle]
pub unsafe extern "C" fn safe_divide(fcinfo: PG_FUNCTION_ARGS) -> Datum {
    let a = pgrx_pg_sys::PG_GETARG_FLOAT8(fcinfo, 0);
    let b = pgrx_pg_sys::PG_GETARG_FLOAT8(fcinfo, 1);
    
    if b == 0.0 {
        ereport(
            ERROR,
            (
                ERRCODE_INVALID_PARAMETER_VALUE,
                "division by zero".to_string().as_ptr(),
            ),
        );
    }
    
    PG_RETURN_FLOAT8(a / b)
}

// PostgreSQL 扩展初始化函数
#[no_mangle]
pub unsafe extern "C" fn _PG_init() {
    pg_magic::Pg_magic_func();
    
    // 注册所有函数
    pgrx_pg_sys::pg_finfo!(add_one);
    pgrx_pg_sys::pg_finfo!(reverse_text);
    pgrx_pg_sys::pg_finfo!(safe_divide);
}
-- extension.sql
CREATE OR REPLACE FUNCTION add_one(integer) 
RETURNS integer 
AS 'MODULE_PATHNAME', 'add_one'
LANGUAGE C STRICT;

CREATE OR REPLACE FUNCTION reverse_text(text) 
RETURNS text 
AS 'MODULE_PATHNAME', 'reverse_text'
LANGUAGE C STRICT;

CREATE OR REPLACE FUNCTION safe_divide(float8, float8) 
RETURNS float8 
AS 'MODULE_PATHNAME', 'safe_divide'
LANGUAGE C STRICT;
// build.rs
fn main() {
    pgrx_pg_sys::build();
}

构建与部署步骤

  1. 创建项目结构:
cargo new --lib pg_extension_example
cd pg_extension_example
  1. 添加依赖到 Cargo.toml:
[package]
name = "pg_extension_example"
version = "0.1.0"
edition = "2021"

[lib]
name = "pg_extension_example"
crate-type = ["cdylib"]

[dependencies]
pgrx-pg-sys = "0.7"
  1. 构建扩展:
cargo build --release
  1. 安装到 PostgreSQL:
cp target/release/libpg_extension_example.so $(pg_config --pkglibdir)
psql -c "CREATE EXTENSION IF NOT EXISTS pg_extension_example"
  1. 测试函数:
SELECT add_one(5);  -- 返回6
SELECT reverse_text('hello');  -- 返回'olleh'
SELECT safe_divide(10.0, 2.0);  -- 返回5.0

注意事项

  1. 所有 PostgreSQL 函数必须标记为 unsafe extern "C"
  2. 注意 PostgreSQL 的内存管理机制,不要混合使用 Rust 和 PostgreSQL 的内存分配
  3. 确保正确处理错误条件,避免内存泄漏
  4. 测试时考虑不同 PostgreSQL 版本的兼容性
  5. 复杂的扩展开发建议使用完整的 pgrx 框架,而不仅仅是 pgrx-pg-sys

这个完整示例展示了如何使用 pgrx-pg-sys 开发包含多种功能的 PostgreSQL 扩展,包括基本函数、文本处理和错误处理等常见场景。

回到顶部