PostgreSQL内核开发过程介绍(二)-用户自定义函数

用户自定义C函数

  • 创建一个C函数
  • 编译成一个共享库
  • 在PostgreSQL中创建一个新函数

PostgreSQL 提供了四种函数

  • SQL函数
  • 过程语言函数
  • 内部函数
  • C语言函数

SQL函数

SQL 函数执行一个由任意 SQL 语句构成的列表,返回列表中最后一个查询的结果。

简单情况(非集合)中:最后一个查询的结果的第一行将被返回,如果最后一个查询正好根本不返回行,将会返回空值。
集合情况中:SQL 函数可以通过指定函数的返回类型为SETOF sometype被声明为返回一个集合(也就是多个行),或者等效地声明它为RETURNS TABLE(columns)。在这种情况下,最后一个查询的结果的所有行会被返回。

SQL语言中的任何命令集合都能被打包在一起并且被定义成一个函数。除了SELECT查询,命令可以包括数据修改查询(INSERT、UPDATE以及DELETE)和其他 SQL 命令(你不能在SQL函数中使用事务控制命令,例如COMMIT、SAVEPOINT,以及一些工具命令,例如VACUUM)。不过,最后一个命令必须是一个SELECT或者带有一个RETURNING子句,该命令必须返回符合函数返回类型的数据。或者,如果你想要定义一个执行动作但是不返回有用的值的函数,你可以把它定义为返回void。

简单情况示例

postgres=# CREATE OR REPLACE FUNCTION hello(prompt text) RETURNS text  AS $$
postgres$#     SELECT 'hello1 ' || prompt; 
postgres$#     SELECT 'hello2 ' || prompt; 
postgres$# $$ LANGUAGE SQL;
CREATE FUNCTION
postgres=# select hello('world');
    hello     
--------------
 hello2 world
(1 row)

过程语言函数(PL/pgSQL)

语法如下

CREATE FUNCTION somefunc(integer, text) RETURNS integer AS $$ 
    function body text
$$ LANGUAGE plpgsql;

简单示例:

postgres=# CREATE OR REPLACE FUNCTION hello2(prompt text) RETURNS text AS $$
postgres$# BEGIN
postgres$#     return 'hello ' || prompt;
postgres$# END;
postgres$# $$ LANGUAGE plpgsql;
CREATE FUNCTION
postgres=# select hello2('world');
   hello2    
-------------
 hello world

内部函数

内部函数由 C 编写并且已经被静态链接到LightDB 服务器中。该函数定义的“主体”指定该函数的 C 语言名称, 它必须和声明 SQL 函数所用的名称一样(为了向后兼容性的原因,也接受空body,那时会认为 C 语言函数名与 SQL 函数名相同)。

简单示例

postgres=# CREATE OR REPLACE FUNCTION square_root(double precision) RETURNS double precision
    AS 'dsqrt'
    LANGUAGE internal
    STRICT;
CREATE FUNCTION
postgres=# select square_root(4);
 square_root 
-------------
           2

C语言函数

用户定义的函数可以用 C 编写(或者可以与 C 兼容的语言,例如 C++)。 这类函数被编译成动态载入对象(也被称为共享库)并且由服务器在 需要时载入。动态载入是把“C语言”函数和 “内部”函数区分开的特性 — 两者真正的编码习惯 实际上是一样的(因此,标准的内部函数库是用户定义的 C 函数很好 的源代码实例)。

当前仅有一种调用约定被用于C函数(“版本1”)。如下文所示,为函数编写一个PG_FUNCTION_INFO_V1()宏就能指示对该调用约定的支持。

动态载入

在一个会话中第一次调用一个特定可载入对象文件中的用户定义函数时, 动态载入器会把那个对象文件载入到内存以便该函数被调用。因此用户 定义的 C 函数的CREATE FUNCTION必须 为该函数指定两块信息:可载入对象文件的名称,以及要在该对象文件中 调用的特定函数的 C 名称(链接符号)。如果没有显式指定 C 名称,则 它被假定为和 SQL 函数名相同。

下面的算法被用来基于CREATE FUNCTION 命令中给定的名称来定位共享对象文件:

  1. 如果名称是一个绝对路径,则载入给定的文件。
  2. 如果该名称以字符串$libdir开始,那么这一部分会被 LightDB包的库目录名(在编译时确定)替换。
  3. 如果该名称不包含目录部分,会在配置变量 dynamic_library_path指定的路径中搜索该 文件。
  4. 否则(在该路径中没找到该文件,或者它包含一个非绝对目录), 动态载入器将尝试接受给定的名称,这大部分会导致失败(依赖 当前工作目录是不可靠的)。

