37.9. C 语言功能

用户定义的函数可以用 C(或可以与 C 兼容的语言,例如 C)编写。这些函数被编译成可动态加载的对象(也称为共享库),并由服务器按需加载。动态加载功能是“ C 语言”功能与“内部”功能的区别-实际的编码约定在本质上是相同的。 (因此,标准内部函数库为用户定义的 C 函数提供了丰富的编码示例.)

当前,仅一个调用约定用于 C 函数(“版本 1”)。通过对该函数编写PG_FUNCTION_INFO_V1()宏调用来表明对该调用约定的支持,如下所示。

37 .9.1. 动态加载

首次在会话中调用特定可加载目标文件中的用户定义函数时,动态加载程序会将目标文件加载到内存中,以便可以调用该函数。因此,用户定义的 C 函数的CREATE FUNCTION必须为该函数指定两条信息:可装载目标文件的名称以及要在该目标文件中调用的特定函数的 C 名称(链接符号)。如果未显式指定 C 名称,则假定它与 SQL 函数名称相同。

以下算法用于根据CREATE FUNCTION命令中给出的名称来定位共享库文件:

  • 如果名称是绝对路径,则将加载给定文件。

  • 如果名称以字符串$libdir开头,则该部分将替换为 PostgreSQL 软件包库目录名,该目录名在构建时确定。

  • 如果名称不包含目录部分,则在配置变量dynamic_library_path指定的路径中搜索文件。

  • 否则(在路径中找不到该文件,或者该文件包含非绝对目录部分),动态加载程序将尝试采用给定的名称,这很可能会失败。 (依靠当前工作目录是不可靠的.)

如果此序列不起作用,则将平台特定的共享库文件 extensions(通常为.so)附加到给定名称,然后重试此序列。如果同样失败,则加载将失败。

建议相对于$libdir或通过动态库路径定位共享库。如果新安装位于其他位置,则可以简化版本升级。 $libdir代表的实际目录可以通过命令pg_config --pkglibdir找到。

PostgreSQL 服务器运行时所使用的用户 ID 必须能够遍历要加载的文件的路径。常见的错误是使文件或更高级别的目录对 postgres 用户不可读和/或不可执行。

无论如何,CREATE FUNCTION命令中给出的文件名实际上都记录在系统目录中,因此,如果需要再次加载该文件,则将应用相同的过程。

Note

PostgreSQL 不会自动编译 C 函数。在CREATE FUNCTION命令中引用对象文件之前,必须先对其进行编译。有关其他信息,请参见Section 37.9.5

为了确保不将动态加载的目标文件加载到不兼容的服务器中,PostgreSQL 检查文件是否包含带有适当内容的“魔术块”。这使服务器可以检测到明显的不兼容性,例如为其他主要 PostgreSQL 版本编译的代码。从 PostgreSQL 8.2 开始,魔术块是必需的。要包含魔术块,请在包含头文件fmgr.h之后,将其写入模块源文件中的一个(并且仅一个)中:

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

如果代码不需要针对 8.2 之前的 PostgreSQL 版本进行编译,则可以省略#ifdef测试。

首次使用后,动态加载的目标文件将保留在内存中。将来在同一会话中对该文件中的函数进行调用,只会导致符号表查找的少量开销。如果您需要强制重新加载目标文件,例如在重新编译目标文件后,请开始全新的会话。

(可选)动态加载的文件可以包含初始化和完成功能。如果文件包含名为_PG_init的函数,则在加载文件后将立即调用该函数。该函数不接收任何参数,应返回 void。如果文件包含名为_PG_fini的函数,则将在卸载文件前立即调用该函数。同样,该函数不接收任何参数,应返回 void。注意,_PG_fini仅在文件卸载期间被调用,而不在进程终止期间被调用。 (目前,卸载被禁用并且永远不会发生,但是将来可能会改变.)

37 .9.2. C 语言函数中的基本类型

要知道如何编写 C 语言函数,您需要知道 PostgreSQL 如何在内部表示基本数据类型以及如何将它们传递给函数。在内部,PostgreSQL 将基本类型视为“大量内存”。您在类型上定义的用户定义函数又定义了 PostgreSQL 在其上进行操作的方式。也就是说,PostgreSQL 将仅从磁盘存储和检索数据,并使用用户定义的函数 Importing,处理和输出数据。

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

  • 按值传递,固定长度

  • 通过引用,定长

  • 通过引用传递,可变长度

按值类型的长度只能为 1、2 或 4 个字节(如果计算机上的sizeof(Datum)为 8,则还可以为 8 个字节)。您应该谨慎定义类型,以使它们在所有体系结构上具有相同的大小(以字节为单位)。例如,long类型很危险,因为在某些机器上它是 4 字节,在其他机器上是 8 字节,而int类型在大多数 Unix 机器上是 4 字节。在 Unix 机器上int4类型的合理实现可能是:

/* 4-byte integer, passed by value */
typedef int int4;

(实际的 PostgreSQL C 代码将这种类型称为int32,因为 C 中的惯例是intXX表示* XX * * bits *.因此也请注意,C 类型int8的大小为 1 个字节.SQL 类型int8在以下情况下称为int64.C.另请参阅Table 37.1。)

另一方面,任何大小的固定长度类型都可以通过引用传递。例如,以下是 PostgreSQL 类型的示例实现:

/* 16-byte structure, passed by reference */
typedef struct
{
    double  x, y;
} Point;

