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

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介: 可以用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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
19天前
|
存储 编译器 C语言
爱上C语言:函数递归,青蛙跳台阶图文详解
爱上C语言:函数递归,青蛙跳台阶图文详解
|
19天前
|
编译器 程序员 C语言
爱上C语言:什么C语言函数不太会,那你千万不要错过这篇(函数篇)
爱上C语言:什么C语言函数不太会,那你千万不要错过这篇(函数篇)
|
20天前
|
程序员 编译器 C语言
C语言库函数 — 错误信息报告函数
C语言库函数 — 错误信息报告函数
18 0
|
5天前
|
C语言
C语言:内存函数(memcpy memmove memset memcmp使用)
C语言:内存函数(memcpy memmove memset memcmp使用)
|
2天前
|
C语言
pta浙大版《C语言程序设计(第3版)》 习题6-4 使用函数输出指定范围内的Fibonacci数 (20分)
pta浙大版《C语言程序设计(第3版)》 习题6-4 使用函数输出指定范围内的Fibonacci数 (20分)
|
2天前
|
C语言
pta 浙大版《C语言程序设计(第3版)》题目集 习题6-6 使用函数输出一个整数的逆序数 (20分)
pta 浙大版《C语言程序设计(第3版)》题目集 习题6-6 使用函数输出一个整数的逆序数 (20分)
|
2天前
|
C语言
(浙大版《C语言程序设计(第3版)》 习题6-5 使用函数验证哥德巴赫猜想 (20分)
(浙大版《C语言程序设计(第3版)》 习题6-5 使用函数验证哥德巴赫猜想 (20分)
|
4天前
|
安全 C语言
【C语言】strcpy与strncpy函数的使用和模拟实现
【C语言】strcpy与strncpy函数的使用和模拟实现
5 0
|
4天前
|
C语言
【C语言】字符分类函数与字符转换函数
【C语言】字符分类函数与字符转换函数
9 1
|
4天前
|
程序员 编译器 C语言
C语言之函数与参数
C语言之函数与参数
5 0