PostgreSQL插件开发

news/2024/7/9 21:16:35 标签: postgresql, 数据库

PostgreSQL中许多控制信息都是以系统表的形式来管理,这个特点决定了PostgreSQL比其他数据库更容易进行内核扩展。PostgreSQL还提供了丰富的数据库内核编程接口,允许开发者以插件的形式将自己的代码融入内核。

PostgreSQL插件开发非常简单,下面举一个例子,开发一个随机测试数据生成器。

插件名为pg_testgen,首先需要创建四个文件:

pg_testgen.control      # 插件名.control
pg_testgen.c            # 插件名.c
pg_testgen--1.0.sql     # 插件名--1.0.sql
Makefile                # 用于编译

pg_testgen.control是插件的控制文件,是一个只有五六行的模版,存放插件说明、默认版本号、模块路径、是否可重入等,只需要将插件名更改即可。

# pg_testgen extension
comment = 'PostgreSQL test generator'
default_version = '1.0'
module_pathname = '$libdir/pg_testgen'
relocatable = true

Makefile也基本上是模版,根据自己的插件名、文件名修改即可:

# contrib/pg_testgen/Makefile
MODULE_big = pg_testgen
OBJS = \        # 所有.c文件生产的同名.o目标文件
	$(WIN32RES) \
	pg_testgen.o
PGFILEDESC = "pg_testgen - test data generator"

PG_CPPFLAGS = -I$(libpq_srcdir)
SHLIB_LINK_INTERNAL = $(libpq)

EXTENSION = pg_testgen  # 插件名
DATA = pg_testgen--1.0.sql  # 插件的sql文件

REGRESS = pg_testgen # sql、expected 文件夹下的测试sql文件名

ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
SHLIB_PREREQS = submake-libpq
subdir = contrib/pg_testgen
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif

pg_testgen--1.0.sql是插件的初始化sql文件,1.0是版本号,如果需要版本更新到1.1,则需要pg_testgen--1.0--1.1.sql文件。文件的内容如下:

\echo Use "CREATE EXTENSION pg_testgen" to load this file. \quit

CREATE FUNCTION rand_int(integer, integer)
    RETURNS integer
    AS 'MODULE_PATHNAME', 'rand_int'
    LANGUAGE C STRICT;
......
......

该文件必须在psql中通过CREATE EXTENSION pg_testgen加载,否则会报第一行的错误。下面的语句是PL/pgSQL语句,支持如建表、插入、创建函数等PostgreSQL操作。我们希望使用C语言进行数据库内核的插件开发,所以这里需要创建函数并关联到对应的C语言函数。这里的rand_int(integer, integer)是有两个整数作为参数函数,返回值是整型,加载插件后使用rand_int(a, b)即可使用。PL/pgSQL语句定义的函数和C语言函数一般同名。

pg_testgen.c文件是插件开发的主要文件,首先需要引入两个必须的头文件,并声明PG_MODULE_MAGIC确保不会错误加载共享库文件:

#include "postgres.h"
#include "fmgr.h"

PG_MODULE_MAGIC;

对于在pg_testgen--1.0.sql中定义的C语言函数,需要在该文件中声明并实现:

PG_FUNCTION_INFO_V1(rand_int);
Datum rand_int(PG_FUNCTION_ARGS){
    int32 ret = 123;
    PG_RETURN_INT32(ret);
}

注意每个需要在psql中调用的函数都需要用PG_FUNCTION_INFO_V1(rand_int);来注册,而只在扩展内部调用的函数则不需要,且最好声明为static函数。

对于参数,由于是两个int类型,可以通过下面的宏定义来获取:

int32 arg1 = PG_GETARG_INT32(0);
int32 arg2 = PG_GETARG_INT32(1);

注意这里不能判断从psql中传入的参数个数,因为参数个数是可变的,对于C语言插件,函数名是唯一标识,不能支持C语言的函数重载。但PL/pgSQL定义的函数可以重载,如果需要重载函数,需要要在pg_testgen--1.0.sql中定义多个同名函数,这些函数都会调用同一个C函数,参数个数保存在fcinfo->nargs中,可以通过以下语句判断从psql中调用对应C函数的参数是否正确:

Assert(fcinfo->nargs == 0 || fcinfo->nargs == 2);