为了确保动态载入对象文件不会被载入到一个不兼容的服务器, PostgreSQL会检查该文件是否包含一个 带有合适内容的“magic block”。这允许服务器检测到明显的不兼 容,例如为不同PostgreSQL主版本编译 的代码。要包括一个 magic block,在写上包括 头文件fmgr.h的语句之后,在该模块的源文件之一(并且只 能在其中一个)中写上这些:

PG_MODULE_MAGIC;

在被第一次使用后,动态载入对象文件会留在内存中。在同一个会话中 对该函数未来的调用将只会消耗很小的负荷进行符号表查找。如果需要 重新载入一个对象文件(例如重新编译以后),需要开始一个新的会话。

可以选择让一个动态载入文件包含初始化和终止化函数。如果文件包含一个 名为_PG_init的函数,则文件被载入后会立刻调用该函数。 该函数不接受参数并且应该返回 void。如果文件包括一个名为 _PG_fini的函数,则在卸载该文件之前会立即调用该函数。 同样地,该函数不接受参数并且应该返回 void。注意将只在卸载文件的过程 中会调用_PG_fini,进程结束时不会调用它(当前,卸载被 禁用并且从不发生,但是未来可能会改变)。

基本数据类型

基本类型可以有三种内部格式之一:

  • 传值,定长
  • 传引用,定长
  • 传引用,变长

传值类型在长度上只能是 1、2 或 4 字节(如果你的机器上 sizeof(Datum)是 8,则还有 8 字节)。你应当小心地 定义你的类型以便它们在所有的架构上都是相同的尺寸(字节)。例如, long类型很危险,因为它在某些机器上是 4 字节但在 另外一些机器上是 8 字节,而int类型在大部分 Unix 机器 上都是 4 字节。

在PostgreSQL函数中传进或传出这种 类型时,只能使用指向这种类型的指针。要返回这样一种类型的值,用 palloc分配正确的内存量,然后填充分配好的内存, 并且返回一个指向该内存的指针(还有,如果只想返回与具有相同数据类型的 一个输入参数相同的值,可以跳过额外的palloc并且返回 指向该输入值的指针)。

最后,所有变长类型必须也以引用的方式传递。所有变长类型必须用一个 正好 4 字节的不透明长度域开始,该域会由SET_VARSIZE 设置,绝不要直接设置该域!所有要被存储在该类型中的数据必须在内存 中接着该长度域的后面存储。长度域包含该结构的总长度,也就是包括长 度域本身的尺寸。

另一个重点是要避免在数据类型值中留下未被初始化的位。例如,要注意 把可能存在于结构中的任何对齐填充字节置零。如果不这样做,你的数据 类型的逻辑等价常量可能会被规划器认为是不等的,进而导致低效的(不过 还是正确的)计划。

绝不要修改通过引用传递的输入值的内容。如果这样做很可能会破坏磁盘上的数据,因为给出的指针可能直接指向一个磁盘缓冲 区。

版本 1 的调用约定

版本-1 的调用规范依赖于宏来降低传参数和结果的复杂度。版本-1 函数的 C 声明总是:

PG_FUNCTION_INFO_V1(funcname);

Datum funcname(PG_FUNCTION_ARGS)`

在版本-1 函数中,每一个实参都使用对应于该参数数据类型的PG_GETARG_xxx()宏取得。(在非严格的函数中,需要使用PG_ARGISNULL()对参数是否为空提前做检查;见下文。)结果要用对应于返回类型的PG_RETURN_xxx()宏返回。PG_GETARG_xxx()的参数是要取得的函数参数的编号,从零开始计。PG_RETURN_xxx()的参数是实际要返回的值。

示例
步骤1:编写C函数

#include <postgres.h>
#include <fmgr.h>
#include <utils/palloc.h>
#include <math.h>

PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(my_add);
Datum my_add(PG_FUNCTION_ARGS)
{
    int32 a = PG_GETARG_INT32(0);
    int32 b = PG_GETARG_INT32(1);
    int32 result = a + b;
    PG_RETURN_INT32(result);
}

步骤2:编译成共享库并放到PostgreSQL lib目录

编译

gcc -I/home/postgres/postgresql-14.7/src/include -fPIC -c my_math.c -o my_math.o
gcc -shared my_math.o -o my_math.so

安装

cp my_math.so /usr/local/pgsql/lib/

步骤3:创建一个PostgreSQL函数定义

create or replace function my_add(a int4, b int4) returns int4 as
'$libdir/my_math.so', 'my_add' 
language c strict;

这里SQL类型int4 代表PostgreSQL C类型 int32 ,在PostgreSQL中习惯是intXX 表示XX 位

步骤4:使用

postgres=# select my_add(1, 2);
 my_add 
--------
      3
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值