将它们传入和传出 PostgreSQL 函数时,只能使用指向此类的指针。要返回这种类型的值,请使用palloc分配适当的内存量,填写分配的内存,然后返回指向它的指针。 (此外,如果您只想返回与数据类型相同的 Importing 参数之一相同的值,则可以跳过多余的palloc并仅将指针返回到 Importing 值.)

最后,所有可变长度类型也必须通过引用传递。所有可变长度类型必须以不透明的长度为 4 字节的长度开头,该长度将由SET_VARSIZE设置;切勿直接设置此字段!该类型中要存储的所有数据都必须紧随该长度字段之后位于内存中。长度字段包含结构的总长度,即,它包含长度字段本身的大小。

另一个要点是避免在数据类型值中保留任何未初始化的位;例如,请注意将结构中可能存在的所有对齐填充字节清零。否则,计划者可能会认为您的数据类型的逻辑等效常量不相等,从而导致效率低下(尽管并非正确)的计划。

Warning

从不修改通过引用 Importing 值的内容。如果这样做,可能会损坏磁盘上的数据,因为给出的指针可能直接指向磁盘缓冲区。 Section 37.10中解释了此规则的唯一 exception。

例如,我们可以如下定义类型text

typedef struct {
    int32 length;
    char data[FLEXIBLE_ARRAY_MEMBER];
} text;

[FLEXIBLE_ARRAY_MEMBER]表示此声明未指定数据部分的实际长度。

在处理可变长度类型时,我们必须小心分配正确的内存量并正确设置长度字段。例如,如果我们想在text结构中存储 40 个字节,则可以使用如下代码片段:

#include "postgres.h"
...
char buffer[40]; /* our source data */
...
text *destination = (text *) palloc(VARHDRSZ + 40);
SET_VARSIZE(destination, VARHDRSZ + 40);
memcpy(destination->data, buffer, 40);
...

VARHDRSZsizeof(int32)相同,但是使用宏VARHDRSZ来引用可变长度类型的开销的大小被认为是一种很好的样式。另外,必须使用SET_VARSIZE宏设置长度字段*,而不是通过简单分配。

Table 37.1指定在编写使用 PostgreSQL 内置类型的 C 语言函数时,哪种 C 类型对应于哪种 SQL 类型。 “定义于”列提供了要包含的头文件以获得类型定义。 (实际定义可能在列出的文件所包含的其他文件中.建议用户坚持使用已定义的界面.)请注意,在任何源文件中都应始终首先包含postgres.h,因为它声明了许多东西反正您将需要。

表 37.1 内置 SQL 类型的等效 C 类型

SQL TypeC TypeDefined In
abstimeAbsoluteTimeutils/nabstime.h
bigint ( int8 )int64postgres.h
booleanboolpostgres.h(也许是内置编译器)
boxBOX*utils/geo_decls.h
byteabytea*postgres.h
"char"char(compiler built-in)
characterBpChar*postgres.h
cidCommandIdpostgres.h
dateDateADTutils/date.h
smallint ( int2 )int16postgres.h
int2vectorint2vector*postgres.h
integer ( int4 )int32postgres.h
real ( float4 )float4*postgres.h
double precision ( float8 )float8*postgres.h
intervalInterval*datatype/timestamp.h
lsegLSEG*utils/geo_decls.h
nameNamepostgres.h
oidOidpostgres.h
oidvectoroidvector*postgres.h
pathPATH*utils/geo_decls.h
pointPOINT*utils/geo_decls.h
regprocregprocpostgres.h
reltimeRelativeTimeutils/nabstime.h
texttext*postgres.h
tidItemPointerstorage/itemptr.h
timeTimeADTutils/date.h
time with time zoneTimeTzADTutils/date.h
timestampTimestamp*datatype/timestamp.h
tintervalTimeIntervalutils/nabstime.h
varcharVarChar*postgres.h
xidTransactionIdpostgres.h

现在,我们已经介绍了基本类型的所有可能结构,我们可以显示一些实际函数的示例。

37 .9.3. 版本 1 呼叫约定

版本 1 调用约定依靠宏来抑制传递参数和结果的大多数复杂性。版本 1 函数的 C 声明始终为:

Datum funcname(PG_FUNCTION_ARGS)

另外,宏调用:

PG_FUNCTION_INFO_V1(funcname);

必须出现在同一源文件中。 (按惯例,它是在函数本身之前编写的.)internal语言函数不需要此宏调用,因为 PostgreSQL 假定所有内部函数都使用 version-1 约定。但是,动态加载的功能需要它。

在版本 1 函数中,每个实际参数都是使用与参数的数据类型相对应的PG_GETARG_xxx()宏获取的。 (在非严格函数中,需要使用PG_ARGISNULL()事先检查参数是否为空;请参见下文.)使用PG_RETURN_xxx()宏作为返回类型返回结果。 PG_GETARG_xxx()以要获取的函数参数的编号作为其自变量,计数从 0 开始。PG_RETURN_xxx()以要返回的实际值作为其自变量。

以下是一些使用版本 1 调用约定的示例:

#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

/* by value */

PG_FUNCTION_INFO_V1(add_one);

Datum
add_one(PG_FUNCTION_ARGS)
{
    int32   arg = PG_GETARG_INT32(0);

    PG_RETURN_INT32(arg + 1);
}

/* by reference, fixed length */

PG_FUNCTION_INFO_V1(add_one_float8);