这个判断表示该C函数会被两个pg_testgen--1.0.sql中定义的函数调用,参数个数分别为0和2。如果psql中调用函数时参数个数错误,会直接报错、不会往下执行。

对于不同的参数类型,需要用不同的宏定义来获取,可以在"fmgr.h"头文件中查看:

PG_GETARG_INT32(0);
PG_GETARG_CHAR(1);
PG_GETARG_CSTRING(2);
PG_GETARG_FLOAT8(3);
......

对于不同的返回类型也需要用"fmgr.h"中的不同的宏定义:

PG_RETURN_INT32(x);
PG_RETURN_TEXT_P(x);
PG_RETURN_FLOAT8(x);
PG_RETURN_BOOL(x);
......

下面是一个完整的例子,一个重载了两个PL/pgSQL函数的C函数,如果没有参数则返回一个随机整数,如果有两个参数[a, b]则返回一个在[a, b]间的随机整数:

static inline int32 rand_int_internal(int min, int max){
    return rand() % (max - min + 1) + min;
}

PG_FUNCTION_INFO_V1(rand_int);
Datum rand_int(PG_FUNCTION_ARGS){
    Assert(fcinfo->nargs == 0 || fcinfo->nargs == 2);
    int32 min = fcinfo->nargs == 2 ? PG_GETARG_INT32(0) : 0;
    int32 max = fcinfo->nargs == 2 ? PG_GETARG_INT32(1) : INT32_MAX;
    PG_RETURN_INT32(rand_int_internal(min, max));
}

如果我们需要获取、返回一个text字符串类型,需要使用PostgreSQL提供的text结构体来封装,在分配内存时需要加上一个头部大小CARHDRSZVARDATA(t)表示t的数据部分起始地址,VARDATA_ANY(t)可以取到text的数据部分或者一个普通Datum的数据部分,VARSIZE_ANY_EXHDR(t)表示text除去头部的数据长度:

// 一个大小为size的text,分配内存时需要加上头部大小
text *t = (text *)palloc(VARHDRSZ + size);
SET_VARSIZE(t, VARHDRSZ + size);

// 将数据拷贝到t的数据部分
memcpy((void *) VARDATA(t), /* destination */
           (void *) VARDATA_ANY(src), /* source */
           VARSIZE_ANY_EXHDR(src));   /* how many bytes */

PG_RETURN_TEXT_P(t);  // 返回改text的指针

如果需要返回多行数据,需要使用SRF(set-returning functions),在pg_testgen--1.0.sql定义返回值时需要机上SETOF关键字:

CREATE FUNCTION rows_int(integer)
    RETURNS SETOF integer
    AS 'MODULE_PATHNAME', 'rows_int'
    LANGUAGE C STRICT;

CREATE FUNCTION rows_int(integer, integer, integer)
    RETURNS SETOF integer
    AS 'MODULE_PATHNAME', 'rows_int'
    LANGUAGE C STRICT;

在.c文件中,需要包含SRF头文件:

#include "funcapi.h"

SRF函数是递归调用的,和普通函数使用方法相同,只是需要套进SRF模版,以下是一个很简单的例子,可以当作模版使用,该函数接收一个参数r或者三个参数[r, a, b]。如果只有一个参数,返回r行随机整数;如果是三个参数,则返回r行[a, b]间的整数。

static inline int32 rand_int_internal(int min, int max){
    return rand() % (max - min + 1) + min;
}

PG_FUNCTION_INFO_V1(rows_int);
Datum
rows_int(PG_FUNCTION_ARGS)
{
    Assert(fcinfo->nargs == 1 || fcinfo->nargs == 3);
    
    FuncCallContext     *funcctx;
    int32 times = PG_GETARG_INT32(0);

    // 第一次调用SRF函数时的初始化
    if (SRF_IS_FIRSTCALL())
    {
        MemoryContext   oldcontext;

        /* create a function context for cross-call persistence */
        funcctx = SRF_FIRSTCALL_INIT();

        /* switch to memory context appropriate for multiple function calls */
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

       // 需要返回tuple的行数
        funcctx->max_calls = times;

        MemoryContextSwitchTo(oldcontext);
    }

    // 每次调用SRF函数都需要的设置
    funcctx = SRF_PERCALL_SETUP();

    if (funcctx->call_cntr < funcctx->max_calls)    // 还需要返回更多行
    {
        int32 min = fcinfo->nargs == 3 ? PG_GETARG_INT32(1) : 0;
        int32 max = fcinfo->nargs == 3 ? PG_GETARG_INT32(2) : INT32_MAX;
        SRF_RETURN_NEXT(funcctx, Int32GetDatum(rand_int_internal(min, max)));   // 返回一行
    }
    else    // 已经返回足够行
    {
        SRF_RETURN_DONE(funcctx);       // SRF函数结束
    }
}

