利用pgrx开发postgre插件

打印 上一主题 下一主题

主题 845|帖子 845|积分 2535

pg插件开发发起在linux情况下进行, windows可以采用虚拟机的方式开发.
安装虚拟机情况


  • 准备虚拟机, 利用的是ubuntu22.04, 太新的版本会依赖拉取有问题
  • 安装开发工具和依赖:
    ​sudo apt-get update sudo apt-get install -y git clang gcc make build-essential libz-dev zlib1g-dev strace libssl-dev pkg-config libreadline-dev libpg-dev flex bison libxml2-dev libxslt-dev libxml2-utils xsltproc​
  • 安装rust: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh​
  • 安装cargo: sudo apt-get install cargo​
  • 安装pgrx: cargo install cargo-pgrx​
  • 初始化pgrx: cargo pgrx init​ 这会安装12 - 17版本的pg.
    耐心等待所有步骤完成后, 就可以利用pgrx新建模板来写插件了
hello pgrx

执行命令新建pg扩展项目: cargo pgrx new ​
利用天生的项目模板进行插件开发
调试

假如有需要, 可以修改自己要用的pg版本配置文件来方便调试, 例如要用pg16就改.pgrx/data-16

  • pg_hba.conf和pg_ident.conf 支持外部工具连接pg
  • postgresql.conf设置日志输出级别
    修改log_min_messages和client_min_messages
运行默认模板插件的函数

进入pgrx新建的项目目录后, 在控制台执行cargo pgrx run pg16表示在postgre16版本运行该插件.
默认端口起始是28800, 因此pg16开放的端口是28816(pg17就是28817),假如开放允许外部连接可以利用这个端口进去(pgrx run命令会自动接入到pg控制台模式)
执行sql安装pg扩展:
  1. create extension <project_name>;
  2. SELECT extname AS "Extension Name", extversion AS "Version"
  3. FROM pg_extension;
复制代码
的来源是自己建立的项目名称.
调试用的宏

例: use pgrx::{debug1, debug5}
按照pg级别利用, 有:

  • debug5!
  • debug4!
  • debug3!
  • debug2!
  • debug1!
  • info!
  • notice!
  • warning!
  • error!
  • log!
  • FATAL!
  • PANIC!
自定义类型

see: 自定义类型官方示例
自定义类型需要实现序列化和反序列化:
  1. #[derive(PostgresType, Deserialize, Serialize)]
  2. pub struct MyType {}
复制代码
重载运算符

see: 重载运算符官方示例
  1. #[pg_operator(immutable, parallel_safe)]
  2. #[opname(||)]
  3. fn mytype_concact(mut left: MyType, right: MyType) -> MyType {
  4.     left.0.push_str(&right.0);
  5.     left
  6. }
复制代码
处理panic

官方提供了一个叫做PgTryBuilder的实现, 用来捕获rust中的panic.
  1. #[pg_extern]
  2. fn maybe_panic(panic: bool, trap_it: bool, message: &str) {
  3.     PgTryBuilder::new(|| {
  4.         if panic {
  5.             panic!("panic says: {}", message)
  6.             // std::panic::panic_any(42)
  7.         }
  8.     })
  9.     .catch_rust_panic(|cause| {
  10.         // we can catch general Rust panics.  The `error` argument is a pg_sys::panic::CaughtError::RustPanic
  11.         // enum variant with the payload from the originating panic
  12.         if trap_it {
  13.             if let CaughtError::RustPanic { ereport, payload } = &cause {
  14.                 warning!("{:#?}", ereport);
  15.                 if let Some(s) = payload.downcast_ref::<String>() {
  16.                     // we have access to the panic!() message
  17.                     warning!("{}", s);
  18.                     return;
  19.                 } else {
  20.                     // this won't happen with this example, but say the `panic_any(42)` was used
  21.                     // instead.  Then we'd be here, and we can just raise another `panic!()`, which
  22.                     // will be what's ultimately reported to Postgres.
  23.                     //
  24.                     // In this case, Postgres' LOCATION error slot will be this line, and the CONTEXT
  25.                     // slot will show the line number of the original `panic_any(42)` above
  26.                     panic!("panic payload not a `String`");
  27.                 }
  28.             }
  29.             unreachable!("internal error:  `CaughtError` not a `::RustPanic`");
  30.         } else {
  31.             cause.rethrow()
  32.         }
  33.     })
  34.     // finally block always runs after the catch handlers finish (even if they rethrow or raise
  35.     // their own panic, like in this case)
  36.     .finally(|| warning!("FINALLY!"))
  37.     .execute()
  38. }
复制代码
在插件中执行SQL

see: 官方spi示例
see: hook列表(没有试过)
执行SQL需要用到pgrx提供的spi接口
这是一个在hook中执行sql的示例:
  1. #[pg_guard]
  2. pub unsafe extern "C" fn _PG_init() {
  3.         PgTryBuilder::new(|| {
  4.             let vec = generate_rsa_key().unwrap();
  5.             Spi::connect(move |mut client| {
  6.                 let init_schema = format!(
  7.                     r#"
  8.                     create schema if not exists  {};
  9.                 "#,
  10.                     PROJECT_SCHEMA
  11.                 );
  12.                 match client.update(init_schema.as_str(), None, None) {
  13.                     Ok(_) => {}
  14.                     Err(_) => {
  15.                         return;
  16.                     }
  17.                 };
  18.                 let init_sql = format!(
  19.                     r#"
  20.                 create table if not exists  {}.t_rsa_key(
  21.                     id serial8 not null primary key,
  22.                     public_key text,
  23.                     private_key text
  24.                 );
  25.             "#,
  26.                     PROJECT_SCHEMA
  27.                 );
  28.                 client.update(init_sql.as_str(), None, None).unwrap();
  29.                 let insert_sql = format!(
  30.                     r#"
  31.             insert into {}.t_rsa_key (public_key,private_key) values ('{}','{}');
  32.             "#,
  33.                     PROJECT_SCHEMA, vec[0], vec[1]
  34.                 );
  35.                 client.update(insert_sql.as_str(), None, None).unwrap();
  36.             })
  37.         })
  38.         .execute();
  39. }
复制代码
关于_PG_init被调用多次的解决方式

https://stackoverflow.com/questions/28037300/in-postgresql-why-is-pg-init-called-twice
https://github.com/pgcentralfoundation/pgrx/blob/develop/pgrx-examples/shmem/README.md
(TODO: 实在我也没行止理这个问题)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

悠扬随风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表