Datum
add_one_float8(PG_FUNCTION_ARGS)
{
    /* The macros for FLOAT8 hide its pass-by-reference nature. */
    float8   arg = PG_GETARG_FLOAT8(0);

    PG_RETURN_FLOAT8(arg + 1.0);
}

PG_FUNCTION_INFO_V1(makepoint);

Datum
makepoint(PG_FUNCTION_ARGS)
{
    /* Here, the pass-by-reference nature of Point is not hidden. */
    Point     *pointx = PG_GETARG_POINT_P(0);
    Point     *pointy = PG_GETARG_POINT_P(1);
    Point     *new_point = (Point *) palloc(sizeof(Point));

    new_point->x = pointx->x;
    new_point->y = pointy->y;

    PG_RETURN_POINT_P(new_point);
}

/* by reference, variable length */

PG_FUNCTION_INFO_V1(copytext);

Datum
copytext(PG_FUNCTION_ARGS)
{
    text     *t = PG_GETARG_TEXT_PP(0);

    /*
     * VARSIZE_ANY_EXHDR is the size of the struct in bytes, minus the
     * VARHDRSZ or VARHDRSZ_SHORT of its header.  Construct the copy with a
     * full-length header.
     */
    text     *new_t = (text *) palloc(VARSIZE_ANY_EXHDR(t) + VARHDRSZ);
    SET_VARSIZE(new_t, VARSIZE_ANY_EXHDR(t) + VARHDRSZ);

    /*
     * VARDATA is a pointer to the data region of the new struct.  The source
     * could be a short datum, so retrieve its data through VARDATA_ANY.
     */
    memcpy((void *) VARDATA(new_t), /* destination */
           (void *) VARDATA_ANY(t), /* source */
           VARSIZE_ANY_EXHDR(t));   /* how many bytes */
    PG_RETURN_TEXT_P(new_t);
}

PG_FUNCTION_INFO_V1(concat_text);

Datum
concat_text(PG_FUNCTION_ARGS)
{
    text  *arg1 = PG_GETARG_TEXT_PP(0);
    text  *arg2 = PG_GETARG_TEXT_PP(1);
    int32 arg1_size = VARSIZE_ANY_EXHDR(arg1);
    int32 arg2_size = VARSIZE_ANY_EXHDR(arg2);
    int32 new_text_size = arg1_size + arg2_size + VARHDRSZ;
    text *new_text = (text *) palloc(new_text_size);

    SET_VARSIZE(new_text, new_text_size);
    memcpy(VARDATA(new_text), VARDATA_ANY(arg1), arg1_size);
    memcpy(VARDATA(new_text) + arg1_size, VARDATA_ANY(arg2), arg2_size);
    PG_RETURN_TEXT_P(new_text);
}

假设上面的代码已经在文件funcs.c中准备好并编译为共享对象,我们可以使用以下命令为 PostgreSQL 定义函数:

CREATE FUNCTION add_one(integer) RETURNS integer
     AS 'DIRECTORY/funcs', 'add_one'
     LANGUAGE C STRICT;

-- note overloading of SQL function name "add_one"
CREATE FUNCTION add_one(double precision) RETURNS double precision
     AS 'DIRECTORY/funcs', 'add_one_float8'
     LANGUAGE C STRICT;

CREATE FUNCTION makepoint(point, point) RETURNS point
     AS 'DIRECTORY/funcs', 'makepoint'
     LANGUAGE C STRICT;

CREATE FUNCTION copytext(text) RETURNS text
     AS 'DIRECTORY/funcs', 'copytext'
     LANGUAGE C STRICT;

CREATE FUNCTION concat_text(text, text) RETURNS text
     AS 'DIRECTORY/funcs', 'concat_text'
     LANGUAGE C STRICT;

在这里,* DIRECTORY 表示共享库文件的目录(例如 PostgreSQL 教程目录,其中包含本节中使用的示例代码)。 (更好的样式是在将 DIRECTORY *添加到搜索路径之后,在AS子句中仅使用'funcs'.在任何情况下,我们都可以省略共享库的系统特定 extensions,通常为.so.)

请注意,我们已将函数指定为“ strict”,这意味着如果任何 Importing 值为 null,则系统应自动假定结果为 null。通过这样做,我们避免了必须检查功能代码中的空 Importing。没有这个,我们将不得不使用PG_ARGISNULL()显式检查 null 值。

PG_ARGISNULL(n)允许函数测试每个 Importing 是否为空。 (当然,只有在未声明为“ strict”的函数中才需要执行此操作.)与PG_GETARG_xxx()宏一样,Importing 自变量从零开始计数。请注意,除非已验证参数不为 null,否则应避免执行PG_GETARG_xxx()。要返回空结果,请执行PG_RETURN_NULL();这适用于严格和非严格功能。

乍一看,与使用简单的C调用约定相比,版本 1 编码约定似乎毫无意义。但是,它们确实使我们能够处理NULL个有效的参数/返回值以及“已烘烤”(压缩或脱机)值。

版本 1 接口提供的其他选项是PG_GETARG_xxx()宏的两个变体。其中第一个PG_GETARG_xxx_COPY()保证返回安全写入的指定参数的副本。 (普通宏有时会返回一个指针,该指针指向物理存储在表中的值,该值不得写入.使用PG_GETARG_xxx_COPY()宏可保证可写结果.)第二个变体由PG_GETARG_xxx_SLICE()宏组成,该宏带有三个参数。第一个是函数参数的编号(如上所述)。第二和第三个是要返回的段的偏移量和长度。偏移量从零开始计数,负长度要求返回值的其余部分。这些宏在存储类型为“外部”的情况下,可以更有效地访问大值部分。 (可以使用ALTER TABLE tablename ALTER COLUMN colname SET STORAGE storagetype来指定列的存储类型.* storagetype *是plainexternalextendedmain之一.)

