PostgreSQL SQL扩展 ---- C语言函数(二)

本文涉及的产品
云原生数据库 PolarDB MySQL 版,通用型 2核8GB 50GB
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
简介: 可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)

四、编码规范

在我们转向更高级主题之前,我们将讨论一些PostgreSQL C语言函数的编码规则。虽然将非C语言编写的函数载入PostgreSQL是可能的,但这通常比较困难,因为诸如C++,FORTRAN,或者Pascal语言往往有着与C语言不同的调用约定。这意味着其他语言无法用C语言的方式在函数间传递参数和返回值。基于此,这里我们假设你的C语言函数是真真正正用C语言编写的。

编写、构建C函数的基本规则如下:

  • 使用 pg_config --includedir-server 检查你或者你用户的系统中是否安装了PostgreSQL服务端开发头文件。
  • 编译和链接可动态加载到PostgreSQL中的代码需要一些特定的标志。本文第五节会详细说明如何在特定的操作系统上完成此操作。
  • 切记要为你的共享库定义magic block”。
  • 分配内存时,请使用PostgreSQL的函数 pallocpfree 替代C语言库函数 mallocfree 。通过palloc 分配的内存在每个事务完成后会自动释放以预防内存泄露。
  • 使用结构前,一定要使用 memset 将结构的每个字节置0(或者在首次为它分配空间时使用 palloc0 )。因为即使给结构的每个字段都赋了值,也有可能仍旧存在为对齐而产生的含有垃圾值的填充字节(结构中的空洞)。不这样做,就很难支持hash索引或hash join,因为只能使用数据结构的有效位去计算hash值。有时规划器也会通过逐位比较来判断常量是否相等,因此如果逻辑上相等的值逐位比较却不相等,就会得到不想要的规划结果,从而产生错误的执行计划。
  • postgres.h中声明了一些PostgreSQL的内部类型,函数管理接口(PG_FUNCTION_ARGS......)则在 fmgr.h中,因此程序至少要包含这两个头文件。基于可移植性考虑,最好首先包含 postgres.h ,将其放在其他系统或用户头文件之前。包含 postgres.h 会自动包含 elog.hpalloc.h
  • 目标文件里的符号名必须唯一,不能与已经加载到PostgreSQL服务中的其他可执行符号名冲突。如果收到与此相关的错误消息,必须重命名相关的函数或变量。

五、编译链接动态加载函数

要想使用C语言编写的PostgreSQL扩展函数,必须使用特定的方法编译链接生成一个可被动态加载到服务器中的文件。准确的说,需要创建一个共享库。

本小节不会介绍C编译器、cc、链接器ld,相关内容请参阅你操作系统文档中的手册页。PostgreSQL的源码中也提供了几个可工作的示例,位于contrib目录。这些例子需要可用的PostgreSQL源码。

创建共享库与链接可执行文件类似:先将源码编译成目标文件,然后将目标文件链接到一起。目标文件要创建成位置无关代码(position-independent code (PIC)),从概念上讲,可执行文件可以将它们加载到内存中的任意位置。(适用于可执行文件的目标文件通常不用这种方式编译)。链接共享库时会使用一些特定的选项以与链接可执行文件区分开来。

接下来的示例中,我们假设你的源码名为foo.c,创建的共享库名为foo.so。除非另有说明,中间目标文件名为foo.o。共享库可包含不止一个目标文件,这里我们仅使用一个。

FreeBSD

创建PIC的编译选项是 -fPIC 。创建共享库的编译器选项是 -shared

gcc-fPIC-c foo.c
gcc-shared-o foo.so foo.o

Linux

创建PIC的编译选项是 -fPIC。创建共享库的编译选项是 -shared。下面是一个完整的示例:

cc-fPIC-c foo.c
cc-shared-o foo.so foo.o

macOS

假设已安装好开发工具,示例如下:

cc-c foo.c
cc-bundle-flat_namespace-undefined suppress -o foo.so foo.o

NetBSD

创建PIC的编译选项是 -fPIC。对于ELF系统,编译共享库的编译选项是 -shared 。对于早期的非ELF系统,使用 ld -Bshareable

gcc-fPIC-c foo.c
gcc-shared-o foo.so foo.o

OpenBSD

创建PIC的编译选项是 -fPIC。使用 ld -Bshareable 链接共享库。

gcc-fPIC-c foo.c
ld -Bshareable-o foo.so foo.o

Solaris

使用Sun编译器创建 PIC 的编译选项是 -KPIC ,使用GCC编译器的编译选项是 -fPIC 。链接共享库,可使用 -G 选项,对于GCC,也可以使用 -shared

cc-KPIC-c foo.c
cc-G-o foo.so foo.o

或者

gcc-fPIC-c foo.c
gcc-G-o foo.so foo.o

生成好的共享库文件可被加载到。给 CREATE FUNCTION 命令指定的文件名必须是共享库的名称而非中间目标文件的。 CREATE FUNCTION 命令可以省略系统标准的共享库扩展名(通常是 .so.sl),省略掉扩展名会有更好的可移植性。

六、组合类型参数

组合类型不具有类似C语言结构那样的固定布局。组合类型实例可以有空字段。此外,属于继承层次结构一部分的组合类型可以具有与同一继承层次结构的其他成员不同的字段。因此,PostgreSQL提供了一个函数接口,用于从C语言访问组合类型的字段。

Suppose we want to write a function to answer the query:

SELECT name, c_overpaid(emp,1500)AS overpaid
FROM emp
WHERE name ='Bill'OR name ='Sam';

使用版本-1调用约定,定义 c_overpaid 如下:

#include "postgres.h"#include "executor/executor.h"  /* for GetAttributeByName() */PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(c_overpaid);
Datumc_overpaid(PG_FUNCTION_ARGS)
{
HeapTupleHeadert=PG_GETARG_HEAPTUPLEHEADER(0);
int32limit=PG_GETARG_INT32(1);
boolisnull;
Datumsalary;
salary=GetAttributeByName(t, "salary", &isnull);
if (isnull)
PG_RETURN_BOOL(false);
/* Alternatively, we might prefer to do PG_RETURN_NULL() for null salary. */PG_RETURN_BOOL(DatumGetInt32(salary) >limit);
}

GetAttributeByNamePostgreSQL的系统函数,用于返回指定行的属性。它有三个参数:传递到函数中的HeapTupleHeader 类型, 所需属性的名称,以及告知该属性是否为空的返回参数。GetAttributeByName 返回一个 Datum 值,你可以使用相应的 DatumGetXXX() 函数将其转换到正确的数据类型。如果设置了null标志,则返回值没有意义;在尝试对结果执行任何操作之前,请始终检查null标志。

另外,GetAttributeByNum 通过列号而不是名字来选择目标属性。

以下命令声明供SQL调用的 c_overpaid 函数:

CREATE FUNCTION c_overpaid(emp,integer) RETURNS booleanAS'DIRECTORY/funcs','c_overpaid'    LANGUAGE C STRICT;

这里使用 STRICT 可不必检查输入参数是否为空。

七、返回行(组合类型)

从C语言函数返回一行或组合类型值,可以使用一组提供了宏和函数的特定API以隐藏构造组合数据类型的复杂性。使用这些API需要在源码中包含:

#include "funcapi.h"

构造组合类型(以后称为元组)有两种途径:从Datum值数组构造,或者从一个能被传递到元组列数据类型的输入转换函数的C字符串数组构造。不管用哪种方式,首先需要为元组结构获取或者构造一个 TupleDesc 描述符。当使用 Datums时,传递 TupleDescBlessTupleDesc,然后为每一行调用 heap_form_tuple 。当使用C字符串时,传递 TupleDescTupleDescGetAttInMetadata,然后为每一行调用 BuildTupleFromCStrings 。在返回元组集合的场景,在函数首次调用时可一次完成全部的设置步骤。

有几个辅助函数可用于设置所需的 TupleDesc。在大多数返回组合值的函数中建议如下调用:

TypeFuncClassget_call_result_type(FunctionCallInfofcinfo,
Oid*resultTypeId,
TupleDesc*resultTupleDesc)

将相同的 fcinfo 结构传递给调用函数本身(当然,这需要使用版本-1调用约定)。 resultTypeId 可以置空或者设置成一个局部变量地址用以接收函数的返回类型OID。 resultTupleDesc 可以是局部 TupleDesc 变量的地址。 TYPEFUNC_COMPOSITE 用于检查结果。这样做后, resultTupleDesc 已被需要的 TupleDesc 填充。(如果没有,可以报告错误function returning record called in context that cannot accept type record”。

获取TupleDesc比较旧的已经废弃的函数是:

TupleDescRelationNameGetTupleDesc(constchar*relname)

用于获取命名relation行类型的 TupleDesc

TupleDescTypeGetTupleDesc(Oidtypeoid, List*colaliases)

用于获取基于一个类型的OID的 TupleDesc 。可用于获取一个基类型或者组合类型的 TupleDesc 。无法用于返回 record 的函数,也无法解析多态类型。

一旦有了 TupleDesc ,调用

TupleDescBlessTupleDesc(TupleDesctupdesc)

如果计划使用Datums。

可使用:

AttInMetadata*TupleDescGetAttInMetadata(TupleDesctupdesc)

如果计划使用C字符串。如果写一个返回集合的函数,可以保存这些函数的结果到 FuncCallContext 结构 — 分别使用 tuple_descattinmeta 字段。

当采用Datums,使用

HeapTupleheap_form_tuple(TupleDesctupdesc, Datum*values, bool*isnull)

用给定的采用Datum方式的用户数据构造 HeapTuple

当采用C字符串方式,使用

HeapTupleBuildTupleFromCStrings(AttInMetadata*attinmeta, char**values)

用给定的采用C字符串方式的用户数据构造 HeapTuplevalues 是一个C字符串数组,每个数组表示返回行的一个属性。每一个C字符串都应该是属性数据类型输入函数所期望的形式。要给其中一个属性返回空值,需要将 values 数组中相应指针设置为 NULL 。每返回一行都需要调用此函数一次。

一旦从函数中构造好要返回的元组,它必须被转换成 Datum 。可使用

HeapTupleGetDatum(HeapTupletuple)

去转换 HeapTuple 到合法的Datum。如果函数返回单行,可直接返回些 Datum ,或在返回数据集的函数中做为当前行返回。

具体示例见下节。


相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
打赏
0
0
0
0
18
分享
相关文章
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
117 23
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
22 1
一文彻底搞清楚C语言的函数
|
2月前
|
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
127 15
|
2月前
|
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
70 24
|
2月前
|
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
107 16
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
61 3
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
40 2
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
73 1
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
124 10
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
92 9