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

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
云原生数据库 PolarDB MySQL 版,通用型 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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
30天前
|
SQL Oracle 关系型数据库
SQL优化-使用联合索引和函数索引
在一次例行巡检中,发现一条使用 `to_char` 函数将日期转换为字符串的 SQL 语句 CPU 利用率很高。为了优化该语句,首先分析了 where 条件中各列的选择性,并创建了不同类型的索引,包括普通索引、函数索引和虚拟列索引。通过对比不同索引的执行计划,最终确定了使用复合索引(包含函数表达式)能够显著降低查询成本,提高执行效率。
|
1月前
|
SQL 数据库 数据库管理
数据库SQL函数应用技巧与方法
在数据库管理中,SQL函数是处理和分析数据的强大工具
|
1月前
|
SQL 数据库 索引
SQL中COUNT函数结合条件使用的技巧与方法
在SQL查询中,COUNT函数是一个非常常用的聚合函数,用于计算表中满足特定条件的记录数
|
1月前
|
SQL 关系型数据库 MySQL
SQL日期函数
SQL日期函数
|
1月前
|
编译器 C语言
初识C语言:扩展世界观,选择语句之行
初识C语言:扩展世界观,选择语句之行
|
2月前
|
SQL 关系型数据库 C语言
PostgreSQL SQL扩展 ---- C语言函数(三)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
33 3
|
6天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
21 6
|
26天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10
|
19天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。