最后,版本 1 函数调用约定使返回设置结果(Section 37.9.8)并实现触发器函数(Chapter 38)和过程语言调用处理程序(Chapter 55)成为可能。有关更多详细信息,请参见源代码发行版中的src/backend/utils/fmgr/README

37 .9.4. 编写代码

在转向更高级的主题之前,我们应该讨论一些 PostgreSQL C 语言函数的编码规则。尽管可以将用 C 以外的语言编写的函数加载到 PostgreSQL 中,但这通常很困难(如果可能的话),因为其他语言(例如 C,FORTRAN 或 Pascal)通常不遵循与 C.也就是说,其他语言不会以相同的方式在函数之间传递参数并返回值。因此,我们假设您的 C 语言函数实际上是用 C 编写的。

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

  • 使用pg_config --includedir-server 来查找 PostgreSQL 服务器头文件在您的系统(或将在其上运行用户的系统)上的安装位置。

  • 编译和链接您的代码,以便可以将其动态加载到 PostgreSQL 中,始终需要特殊标志。有关如何针对特定 os 执行此操作的详细说明,请参见Section 37.9.5

  • 记住要为共享库定义一个“魔术块”,如Section 37.9.1中所述。

  • 分配内存时,请使用 PostgreSQL 函数palloc pfree 而不是相应的 C 库函数mallocfreepalloc分配的内存将在每个事务结束时自动释放,以防止内存泄漏。

  • 始终使用memset将结构的字节清零(或首先使用palloc0分配它们)。即使您分配给结构的每个字段,也可能会有包含垃圾值的对齐填充(结构中的孔)。没有这个,就很难支持哈希索引或哈希联接,因为您必须只挑选数据结构的重要位来计算哈希。计划者有时还依赖于通过按位相等对常量进行比较,因此,如果逻辑等效值不按位相等,则可能会得到不良的计划结果。

  • 大多数内部 PostgreSQL 类型在postgres.h中声明,而函数 Management 器接口(PG_FUNCTION_ARGS等)在fmgr.h中声明,因此您至少需要包括这两个文件。出于可移植性原因,最好在任何其他系统或用户头文件之前包含postgres.h * first *。包括postgres.h也将为您包括elog.hpalloc.h

  • 在目标文件中定义的符号名称不得相互冲突,也不得与 PostgreSQL 服务器可执行文件中定义的符号冲突。如果收到错误消息,则必须重命名函数或变量。

37 .9.5. 编译和链接动态加载的函数

在能够使用用 C 编写的 PostgreSQL 扩展功能之前,必须以特殊方式编译和链接它们,以生成可以由服务器动态加载的文件。确切地说,需要创建一个共享库

有关本节未包含的其他信息,您应该阅读 os 的文档,尤其是 C 编译器cc和链接编辑器ld的手册页。此外,PostgreSQL 源代码在contrib目录中包含几个工作示例。但是,如果您依赖这些示例,则将使模块取决于 PostgreSQL 源代码的可用性。

创建共享库通常类似于链接可执行文件:首先将源文件编译为目标文件,然后将目标文件链接在一起。需要将目标文件创建为“位置无关代码”(PIC),这从概念上讲意味着当它们由可执行文件加载时,可以将它们放置在内存中的任意位置。 (用于可执行文件的目标文件通常不会以这种方式进行编译.)链接共享库的命令包含特殊标志,以区别于它与链接可执行文件的链接(至少在理论上-在某些系统上这种做法难看得多)。

在以下示例中,我们假设您的源代码在文件foo.c中,并且我们将创建一个共享库foo.so。除非另有说明,否则中间目标文件将称为foo.o。一个共享库可以包含多个目标文件,但是我们在这里只使用一个。

  • FreeBSD

    • 创建 PIC 的编译器标志为-fPIC。要创建共享库,编译器标志为-shared
gcc -fPIC -c foo.c
gcc -shared -o foo.so foo.o

这适用于 FreeBSD 3.0 版。

  • HP-UX

    • 用于创建 PIC 的系统编译器的编译器标志为+z。使用 GCC 时为-fPIC。共享库的链接器标志是-b。所以:
cc +z -c foo.c

or:

gcc -fPIC -c foo.c

and then:

ld -b -o foo.sl foo.o

与大多数其他系统不同,HP-UX 对共享库使用 extensions.sl

  • 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 的编译器标志为-fPICld -Bshareable用于链接共享库。
gcc -fPIC -c foo.c
ld -Bshareable -o foo.so foo.o
  • Solaris

    • 用于创建 PIC 的编译器标志在 Sun 编译器中为-KPIC,在 GCC 中为-fPIC。要链接共享库,编译器选项在编译器中为-G,在 GCC 中为-shared
cc -KPIC -c foo.c
cc -G -o foo.so foo.o

or

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

Tip

如果这对您来说太复杂,则应考虑使用GNU Libtool,这会将平台差异隐藏在统一的界面后面。

然后可以将生成的共享库文件加载到 PostgreSQL 中。为CREATE FUNCTION命令指定文件名时,必须给它指定共享库文件的名称,而不是中间目标文件的名称。请注意,可以从CREATE FUNCTION命令中省略系统的标准共享库 extensions(通常是.so.sl),并且通常应省略它以实现最佳的可移植性。

