# 38.10.C语言函数
用户定义的函数可以用C语言(或与C兼容的语言,如C++)编写。这些函数被编译成可动态加载的对象(也称为共享库),并由服务器按需加载。动态加载特性是“C语言”函数与“内部”函数的区别——两者的实际编码约定基本相同。(因此,标准内部函数库为用户定义的C函数提供了丰富的编码示例。)
目前只有一种调用约定用于C函数(“版本1”)。通过编写一个PG_功能_信息_V1()
宏调用函数,如下所示。
# 38.10.1.动载荷
在会话中首次调用特定可加载对象文件中的用户定义函数时,动态加载程序会将该对象文件加载到内存中,以便调用该函数。这个创建函数
因此,对于用户定义的C函数,必须为该函数指定两条信息:可加载对象文件的名称,以及要在该对象文件中调用的特定函数的C名称(链接符号)。如果未明确指定C名称,则假定它与SQL函数名相同。
以下算法用于根据创建函数
命令:
如果名称是绝对路径,则加载给定文件。
如果名称不包含目录部分,则会在配置变量指定的路径中搜索该文件动态_图书馆_路径.
否则(在路径中找不到该文件,或者它包含非绝对目录部分),动态加载程序将尝试按给定的名称命名,这很可能会失败。(依赖当前工作目录是不可靠的。)
如果此序列不起作用,则特定于平台的共享库文件扩展名(通常为
所以
)将附加到给定的名称,然后重试此序列。如果同样失败,负载也会失败。建议定位共享库,相对于
$libdir
或者通过动态库路径。如果新安装在不同的位置,这将简化版本升级。实际的目录$libdir
可以通过命令找到pg_配置——pkglibdir
.PostgreSQL server运行时使用的用户ID必须能够遍历要加载的文件的路径。使文件或更高级别的目录不可读和/或不可由postgres用户执行是一个常见的错误。
在任何情况下
创建函数
命令按字面意思记录在系统目录中,因此,如果需要再次加载文件,则应用相同的过程。
# 笔记
PostgreSQL不会自动编译C函数。必须先编译对象文件,然后才能在创建函数
命令看见第38.10.5节了解更多信息。
为了确保动态加载的对象文件不会加载到不兼容的服务器中,PostgreSQL会检查该文件是否包含具有适当内容的“魔术块”。这允许服务器检测明显的不兼容,例如为不同的PostgreSQL主版本编译的代码。要包含一个魔术块,在包含了头文件之后,将其写入一个(且仅一个)模块源文件中fmgr。H
:
PG_MODULE_MAGIC;
首次使用后,动态加载的对象文件将保留在内存中。同一会话中对该文件中函数的未来调用只会产生符号表查找的小开销。如果需要强制重新加载对象文件(例如在重新编译后),请开始新的会话。
或者,动态加载的文件可以包含初始化和终结功能。如果文件包含名为_PG_init
,该函数将在加载文件后立即调用。该函数不接收任何参数,应返回void。如果文件包含名为_普古菲尼
,该函数将在卸载文件之前立即调用。同样,函数不接收任何参数,应该返回void。注意_普古菲尼
将仅在卸载文件期间调用,而不是在进程终止期间调用。(目前,卸载已被禁用,并且永远不会发生,但将来可能会发生变化。)
# 38.10.2.C语言函数中的基类型
要知道如何编写C语言函数,您需要知道PostgreSQL如何在内部表示基本数据类型,以及它们如何在函数之间传递。在内部,PostgreSQL将基类型视为“内存块”。在类型上定义的用户定义函数反过来定义了PostgreSQL对其进行操作的方式。也就是说,PostgreSQL只从磁盘存储和检索数据,并使用用户定义的函数输入、处理和输出数据。
基本类型可以有三种内部格式之一:
按值传递,固定长度
参考传递,固定长度
按引用传递,可变长度
按值类型的长度只能是1、2或4字节(如果需要,也可以是8字节)
sizeof(基准面)
在你的机器上是8)。您应该小心地定义类型,以便它们在所有体系结构上的大小(以字节为单位)相同。例如长的
类型是危险的,因为它在某些机器上是4字节,在其他机器上是8字节,而智力
在大多数Unix机器上,类型为4字节。合理执行int4
Unix计算机上的类型可能是:
/* 4-byte integer, passed by value */
typedef int int4;
(实际的PostgreSQL C代码调用此类型int32
,因为这是C语言的惯例智力*
XX*
方法*XX
* 位.因此请注意,C类型int8
大小为1字节。SQL类型int8
被称为int64
C.另见表38.2.)
另一方面,任何大小的固定长度类型都可以通过引用传递。例如,以下是PostgreSQL类型的示例实现:
/* 16-byte structure, passed by reference */
typedef struct
{
double x, y;
} Point;
在将这些类型传入和传出PostgreSQL函数时,只能使用指向这些类型的指针。要返回这种类型的值,请使用帕洛克
,填充分配的内存,并返回指向它的指针。(此外,如果您只想返回与输入参数之一相同且数据类型相同的值,可以跳过额外的参数。)帕洛克
然后将指针返回到输入值。)
最后,所有可变长度类型也必须通过引用传递。所有可变长度类型必须以恰好为4字节的不透明长度字段开头,该字段将由设置变量大小
; 不要直接设置这个字段!要存储在该类型中的所有数据必须位于该长度字段后面的内存中。长度字段包含结构的总长度,也就是说,它包含长度字段本身的大小。
另一个要点是避免在数据类型值中留下任何未初始化的位;例如,注意将结构中可能存在的任何对齐填充字节归零。如果没有这一点,planner可能会认为数据类型的逻辑等效常量不相等,从而导致计划效率低下(尽管不是错误的)。
# 警告
从不修改按参考传递输入值的内容。如果这样做,可能会损坏磁盘上的数据,因为给定的指针可能会直接指向磁盘缓冲区。这条规则的唯一例外是第38.12节.
例如,我们可以定义类型文本
如下:
typedef struct {
int32 length;
char data[FLEXIBLE_ARRAY_MEMBER];
} text;
这[FLEXIBLE_ARRAY_MEMBER]
表示法表示此声明未指定数据部分的实际长度。
在操作可变长度类型时,我们必须小心分配正确的内存量并正确设置长度字段。例如,如果我们想在一个文本
结构,我们可以使用这样的代码片段:
#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);
...
VARHDRSZ
是相同的大小(int32)
, 但使用宏被认为是很好的风格VARHDRSZ
指可变长度类型的开销大小。此外,长度字段必须使用SET_VARSIZE
宏,而不是简单的赋值。
表 38.2显示了与 PostgreSQL 的许多内置 SQL 数据类型相对应的 C 类型。“Defined In”列给出了获取类型定义需要包含的头文件。(实际定义可能在列出的文件包含的不同文件中。建议用户坚持定义的界面。)请注意,您应该始终包含postgres.h首先在服务器代码的任何源文件中,因为它声明了一些无论如何您都需要的东西,并且因为首先包含其他头文件会导致可移植性问题。
表 38.2.
内置 SQL 类型的等效 C 类型
SQL 类型 | C型 | 定义于 |
---|---|---|
布尔值 | 布尔 | postgres.h (也许编译器内置) |
盒子 | 盒子* | 实用程序/geo_decls.h |
拜茶 | 拜茶* | postgres.h |
“字符” | 字符 | (编译器内置) |
特点 | BpChar* | postgres.h |
cid | 命令 ID | postgres.h |
日期 | 日期ADT | 实用程序/日期.h |
浮动4 (真实的 ) | 浮动4 | postgres.h |
浮动8 (双精度 ) | 浮动8 | postgres.h |
整数2 (小字 ) | 整数16 | postgres.h |
整数4 (整数 ) | 整数32 | postgres.h |
整数8 (大整数 ) | 整数64 | postgres.h |
间隔 | 间隔* | 数据类型/时间戳.h |
lseg | 伦敦证券交易所集团* | 实用程序/geo_decls.h |
姓名 | 姓名 | postgres.h |
数字 | 数字 | 实用程序/数字.h |
样的 | 样的 | postgres.h |
类向量 | 类向量* | postgres.h |
小路 | 小路* | 实用程序/geo_decls.h |
观点 | 观点* | 实用程序/geo_decls.h |
正则程序 | 注册程序 | postgres.h |
文本 | 文本* | postgres.h |
时间 | 项目指针 | 存储/itempr.h |
时间 | 时间ADT | 实用程序/日期.h |
时区时间 | 时间TzADT | 实用程序/日期.h |
时间戳 | 时间戳 | 数据类型/时间戳。H |
带时区的时间戳 | 时间戳 | 数据类型/时间戳。H |
瓦尔查尔 | 瓦尔查尔* | 博士后。H |
希德 | TransactionId | 博士后。H |
现在我们已经讨论了所有可能的基类型结构,我们可以展示一些实函数的例子。
# 38.10.3.版本1呼叫约定
version-1调用约定依靠宏来抑制传递参数和结果的大部分复杂性。版本1函数的C声明始终为:
Datum funcname(PG_FUNCTION_ARGS)
此外,宏调用:
PG_FUNCTION_INFO_V1(funcname);
必须出现在同一源文件中。(按照惯例,它是在函数本身之前编写的。)此宏调用对于内部的
-语言函数,因为PostgreSQL假定所有内部函数都使用版本1约定。但是,动态加载的函数需要它。
在version-1函数中,使用PG_GETARG_*
xxx*()
与参数的数据类型相对应的宏。(在非严格函数中,需要使用PG_ARGISNULL()
; 见下文。)使用PG_返回_*
xxx*()
返回类型的宏。PG_GETARG_*
xxx*()
将要获取的函数参数的编号作为其参数,其中计数从0开始。PG_返回_*
xxx*()
将要返回的实际值作为其参数。
下面是使用版本1调用约定的一些示例:
#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"
PG_MODULE_MAGIC;
/* 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);
}
假设上述代码已在文件中准备好芬克斯。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;
在这里*目录
表示共享库文件的目录(例如PostgreSQL教程目录,其中包含本节中使用的示例代码)。(更好的方式是使用“funcs”
在像
在加入目录
*到搜索路径。在任何情况下,我们都可以省略共享库的特定于系统的扩展所以
.)
请注意,我们已将函数指定为“strict”,这意味着如果任何输入值为null,系统应自动假定为null结果。通过这样做,我们可以避免检查函数代码中的空输入。如果没有这个,我们必须使用PG_ARGISNULL()
.
宏PG_ARGISNULL(*
n*)
允许函数测试每个输入是否为空。(当然,只有在未声明为“严格”的函数中才需要这样做。)就跟PG_GETARG_*
xxx*()
宏,输入参数从零开始计数。注意,应该避免执行PG_GETARG_*
xxx*()
直到你验证了这个参数不是空的。要返回空结果,请执行PG_RETURN_NULL()
; 这适用于严格函数和非严格函数。
乍一看,与使用普通编码相比,版本1的编码约定似乎只是毫无意义的蒙昧主义C
电话会议。然而,它们确实允许我们处理无效的
可编辑的参数/返回值,以及“toasted”(压缩或不一致)值。
version-1接口提供的其他选项是PG_GETARG_*
xxx*()
宏。第一个,,PG_GETARG_*
xxx*_复印件()
,保证返回可安全写入的指定参数的副本。(普通宏有时会返回指向物理存储在表中的值的指针,该值不能写入。使用PG_GETARG_*
xxx*_复印件()
宏保证了可写的结果。)第二种变体包括PG_GETARG_*
xxx*_切片()
包含三个参数的宏。第一个是函数参数的编号(如上所述)。第二个和第三个是要返回的段的偏移量和长度。偏移量从零开始计数,负长度要求返回值的剩余部分。在存储类型为“external”的情况下,这些宏可以更有效地访问大值的部分。(可以使用指定列的存储类型。)改变桌子*
数据表名*变列*
科尔曼*设置存储*
存储类型*
. *存储类型
*是其中之一平原
, 外部的
, 延长
或主要的
.)
最后,version-1函数调用约定使返回集合结果成为可能(第38.10.8节)并实现触发功能(第39章)和过程语言调用处理程序(第56章).有关更多详细信息,请参阅src/backend/utils/fmgr/README
在源分布中。
# 38.10.4.编写代码
在讨论更高级的主题之前,我们应该先讨论一下PostgreSQL C语言函数的一些编码规则。虽然可能将用C语言编写的函数装入PostgreSQL,但这通常是困难的(当它完全可能时),因为其他语言,如C++、FORTRAN或Pascal,通常不遵循与C相同的调用约定。也就是说,其他语言不以相同的方式传递函数之间的参数和返回值。因此,我们假设您的C语言函数实际上是用C语言编写的。
编写和构建C函数的基本规则如下:
使用
pg_配置——包含IR服务器
查找PostgreSQL server头文件在您的系统(或用户将在其上运行的系统)上的安装位置。编译和链接代码,以便动态加载到PostgreSQL中,始终需要特殊标志。看见第38.10.5节有关如何为特定操作系统执行此操作的详细说明。
请记住为共享库定义一个“魔法块”,如中所述第38.10.1节.
分配内存时,请使用PostgreSQL函数
帕洛克
和普弗里
而不是相应的C库函数马洛克
和自由的
.所分配的内存帕洛克
将在每个事务结束时自动释放,防止内存泄漏。始终使用
清零
(或分配给他们帕洛克
首先)。即使为结构的每个字段赋值,也可能存在包含垃圾值的对齐填充(结构中的孔)。如果没有这一点,就很难支持散列索引或散列联接,因为必须只提取数据结构中的有效位来计算散列。planner有时还依赖于通过位相等来比较常量,因此如果逻辑相等的值不按位相等,则可能会得到不理想的规划结果。大多数内部PostgreSQL类型都是在
博士后。H
,而函数管理器(PG_函数_参数
等)都在fmgr。H
,所以您需要至少包含这两个文件。为了便于携带,最好包括博士后。H
第一,在任何其他系统或用户头文件之前。包括…在内博士后。H
还将包括埃洛格。H
和帕洛克。H
为你。对象文件中定义的符号名称不得相互冲突,也不得与PostgreSQL server可执行文件中定义的符号冲突。如果收到错误消息,则必须重命名函数或变量。
# 38.10.5.编译和链接动态加载的函数
在使用用C编写的PostgreSQL扩展函数之前,必须以特殊的方式对它们进行编译和链接,以生成可由服务器动态加载的文件。准确地说,一个共享库需要创造。
对于本节以外的信息,您应该阅读操作系统的文档,尤其是C编译器的手册页,复写的副本
,以及链接编辑器,ld
。此外,PostgreSQL源代码中包含几个工作示例contrib
目录但是,如果依赖这些示例,您的模块将依赖于PostgreSQL源代码的可用性。
创建共享库通常类似于链接可执行文件:首先将源文件编译成目标文件,然后将目标文件链接在一起。对象文件需要创建为位置无关代码(图),这在概念上意味着,当可执行文件加载它们时,它们可以被放置在内存中的任意位置。(用于可执行文件的对象文件通常不会以这种方式编译。)链接共享库的命令包含特殊标志,以区分它与链接可执行文件(至少在理论上——在某些系统上,这种做法要丑陋得多)。
在下面的例子中,我们假设您的源代码在一个文件中福。C
我们将创建一个共享库福。所以
。将调用中间对象文件福。o
除非另有说明。一个共享库可以包含多个对象文件,但我们这里只使用一个。
创建PIC的编译器标志是-fPIC
.要创建共享库,编译器标志为-分享
.
gcc -fPIC -c foo.c
gcc -shared -o foo.so foo.o
这适用于FreeBSD的3.0版。
要创建PIC的系统编译器的编译器标志为+z
.使用GCC时-fPIC
。共享库的链接器标志为-b
.所以:
cc +z -c foo.c
或者:
gcc -fPIC -c foo.c
然后:
ld -b -o foo.sl foo.o
HP-UX使用该扩展.sl
用于共享库,与大多数其他系统不同。
创建PIC的编译器标志是-fPIC
.创建共享库的编译器标志为-分享
.完整的示例如下所示:
cc -fPIC -c foo.c
cc -shared -o foo.so foo.o
下面是一个例子。它假定已安装开发人员工具。
cc -c foo.c
cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o
创建PIC的编译器标志是-fPIC
.对于ELF系统,带有标志的编译器-分享
用于链接共享库。在旧的非ELF系统上,可共享的
被使用了。
gcc -fPIC -c foo.c
gcc -shared -o foo.so foo.o
创建PIC的编译器标志是-fPIC
. 可共享的
用于链接共享库。
gcc -fPIC -c foo.c
ld -Bshareable -o foo.so foo.o
创建PIC的编译器标志是-KPIC
使用Sun编译器和-fPIC
与GCC合作。要链接共享库,编译器选项为-G
使用编译器或-分享
与GCC合作。
cc -KPIC -c foo.c
cc -G -o foo.so foo.o
或
gcc -fPIC -c foo.c
gcc -G -o foo.so foo.o
# 提示
如果这对你来说太复杂了,你应该考虑使用。GNU Libtool (opens new window),它将平台差异隐藏在统一界面后面。
然后可以将生成的共享库文件加载到PostgreSQL中。将文件名指定给创建函数
命令时,必须为其指定共享库文件的名称,而不是中间对象文件的名称。请注意,系统的标准共享库扩展(通常是所以
或.sl
)可以从创建函数
命令,并且通常应该省略以获得最佳可移植性。
回头看第38.10.1节关于服务器希望在何处找到共享库文件。
# 38.10.6.复合类型参数
复合类型不像C结构那样有固定的布局。复合类型的实例可以包含空字段。此外,作为继承层次结构一部分的复合类型可以与同一继承层次结构的其他成员具有不同的字段。因此,PostgreSQL提供了一个函数接口,用于从C访问复合类型的字段。
假设我们想要编写一个函数来回答查询:
SELECT name, c_overpaid(emp, 1500) AS overpaid
FROM emp
WHERE name = 'Bill' OR name = 'Sam';
使用version-1调用约定,我们可以定义c_多付
作为:
#include "postgres.h"
#include "executor/executor.h" /* for GetAttributeByName() */
PG_MODULE_MAGIC;
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
返回一个资料
值,可以使用适当的DatumGet*
XXX*()
宏。请注意,如果设置了null标志,则返回值没有意义;在尝试对结果执行任何操作之前,请始终检查null标志。
还有GetAttributeByNum
,它按列号而不是名称选择目标属性。
下面的命令声明函数c_多付
在SQL中:
CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean
AS 'DIRECTORY/funcs', 'c_overpaid'
LANGUAGE C STRICT;
注意,我们使用了严格的
这样我们就不必检查输入参数是否为空。
# 38.10.7.返回行(复合类型)
要从C语言函数返回行或复合类型值,可以使用提供宏和函数的特殊API来隐藏构建复合数据类型的大部分复杂性。要使用此API,源文件必须包括:
#include "funcapi.h"
有两种方法可以构建复合数据值(以下称为“元组”):可以从一个基准值数组中构建它,或者从一个可以传递给元组列数据类型的输入转换函数的C字符串数组中构建它。在这两种情况下,您首先需要获取或构造TupleDesc
元组结构的描述符。在使用基准时,您会通过TupleDesc
到BlessTupleDesc
,然后打电话heap_form_tuple
每行。使用C字符串时,传递TupleDesc
到TupleDescGetAttInMetadata
,然后打电话构建TupleFromCstring
每行。在函数返回一组元组的情况下,设置步骤都可以在函数的第一次调用期间完成一次。
有几个助手功能可用于设置所需的TupleDesc
.在大多数返回复合值的函数中,推荐的方法是调用:
TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,
Oid *resultTypeId,
TupleDesc *resultTupleDesc)
同样的fcinfo
结构传递给调用函数本身。(这当然需要使用版本1的调用约定。)结果类型ID
可以指定为无效的
或者作为局部变量的地址来接收函数的结果类型OID。结果二元组
应该是当地人的地址TupleDesc
变量检查结果是否正确TYPEFUNC_复合材料
; 如果是这样,结果二元组
已经充满了需要的东西TupleDesc
(如果不是,您可以按照“函数返回在不能接受类型记录的上下文中调用的记录”这句话报告错误。)
# 提示
获取\调用\结果\类型
可以解析多态函数结果的实际类型;因此,它在返回标量多态结果的函数中很有用,而不仅仅是在返回复合结果的函数中。这个结果类型ID
输出主要用于返回多态标量的函数。
# 笔记
获取\调用\结果\类型
他有一个兄弟姐妹获取导出结果类型
,可用于解析表达式树表示的函数调用的预期输出类型。当试图从函数本身外部确定结果类型时,可以使用此选项。还有获取函数结果类型
,只有函数的OID可用时才能使用。但是,这些函数无法处理声明为返回的函数记录
和获取函数结果类型
无法解析多态类型,因此应优先使用获取\调用\结果\类型
.
用于获取TupleDesc
它们是:
TupleDesc RelationNameGetTupleDesc(const char *relname)
得到一个TupleDesc
对于命名关系的行类型,以及:
TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)
得到一个TupleDesc
基于OID类型。这可以用来获得TupleDesc
对于基本类型或复合类型。对于返回记录
但是,它无法解析多态类型。
一旦你有了TupleDesc
,致电:
TupleDesc BlessTupleDesc(TupleDesc tupdesc)
如果您计划使用基准,或者:
AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)
如果你打算使用C字符串。如果正在编写函数返回集,可以将这些函数的结果保存在FuncCallContext
结构-使用元组描述
或阿廷梅塔
字段分别为。
使用基准时,请使用:
HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, bool *isnull)
建立一个一堆
以基准形式给出用户数据。
使用C字符串时,请使用:
HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
建立一个一堆
以C字符串形式给出用户数据。*价值观
是一个C字符串数组,返回行的每个属性对应一个字符串。每个C字符串应采用属性数据类型的输入函数所期望的形式。要为其中一个属性返回空值,请在价值观
*数组应设置为无效的
。对于返回的每一行,都需要再次调用此函数。
一旦构建了从函数返回的元组,就必须将其转换为日期
使用:
HeapTupleGetDatum(HeapTuple tuple)
转换一堆
进入有效日期。这个日期
如果只想返回一行,则可以直接返回,也可以将其用作集合返回函数中的当前返回值。
下一节将显示一个示例。
# 38.10.8.返回集
C语言函数有两个返回集合的选项(多行)。在一种方法中,称为ValuePerCall模式下,重复调用集合返回函数(每次传递相同的参数),每次调用时返回一个新行,直到不再有行要返回,并通过返回NULL发出信号。因此,set returning函数(SRF)必须在调用之间保存足够的状态,以记住它正在做什么,并在每次调用时返回正确的下一项。在另一个方法中,调用具体化模式下,SRF填充并返回包含其整个结果的tuplestore对象;然后整个结果只发生一次调用,不需要调用间状态。
在使用ValuePerCall模式时,重要的是要记住,查询不能保证运行到完成;也就是说,由于以下选项:限度
,执行器可能会在提取所有行之前停止调用set returning函数。这意味着在最后一次调用中执行清理活动是不安全的,因为这可能永远不会发生。对于需要访问外部资源(如文件描述符)的函数,建议使用物化模式。
本节的其余部分将介绍一组辅助宏,这些宏通常用于(尽管不需要使用)使用ValuePerCall模式的SRF。有关物化模式的更多详细信息,请参见src/backend/utils/fmgr/README
.此外contrib
PostgreSQL源代码发行版中的模块包含许多使用ValuePerCall和Materialize模式的SRF示例。
要使用此处介绍的ValuePerCall支持宏,请包括芬卡皮。H
.这些宏与结构一起工作FuncCallContext
它包含需要在通话中保存的状态。在呼叫SRF中,fcinfo->flinfo->fn_额外
用于保存指向的指针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 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)
把它还给打电话的人。(后果
一定是那种资料
,可以是单个值,也可以是如上所述准备的元组。)最后,当您的函数完成返回数据时,请使用:
SRF_RETURN_DONE(funcctx)
清理并结束SRF。
调用SRF时当前的内存上下文是在调用之间清除的临时上下文。这意味着你不需要打电话普弗里
在你使用帕洛克
; 无论如何它都会消失的。然而,如果您想要分配任何数据结构来跨越调用,您需要将它们放在其他地方。引用的内存上下文多呼叫存储ctx
对于任何需要保存到SRF运行完成之前的数据,都是一个合适的位置。在大多数情况下,这意味着你应该切换到多呼叫存储ctx
在进行第一次通话设置时。使用FunctX->user_fctx
持有指向任何此类交叉调用数据结构的指针。(您在中分配的数据)多呼叫存储ctx
将在查询结束时自动消失,因此也无需手动释放该数据。)
# 警告
虽然函数的实际参数在两次调用之间保持不变,但如果您删除参数值(这通常由PG_GETARG_*
xxx*
宏)在瞬态上下文中,则在每个循环中都会释放已删除的副本。因此,如果您在您的用户_fctx
,则必须将它们复制到多呼叫存储ctx
在detoast之后,或者确保仅在该上下文中detoast值。
完整的伪代码示例如下所示:
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;
注意,在这个方法中,函数的输出类型形式上是匿名的记录
类型
# 38.10.9.多态参数和返回类型
C语言函数可以声明为接受和返回中描述的多态类型第38.2.5节.当函数的参数或返回类型被定义为多态类型时,函数作者无法提前知道调用它的数据类型或需要返回的数据类型。中提供了两个例程fmgr。H
允许版本1 C函数发现其参数的实际数据类型以及预期返回的类型。这些例行程序被称为get_fn_expr_rettype(FmgrInfo*flinfo)
和get_fn_expr_argtype(FmgrInfo*flinfo,int argnum)
.返回结果或参数类型OID,或残疾人
如果信息不可用。结构弗林弗
通常作为fcinfo->flinfo
.参数阿格努姆
是零基的。获取\调用\结果\类型
也可用作替代品获取\u fn\u expr\u rettype
.还有获取变量
,可用于确定变量参数是否已合并到数组中。这主要适用于可变“任意”
函数,因为对于采用普通数组类型的变量函数,这种合并总是会发生的。
例如,假设我们要编写一个函数来接受任何类型的单个元素,并返回该类型的一维数组:
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中:
CREATE FUNCTION make_array(anyelement) RETURNS anyarray
AS 'DIRECTORY/funcs', 'make_array'
LANGUAGE C IMMUTABLE;
多态性的一个变体仅适用于C语言函数:它们可以声明为接受类型的参数“任何”
(请注意,此类型名称必须双引号,因为它也是SQL保留字。)这就像任何元素
除了它不约束不同的“任何”
参数必须是相同的类型,也不能帮助确定函数的结果类型。C语言函数还可以将其最终参数声明为可变“任意”
。这将匹配任何类型(不一定是同一类型)的一个或多个实际参数。这些论点将不被收集成一个数组,就像正常的变量函数一样;它们将单独传递给函数。这个PG_NARGS()
使用此功能时,必须使用宏和上述方法来确定实际参数的数量及其类型。此外,此类功能的用户可能希望使用可变的
关键字,期望函数将数组元素视为单独的参数。如果需要,函数本身必须在使用后实现该行为获取变量
检测实际论点是否带有可变的
.
# 38.10.10.共享内存和LWLocks
外接程序可以在服务器启动时保留LWLock和共享内存分配。必须通过在中指定外接程序的共享库来预加载外接程序的共享库共享_预加载_图书馆.通过调用以下命令保留共享内存:
void RequestAddinShmemSpace(int size)
从你的_PG_init
作用
通过调用以下命令保留LWLock:
void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)
从…起_PG_init
。这将确保num_lwlocks
LWLocks的名称为份额名称
使用GetNamedLWLockTranche
获取指向此数组的指针。
为了避免可能的竞争条件,每个后端都应该使用LWLockAddinsmeminitlock
连接并初始化其共享内存分配时,如下所示:
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);
}
# 38.10.11.利用C++实现可扩展性
虽然PostgreSQL后端是用C编写的,但是如果遵循以下准则,则可以在C++中编写扩展名:
后端访问的所有函数都必须向后端提供一个C接口;这些C函数可以调用C++函数。例如
外部C
后端访问的功能需要链接。这对于任何作为后端和C++代码之间指针传递的函数也是必要的。使用适当的释放方法释放内存。例如,大多数后端内存是使用
帕洛克()
,所以使用pfree()
释放它。使用C++删去
在这种情况下,我们将失败。防止异常传播到C代码中(在all的顶层使用catch all块
外部C
功能)。这是必要的,即使C++代码没有明确地抛出任何异常,因为像内存不足之类的事件仍然可以抛出异常。必须捕获任何异常,并将适当的错误传递回C接口。如果可能,编译C++-fno例外
彻底消除例外;在这种情况下,必须检查C++代码中的故障,例如,检查是否返回空值。新的
.如果从C++代码调用后端函数,请确保C++调用堆栈只包含普通的旧数据结构(POD)。这是必要的,因为后端错误会产生远程错误
longjmp()
这不能正确地打开一个C++调用堆栈,而非POD对象。总之,最好把C++代码放在墙的后面。
外部C
与后端接口并避免异常、内存和调用堆栈泄漏的函数。