funcctx->max_calls是需要调用SRF函数的次数,即返回的行数。

为了方便对插件进行功能测试,可以创建sqlexpected目录,sql目录下的pg_testgen.sql是Makefile里REGRESS字段指明的测试sql,expected目录下同名的pg_testgen.out是对应测试sql的期望输出,使用make check可以很方便的进行测试。测试会生成log目录存放数据库的日志,生成results目录存放实际的输出。

开发插件完成后,将插件目录放在contrib目录下,比如contrib/pg_testgen,进入插件目录,使用make命令编译,然后make install安装插件。如果成功,用psql连接数据库,用CREATE EXTENSION pg_testgen;加载插件,即可使用。

一个最简单的、完整的插件源代码:pg_testgen


http://www.niftyadmin.cn/n/1842708.html

相关文章

PostgreSQL内存上下文

不管是什么样的数据库系统&#xff0c;存储管理的本质都是一样的&#xff1a;如何减少I/O次数。内存的访问速度至少是磁盘的数十万倍&#xff0c;所以通常读写磁盘所用的时间决定了数据库操作的总时间&#xff0c;而内存的访问时间可以忽略不计。因此&#xff0c;要尽可能的提高…

git重置或还原已提交的代码--git reset和git revert

文章目录背景两种方式git resetgit revert背景 在多人协作时&#xff0c;对代码的管理&#xff0c;难免会出现错误。如果出现错误&#xff0c;我们分析下来是要进行一些回退操作&#xff0c;那么解决办法有如下两种&#xff1a; git reset git revert 两种方式 git reset 在…

PostgreSQL文本搜索(一)——简介

PostgreSQL文本搜索&#xff08;一&#xff09;——简介 文本搜索对于数据库系统来说是一个十分重要的功能&#xff0c;它可以在数据库文本文档&#xff08;包括表中的text字段等&#xff09;中搜索一个模式&#xff0c;可以是一个或多个单词、短语、短句&#xff0c;并且可以…

git clone时出现error: RPC failed; curl 18 transfer closed with outstanding read data remaining

目录问题原因1解决原因2解决打开下面的网站获取信息配置更新DNS问题 使用git bash 从github clone代码时遇到下面的错误 error: RPC failed; curl 18 transfer closed with outstanding read data remaining fatal: The remote end hung up unexpectedly fatal: early EOF fa…

PostgreSQL文本搜索(二)——表和索引

PostgreSQL文本搜索&#xff08;二&#xff09;——表和索引 上一节的例子说明了使用简单的常量字符串进行全文匹配。本节展示了如何搜索表数据&#xff0c;或选择使用索引。 搜索表 可以在没有索引的情况下进行全文检索。一个简单的查询是打印在body字段中包含friend这个词…

索引 - 学习总结

目录概述什么是索引索引的优缺点MySQL中索引的基本用法创建索引删除索引测试索引SQL SEERVER中索引的基本用法创建索引创建聚集索引创建复合索引创建覆盖索引创建唯一索引查看索引MySQL设计索引的原则SQL Server索引设计原则索引的数据结构聚集索引和非聚集索引复合索引的使用参…

PostgreSQL文本搜索(五)——词法分析器

PostgreSQL文本搜索&#xff08;五&#xff09;——词法分析器 文本搜索词法分析器负责将原始文档文本分割成token&#xff0c;并确定每个token的类型&#xff0c;其中可能的类型集合由词法分析器本身定义。注意分析器根本不修改文本&#xff0c;它只是识别合理的词的边界。由…

解决:PLS-00103: Encountered the symbol “CREATE”

文章目录问题描述问题解决问题描述 在oracle中&#xff0c;需要修改一个PACKAGE中某一个procedure的逻辑&#xff0c;修改完后&#xff0c;将 package定义和package body放在一个脚本中来执行时&#xff0c;遇到下面的错误&#xff1a; PLS-00103: Encountered the symbol “C…