返回到Section 37.9.1,以了解服务器希望在何处找到共享库文件。

37 .9.6. 复合类型的参数

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

假设我们要编写一个函数来回答查询:

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() */

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PG_FUNCTION_INFO_V1(c_overpaid);

Datum
c_overpaid(PG_FUNCTION_ARGS)
{
    HeapTupleHeader  t = PG_GETARG_HEAPTUPLEHEADER(0);
    int32            limit = PG_GETARG_INT32(1);
    bool isnull;
    Datum salary;

    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);
}

GetAttributeByName是 PostgreSQL 系统函数,它返回指定行之外的属性。它具有三个参数:传递给函数的HeapTupleHeader类型的参数,所需属性的名称以及一个告诉该属性是否为 null 的返回参数。 GetAttributeByName返回一个Datum值,您可以使用适当的DatumGetXXX()宏将其转换为正确的数据类型。注意,如果设置了 null 标志,则返回值是没有意义的;尝试对结果进行任何操作之前,请务必先检查 null 标志。

还有GetAttributeByNum,它通过列号而不是名称来选择目标属性。

以下命令在 SQL 中声明函数c_overpaid

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

注意,我们使用了STRICT,因此我们不必检查 Importing 参数是否为 NULL。

37 .9.7. 返回行(复合类型)

要从 C 语言函数返回行或复合类型的值,可以使用特殊的 API,该 API 提供宏和函数来隐藏构建复合数据类型的大多数复杂性。要使用此 API,源文件必须包括:

#include "funcapi.h"

有两种方法可以构建复合数据值(以下称为“Tuples”):您可以从 Datum 值数组或可以传递给 Tuples 列的 Importing 转换函数的 C 字符串数组中构建数据数据类型。无论哪种情况,您都首先需要获取或构造一个 Tuples 结构的TupleDescDescriptors。使用基准时,将TupleDesc传递给BlessTupleDesc,然后为每一行调用heap_form_tuple。使用 C 字符串时,请将TupleDesc传递给TupleDescGetAttInMetadata,然后为每一行调用BuildTupleFromCStrings。在函数返回一组 Tuples 的情况下,设置步骤可以在函数的第一次调用期间全部完成一次。

几个帮助程序功能可用于设置所需的TupleDesc。在大多数返回复合值的函数中,建议这样做的方法是调用:

TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,
                                   Oid *resultTypeId,
                                   TupleDesc *resultTupleDesc)

将相同的fcinfo结构传递给调用函数本身。 (这当然要求您使用版本 1 的调用约定.)resultTypeId可以指定为NULL或作为局部变量的地址来接收函数的结果类型 OID。 resultTupleDesc应该是本地TupleDesc变量的地址。检查结果是否为TYPEFUNC_COMPOSITE;如果是这样,则resultTupleDesc已填充了所需的TupleDesc。 (如果不是,则可以按照“在不能接受类型记录的上下文中调用函数返回记录的方式”报告错误.)

Tip

get_call_result_type可以解析多态函数结果的实际类型;因此,它在返回标量多态结果的函数中很有用,不仅是返回合成的函数。 resultTypeId输出主要用于返回多态标量的函数。

Note

get_call_result_type具有同级get_expr_result_type,可用于解析由表达式树表示的函数调用的预期输出类型。当尝试从函数本身之外确定结果类型时,可以使用此方法。还有get_func_result_type,当仅函数的 OID 可用时可以使用。但是,这些函数无法处理声明为返回record的函数,并且get_func_result_type无法解析多态类型,因此应优先使用get_call_result_type

现在不建议使用的较旧函数来获取TupleDesc s 是:

TupleDesc RelationNameGetTupleDesc(const char *relname)

为命名关系的行类型获取TupleDesc,并且:

TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)

以获得基于 OID 类型的TupleDesc。这可用于为基本或复合类型获取TupleDesc。对于返回record的函数,它将不起作用,并且它无法解析多态类型。

拥有TupleDesc后,请致电:

TupleDesc BlessTupleDesc(TupleDesc tupdesc)

如果您打算使用基准,或者:

AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)

如果您打算使用 C 字符串。如果要编写函数返回集,则可以将这些函数的结果保存在FuncCallContext结构中-分别使用tuple_descattinmeta字段。

使用基准时,请使用:

HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, bool *isnull)

以 Datum 形式构建HeapTuple给定的用户数据。

使用 C 字符串时,请使用:

HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)

以 C 字符串形式构建一个HeapTuple给定的用户数据。 * values 是 C 字符串的数组,返回行的每个属性一个。每个 C 字符串应采用属性数据类型的 Importing 函数期望的形式。为了返回其中一个属性的空值, values *数组中的相应指针应设置为NULL。您需要为返回的每一行再次调用此函数。

构建 Tuples 以从函数返回后,必须将其转换为Datum。用:

HeapTupleGetDatum(HeapTuple tuple)

HeapTuple转换为有效基准。如果您只想返回一行,则可以直接返回此Datum,也可以将其用作 set-returning 函数中的当前返回值。

下一部分将显示一个示例。

37 .9.8. 返回集

C 语言函数有两个用于返回集合(多行)的选项。在一种称为* ValuePerCall 模式的方法中,重复调用一个 set-returning 函数(每次都传递相同的参数),并且每次调用都返回一个新行,直到没有更多行要返回并通过返回 NULL 发出 signal 为止。 。因此,集返回功能(SRF)必须在调用之间保存足够的状态,以记住它在做什么,并在每次调用时返回正确的下一项。在另一种称为 Materialize *模式的方法中,SRF 填充并返回一个包含整个结果的 Tuples 对象;那么整个结果只有一个呼叫发生,并且不需要呼叫间状态。

