PostgreSQL:用C语言开发扩展(功能)

这篇文章是几年前写的,不知道可以放在哪里,然后忘记了。

与解释(脚本)语言相比,为PostgreSQL开发扩展时使用C语言的含义可以减少为两点:性能和功能。但简单来说,用C编写的代码将运行得更快,例如,如果在每个记录的请求中调用了该函数一百万次。更具体地说,除了C语言外,某些PostgreSQL功能根本无法完成,例如,其他语言,类型(尤其是从函数返回值)不支持ANYELEMENTANYARRAY以及特别重要的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" // INT4OID
PG_MODULE_MAGIC;
Datum add_int32_array(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(add_int32_array);
Datum add_int32_array(PG_FUNCTION_ARGS)
{
    //     .
    //          ,       int.
    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); //     .  *_P  
                                            //        ,   INT32

    //          INT32 (INT4)
    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,        // int4   
                      'i',         //  '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扩展网络中的用户定义函数


All Articles