这篇文章是几年前写的,不知道可以放在哪里,然后忘记了。与解释(脚本)语言相比,为PostgreSQL开发扩展时使用C语言的含义可以减少为两点:性能和功能。但简单来说,用C编写的代码将运行得更快,例如,如果在每个记录的请求中调用了该函数一百万次。更具体地说,除了C语言外,某些PostgreSQL功能根本无法完成,例如,其他语言,类型(尤其是从函数返回值)不支持ANYELEMENT,ANYARRAY以及特别重要的VARIADIC。简单的C函数
例如,我们编写一个接受两个参数并将其相加的函数。PostgreSQL的文档中描述了此示例,但是我们将对其稍作改进并进行收集。然后加载到PostgreSQL并从SQL函数编写调用。#include "postgres.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(add_ab);
Datum add_ab(PG_FUNCTION_ARGS)
{
int32 arg_a = PG_GETARG_INT32(0);
int32 arg_b = PG_GETARG_INT32(1);
PG_RETURN_INT32(arg_a + arg_b);
}
add_func.c
文件可以用作开发更复杂功能的模板。另外,通过代码- #include“ postgresql.h”:您将始终必须包含的此头文件,包含各种基本类型和功能。
- #include“ fmgr.h”:头文件包含各种PG_ *宏。
- PG_MODULE_MAGIC:一个宏,用于确定我们正在为8.2版以上的PostgreSQL开发模块。
- PG_FUNC_INFO_V1:在PostgreSQL中定义函数调用约定的宏。如果声明一个没有该函数的函数,则将在版本0或其他版本1上达成一致。
- Datum: . , PostgreSQL. - VARIANT Microsoft. «» - . , .
- add_ab(PG_FUNCTION_ARGS): . - . , .
- int32 arg_a = PG_GETARG_INT32(0): ( ).
- PG_RETURN_INT32(arg_a + arg_b): .
现在我们需要正确地编译它。输出将是一个可动态加载的共享库(* .so)。为此,通过Makefile进行操作将更加方便。该文档描述了需要注册的密钥和路径,但是我们将使用PGXS进行收集。这是扩展开发人员的环境,这意味着必须在系统上安装PostgreSQL的所有必需的-dev和-devel软件包。MODULES = add_func
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
我们创建SQL函数
现在,我们将编写一个SQL函数,该函数将从先前创建的扩展库中调用。CREATE FUNCTION add(int, int)
RETURNS int
AS '/usr/lib/postgresql/9.4/lib/add_func', 'add_ab'
LANGUAGE C STRICT;
就这样!现在我们可以使用此功能了SELECT add(1, 2);
自动化安装
现在,我们将安装自动化一些。如果您事先不知道使用哪个PostgreSQL版本以及以哪种方式安装它,这将非常有用。为此,请创建以下文件,CREATE FUNCTION add(int, int)
RETURNS int
AS 'MODULE_PATHNAME', 'add_ab'
LANGUAGE C STRICT;
并在Makefile中添加一行,MODULES = add_func
DATA_built = add_funcs.sql
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
改善一点
我们将对示例进行一些更改并进行一些改进。检查参数
还记得在add(int,int)函数的SQL定义中,我们使用STRICT关键字吗?这意味着,如果至少一个参数为NULL,则该函数将不起作用,并且将仅返回NULL。这类似于SQL语句的行为,例如,如果+运算符中至少有一个NULL参数,则结果将为NULL。我们向函数添加了一个参数检查,类似于SQL聚合函数sum(),它忽略NULL值并继续工作。为此,我们需要做- 即使该参数之一为NULL,也要确保该函数正常工作
- 如果两个参数均为NULL,则返回NULL
PG_FUNCTION_INFO_V1(add_ab_null);
Datum add_ab_null(PG_FUNCTION_ARGS)
{
int32 not_null = 0;
int32 sum = 0;
if (!PG_ARGISNULL(0))
{
sum += PG_GETARG_INT32(0);
not_null = 1;
}
if (!PG_ARGISNULL(1))
{
sum += PG_GETARG_INT32(1);
not_null = 1;
}
if (not_null)
{
PG_RETURN_INT32(sum);
}
PG_RETURN_NULL();
}
现在看看CREATE FUNCTION add(int, int)
RETURNS int
AS '$libdir/add_func', 'add_ab_null'
LANGUAGE C;
SELECT add(NULL, NULL) AS must_be_null, add(NULL, 1) AS must_be_one;
-[ RECORD 1 ]+
must_be_null |
must_be_one | 1
这是使用标准PostgreSQL工具可以实现的相同方法,SELECT (CASE WHEN (a IS null) AND (b IS null)
(THEN null
ELSE coalesce(a, 0) + coalesce(b,0)
END)
FROM (SELECT 1::int AS a, null::int AS b) s;
-[ RECORD 1 ]
case | 1
函数中的任意数量的参数
正如您已经注意到的,我们使用宏来获取参数的值。因此,我们可以传递任意数量的参数,然后只需循环读取它们的值,if (!PG_ARGISNULL(i))
{
sum += PG_GETARG_INT32(i);
not_null = 1;
}
您可能会立即有一个问题,但是您可以使用数组来传递参数的值。实际上,这是需要完成的工作,但是不幸的是,由于在PostgreSQL中存在自己的内存管理器,所以这并不是一件容易的事。但是尝试解决它。PostgreSQL文档中有一个示例,其中传递了文本[]字符数组,但这并不是我们真正需要的。让我们尝试适应我们的目的,#include "utils/array.h"
#include "catalog/pg_type.h"
PG_MODULE_MAGIC;
Datum add_int32_array(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(add_int32_array);
Datum add_int32_array(PG_FUNCTION_ARGS)
{
ArrayType *input_array;
int32 sum = 0;
bool not_null = false;
Datum *datums;
bool *nulls;
int count;
int i;
input_array = PG_GETARG_ARRAYTYPE_P(0);
Assert(ARR_ELEMTYPE(input_array) == INT4OID);
if (ARR_NDIM(input_array) > 1)
ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("1-dimensional array needed")));
deconstruct_array(input_array,
INT4OID,
4,
true,
'i',
&datums, &nulls, &count);
for(i = 0; i < count; i++)
{
if (nulls[i])
continue;
sum += DatumGetInt32(datums[i]);
not_null = true;
}
if (not_null)
PG_RETURN_INT32(sum);
PG_RETURN_NULL();
}
和往常一样,让我们看一下代码,- 整数数组没有特殊类型,因此我们使用通用类型ArrayType,该类型适用于任何类型的数组
- 为了使用第一个参数初始化数组,我们使用了特殊的宏PG_GETARG_ARRAYTYPE_P
- 还检查数组是否真的是一维ARR_NDIM
- int4(= 23)的OID类型定义为INT4OID。要查看其他类型的定义,可以使用SQL,
select oid, typlen, typbyval, typalign from pg_type
where typname = 'int4';
-[ RECORD 1 ]
oid | 23
typlen | 4
typbyval | t
typalign | i
现在,我们只需要教PostgreSQL通过声明一个带有int []参数的函数来使用它,CREATE OR REPLACE FUNCTION add_arr(int[]) RETURNS int
AS '$libdir/add_func', 'add_int32_array'
LANGUAGE C STRICT;
并检查SELECT add_arr('{1,2,3,4,5,6,7,8,9}');
-[ RECORD 1 ]
add_arr | 45
SELECT add_arr(ARRAY[1,2,NULL]);
-[ RECORD 1 ]
add_arr | 3
SELECT add_arr(ARRAY[NULL::int]);
-[ RECORD 1 ]
add_arr |
推荐建议
现在,我们总结一些有关创建扩展的主要建议。处理内存
PostgreSQL DBMS的作者和创建者特别注意处理内存和防止泄漏。这是开发DBMS及其扩展的基本规则之一。这是基于他们自己的决定-内存的上下文。使用palloc()和pfree()
PostgreSQL中所有与内存有关的工作都包含使用非标准函数palloc()和pfree()。结构的初始化
始终初始化新的声明结构。例如,在palloc()之后调用memset()。连接文件
每个扩展名必须至少包含两个文件:postgres.h和fmgr.h。有用的链接
PostgreSQL:服务器编程接口。PostgreSQL:C PostgreSQL扩展网络中的用户定义函数