使用 ValuePerCall 模式时,重要的是要记住,不能保证查询可以完全运行。也就是说,由于诸如LIMIT之类的选项,执行程序可能会在获取所有行之前停止对集合返回函数的调用。这意味着在上一次调用中执行清理活动是不安全的,因为这可能永远不会发生。对于需要访问外部资源的功能(例如文件 Descriptors),建议使用 Materialize 模式。

本节的其余部分介绍了一组使用 ValuePerCall 模式的 SRF 常用(但不需要使用)的帮助程序宏。有关实现模式的其他详细信息,请参见src/backend/utils/fmgr/README。同样,PostgreSQL 源代码发布中的contrib模块包含使用 ValuePerCall 和 Materialize 模式的 SRF 的许多示例。

要使用此处描述的 ValuePerCall 支持宏,请包含funcapi.h。这些宏使用结构FuncCallContext进行工作,该结构包含需要在调用之间保存的状态。在调用 SRF 中,fcinfo->flinfo->fn_extra用于在调用之间保持指向FuncCallContext的指针。宏在首次使用时会自动填充该字段,并期望在后续使用中找到相同的指针。

typedef struct FuncCallContext
{
    /*
     * Number of times we've been called before
     *
     * call_cntr is initialized to 0 for you by SRF_FIRSTCALL_INIT(), and
     * incremented for you every time SRF_RETURN_NEXT() is called.
     */
    uint64 call_cntr;

    /*
     * OPTIONAL maximum number of calls
     *
     * max_calls is here for convenience only and setting it is optional.
     * If not set, you must provide alternative means to know when the
     * function is done.
     */
    uint64 max_calls;

    /*
     * OPTIONAL pointer to result slot
     *
     * This is obsolete and only present for backward compatibility, viz,
     * user-defined SRFs that use the deprecated TupleDescGetSlot().
     */
    TupleTableSlot *slot;

    /*
     * OPTIONAL pointer to miscellaneous user-provided context information
     *
     * user_fctx is for use as a pointer to your own data to retain
     * arbitrary context information between calls of your function.
     */
    void *user_fctx;

    /*
     * OPTIONAL pointer to struct containing attribute type input metadata
     *
     * attinmeta is for use when returning tuples (i.e., composite data types)
     * and is not used when returning base data types. It is only needed
     * if you intend to use BuildTupleFromCStrings() to create the return
     * tuple.
     */
    AttInMetadata *attinmeta;

    /*
     * memory context used for structures that must live for multiple calls
     *
     * multi_call_memory_ctx is set by SRF_FIRSTCALL_INIT() for you, and used
     * by SRF_RETURN_DONE() for cleanup. It is the most appropriate memory
     * context for any memory that is to be reused across multiple calls
     * of the SRF.
     */
    MemoryContext multi_call_memory_ctx;

    /*
     * OPTIONAL pointer to struct containing tuple description
     *
     * tuple_desc is for use when returning tuples (i.e., composite data types)
     * and is only needed if you are going to build the tuples with
     * heap_form_tuple() rather than with BuildTupleFromCStrings().  Note that
     * the TupleDesc pointer stored here should usually have been run through
     * BlessTupleDesc() first.
     */
    TupleDesc tuple_desc;

} FuncCallContext;

使用此基础结构的 SRF 将使用的宏是:

SRF_IS_FIRSTCALL()

使用它来确定您的函数是第一次还是随后被调用。在第一个电话(仅限)上,请致电:

SRF_FIRSTCALL_INIT()

初始化FuncCallContext。在每个函数调用(包括第一个函数调用)上,调用:

SRF_PERCALL_SETUP()

设置使用FuncCallContext

如果您的函数有数据要在当前调用中返回,请使用:

SRF_RETURN_NEXT(funcctx, result)

将其返回给呼叫者。 (result必须是Datum类型,可以是单个值,也可以是如上所述准备的 Tuples.)最后,当函数完成返回数据时,请使用:

SRF_RETURN_DONE(funcctx)

清理并结束 SRF。

调用 SRF 时当前的内存上下文是一个临时上下文,将在两次调用之间清除。这意味着您不需要在使用palloc分配的所有内容上调用pfree;它仍然会消失。但是,如果您想分配任何数据结构以在调用之间使用,则需要将它们放在其他位置。 multi_call_memory_ctx引用的内存上下文是需要保留直到 SRF 完成运行的任何数据的合适位置。在大多数情况下,这意味着您应在进行首次呼叫设置时切换到multi_call_memory_ctx。使用funcctx->user_fctx保留指向任何此类交叉调用数据结构的指针。 (在查询结束时,您在multi_call_memory_ctx中分配的数据将自动消失,因此也无需手动释放该数据.)

Warning

虽然函数的实际参数在调用之间保持不变,但是如果您在瞬态上下文中删除参数值(通常由PG_GETARG_xxx宏透明地完成),则将在每个循环中释放删除的副本。因此,如果您在user_fctx中保留对这些值的引用,则必须在解酒后将它们复制到multi_call_memory_ctx中,或者确保仅在该上下文中对这些值进行解酒。

完整的伪代码示例如下所示:

Datum
my_set_returning_function(PG_FUNCTION_ARGS)
{
    FuncCallContext  *funcctx;
    Datum             result;
    further declarations as needed

    if (SRF_IS_FIRSTCALL())
    {
        MemoryContext oldcontext;

        funcctx = SRF_FIRSTCALL_INIT();
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
        /* One-time setup code appears here: */
        user code
        if returning composite
            build TupleDesc, and perhaps AttInMetadata
        endif returning composite
        user code
        MemoryContextSwitchTo(oldcontext);
    }

    /* Each-time setup code appears here: */
    user code
    funcctx = SRF_PERCALL_SETUP();
    user code

    /* this is just one way we might test whether we are done: */
    if (funcctx->call_cntr < funcctx->max_calls)
    {
        /* Here we want to return another item: */
        user code
        obtain result Datum
        SRF_RETURN_NEXT(funcctx, result);
    }
    else
    {
        /* Here we are done returning items, so just report that fact. */
        /* (Resist the temptation to put cleanup code here.) */
        SRF_RETURN_DONE(funcctx);
    }
}

一个简单的 SRF 返回复合类型的完整示例如下:

PG_FUNCTION_INFO_V1(retcomposite);

Datum
retcomposite(PG_FUNCTION_ARGS)
{
    FuncCallContext     *funcctx;
    int                  call_cntr;
    int                  max_calls;
    TupleDesc            tupdesc;
    AttInMetadata       *attinmeta;

    /* stuff done only on the first call of the function */
    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);

        /* total number of tuples to be returned */
        funcctx->max_calls = PG_GETARG_UINT32(0);

        /* Build a tuple descriptor for our result type */
        if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                     errmsg("function returning record called in context "
                            "that cannot accept type record")));

        /*
         * generate attribute metadata needed later to produce tuples from raw
         * C strings
         */
        attinmeta = TupleDescGetAttInMetadata(tupdesc);
        funcctx->attinmeta = attinmeta;

        MemoryContextSwitchTo(oldcontext);
    }

    /* stuff done on every call of the function */
    funcctx = SRF_PERCALL_SETUP();

    call_cntr = funcctx->call_cntr;
    max_calls = funcctx->max_calls;
    attinmeta = funcctx->attinmeta;

    if (call_cntr < max_calls)    /* do when there is more left to send */
    {
        char       **values;
        HeapTuple    tuple;
        Datum        result;

        /*
         * Prepare a values array for building the returned tuple.
         * This should be an array of C strings which will
         * be processed later by the type input functions.
         */
        values = (char **) palloc(3 * sizeof(char *));
        values[0] = (char *) palloc(16 * sizeof(char));
        values[1] = (char *) palloc(16 * sizeof(char));
        values[2] = (char *) palloc(16 * sizeof(char));

        snprintf(values[0], 16, "%d", 1 * PG_GETARG_INT32(1));
        snprintf(values[1], 16, "%d", 2 * PG_GETARG_INT32(1));
        snprintf(values[2], 16, "%d", 3 * PG_GETARG_INT32(1));

        /* build a tuple */
        tuple = BuildTupleFromCStrings(attinmeta, values);

        /* make the tuple into a datum */
        result = HeapTupleGetDatum(tuple);

        /* clean up (this is not really necessary) */
        pfree(values[0]);
        pfree(values[1]);
        pfree(values[2]);
        pfree(values);

        SRF_RETURN_NEXT(funcctx, result);
    }
    else    /* do when there is no more left */
    {
        SRF_RETURN_DONE(funcctx);
    }
}

在 SQL 中声明此函数的一种方法是:

CREATE TYPE __retcomposite AS (f1 integer, f2 integer, f3 integer);

CREATE OR REPLACE FUNCTION retcomposite(integer, integer)
    RETURNS SETOF __retcomposite
    AS 'filename', 'retcomposite'
    LANGUAGE C IMMUTABLE STRICT;

另一种方法是使用 OUT 参数:

CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer,
    OUT f1 integer, OUT f2 integer, OUT f3 integer)
    RETURNS SETOF record
    AS 'filename', 'retcomposite'
    LANGUAGE C IMMUTABLE STRICT;

请注意,在此方法中,函数的输出类型在形式上为匿名record类型。

37 .9.9. 多态参数和返回类型

可以声明 C 语言函数以接受并返回多态类型anyelementanyarrayanynonarrayanyenumanyrange。有关多态函数的更详细说明,请参见Section 37.2.5。当函数参数或返回类型定义为多态类型时,函数作者无法事先知道将使用哪种数据类型或需要返回哪种数据类型。 fmgr.h中提供了两个例程,以允许版本 1 C 函数发现其参数的实际数据类型以及预期返回的类型。这些例程称为get_fn_expr_rettype(FmgrInfo *flinfo)get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)。它们返回结果或参数类型 OID,或者如果信息不可用则返回InvalidOid。通常通过fcinfo->flinfo访问结构flinfo。参数argnum从零开始。 get_call_result_type也可以替代get_fn_expr_rettype。还有get_fn_expr_variadic,可用于查找可变参数是否已合并到数组中。这对于VARIADIC "any"函数主要有用,因为对于采用普通数组类型的可变参数函数,总是会发生这种合并。

例如,假设我们要编写一个函数以接受任何类型的单个元素,并返回该类型的一维数组:

PG_FUNCTION_INFO_V1(make_array);
Datum
make_array(PG_FUNCTION_ARGS)
{
    ArrayType  *result;
    Oid         element_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
    Datum       element;
    bool        isnull;
    int16       typlen;
    bool        typbyval;
    char        typalign;
    int         ndims;
    int         dims[MAXDIM];
    int         lbs[MAXDIM];

    if (!OidIsValid(element_type))
        elog(ERROR, "could not determine data type of input");

    /* get the provided element, being careful in case it's NULL */
    isnull = PG_ARGISNULL(0);
    if (isnull)
        element = (Datum) 0;
    else
        element = PG_GETARG_DATUM(0);

    /* we have one dimension */
    ndims = 1;
    /* and one element */
    dims[0] = 1;
    /* and lower bound is 1 */
    lbs[0] = 1;

    /* get required info about the element type */
    get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign);

    /* now build the array */
    result = construct_md_array(&element, &isnull, ndims, dims, lbs,
                                element_type, typlen, typbyval, typalign);

    PG_RETURN_ARRAYTYPE_P(result);
}

以下命令在 SQL 中声明函数make_array

CREATE FUNCTION make_array(anyelement) RETURNS anyarray
    AS 'DIRECTORY/funcs', 'make_array'
    LANGUAGE C IMMUTABLE;

多态性有一个变体,仅可用于 C 语言函数:可以声明它们采用"any"类型的参数。 (请注意,此类型名称也必须用双引号括起来,因为它也是 SQL 保留字.)它的工作方式与anyelement相同,只是它不会将不同的"any"参数约束为相同的类型,也不能帮助确定函数的结果类型。 C 语言函数还可以将其最终参数声明为VARIADIC "any"。这将匹配任何类型(不一定是相同类型)的一个或多个实际参数。这些参数不会像普通的可变参数函数那样被收集到数组中。它们只会分别传递给函数。使用此功能时,必须使用PG_NARGS()宏和上述方法确定实际参数的数量及其类型。同样,此类函数的用户可能希望在函数调用中使用VARIADIC关键字,并期望函数会将数组元素视为单独的参数。在使用get_fn_expr_variadic检测实际参数已标记为VARIADIC之后,如果需要,函数本身必须实现该行为。

37 .9.10. 转换功能

在计划期间,可以根据特定于功能的属性简化某些功能调用。例如,int4mul(n, 1)可以简化为n。要定义此类特定于函数的优化,请编写一个* transform function *并将其 OID 放在主要函数pg_proc条目的protransform字段中。转换函数必须具有 SQL 签名protransform(internal) RETURNS internal。参数FuncExpr *实际上是一个虚拟节点,代表对主函数的调用。如果转换函数对表达式树的研究证明简化的表达式树可以代替由此表示的所有可能的具体调用,则构建并返回该简化的表达式。否则,返回NULL指针( SQL 空值)。

我们不保证在转换函数可以简化的情况下 PostgreSQL 永远不会调用主函数。确保简化表达式与实际调用主函数之间的严格对等。

当前,由于安全方面的考虑,该功能尚未在 SQL 级别上向用户公开,因此仅用于优化内置功能是实用的。

37 .9.11. 共享内存和 LWLock

加载项可以在服务器启动时保留 LWLock 和共享内存的分配。必须通过在shared_preload_libraries 中指定外接程序的共享库来预加载该外接程序。共享内存通过以下方式保留:

void RequestAddinShmemSpace(int size)

来自您的_PG_init函数。

通过调用以下命令来保留 LWLock:

void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)

来自_PG_init。这将确保以tranche_name的名称提供num_lwlocks LWLocks 数组。使用GetNamedLWLockTranche获取指向此数组的指针。

为了避免可能出现的竞争情况,每个后端在连接并初始化其共享内存分配时都应使用 LWLock AddinShmemInitLock,如下所示:

static mystruct *ptr = NULL;

if (!ptr)
{
        bool    found;

        LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
        ptr = ShmemInitStruct("my struct name", size, &found);
        if (!found)
        {
                initialize contents of shmem area;
                acquire any requested LWLocks using:
                ptr->locks = GetNamedLWLockTranche("my tranche name");
        }
        LWLockRelease(AddinShmemInitLock);
}

37 .9.12. 使用 C 进行扩展

尽管 PostgreSQL 后端是用 C 编写的,但是如果遵循以下准则,则可以用 C 编写 extensions:

  • 后端访问的所有功能都必须为后端提供一个 C 接口。然后,这些 C 函数可以调用 C 函数。例如,后端访问的功能需要extern C链接。对于在后端和 C 代码之间作为指针传递的任何函数,这也是必需的。

  • 使用适当的释放方法释放内存。例如,大多数后端内存是使用palloc()分配的,因此请使用pfree()释放它。在这种情况下使用 C delete将失败。

  • 防止将异常传播到 C 代码中(使用所有extern C函数顶层的 catch-all 块)。即使 C 代码没有显式抛出任何异常,这也是必要的,因为内存不足之类的事件仍会引发异常。必须捕获任何异常,并将适当的错误传递回 C 接口。如果可能,使用-fno-exceptions编译 C 以完全消除异常;在这种情况下,您必须检查 C 代码中的错误,例如检查new()返回的 NULL。

  • 如果从 C 代码调用后端函数,请确保 C 调用堆栈仅包含普通的旧数据结构(POD)。这是必需的,因为后端错误会生成一个遥远的longjmp(),而该longjmp()无法正确展开具有非 POD 对象的 C 调用堆栈。

总之,最好将 C 代码放在与后端接口的extern C函数的墙后面,并避免异常,内存和调用堆栈泄漏。