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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)

八、返回数据集

C语言函数有两种返回数据集(多行数据)的方法:一种称为 ValuePerCall 方式,重复调用返回数据集的函数(每次调用传递相同的参数),每次调用返回一行,最后返回NULL表示没有更多的行。每次调用时,返回数据集的函数(SRF)必须保存足够多的状态以在整个调用中记住该做什么并且返回正确的下一条目。另一种称为 Materialize 方式,SRF填充并返回包含全部结果的tuplestore对象;一次调用获取全部结果,也无需保存内部调用状态。

在使用ValuePerCall模式时,需要注意查询可能不会执行完成;也就是说,由于LIMIT等选项,执行器可能会在获取完所有行之前停止调用返回值函数。这意味着在最后一个调用中执行清理操作是不安全的,因为这可能永远不会发生。对于需要访问外部资源(例如文件描述符)的函数,建议使用Materialize模式。

本节剩余部分介绍了一套常用助手宏(虽然不需要使用),这些宏用于ValuePerCall模式下的SRF(可视化数据查询)。有关Materialize模式的详细信息,请参见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 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 类型,可以是一个单个值或像上面描述的那样预处理的元组。)最后,当您的函数完成返回数据时,请使用:

SRF_RETURN_DONE(funcctx) 清理并结束 SRF。

当 SRF 被调用时,当前的内存上下文是一个短暂的上下文,将在每次调用之间被清除。这意味着您不需要为使用 palloc 分配的所有内容调用 pfree;无论如何它都会消失。但是,如果您想分配任何跨越调用的数据结构,则需要将其放在其他地方。multi_call_memory_ctx 引用的内存上下文是任何需要在 SRF 完成运行之前生存的数据的合适位置。 在大多数情况下,这意味着您应该在进行第一次调用设置时切换到 multi_call_memory_ctx。 使用 funcctx->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_INT32(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 类型。

九、多态参数和返回类型

C语言函数可以声明接受和返回第38.2.5节中描述的多态类型。当一个函数的参数或返回值被定义为多态类型时,函数作者无法事先知道它将被什么数据类型调用,或者需要返回什么类型。fmgr.h 提供了两个例程,允许版本 1 的 C 函数发现其参数的实际数据类型以及它期望返回的类型。这些例程称为 get_fn_expr_rettype(FmgrInfo *flinfo)get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)。它们返回结果或参数类型的 OID,或者如果信息不可用则返回 InvalidOid。结构 flinfo 通常通过 fcinfo->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 后执行。

十、共享内存和LWLock

插件可以在服务器启动时预留 LWLock 和共享内存分配。插件的共享库必须通过在 `shared_preload_libraries` 中指定它来提前加载。共享库应该在其 _PG_init 函数中注册一个 shmem_request_hook。这个 shmem_request_hook 可以预留 LWLock 或共享内存。共享内存是通过你`shmem_request_hook`调用:

void RequestAddinShmemSpace(int size)

LWLock 从你的shmem_request_hook调用:

void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)

这将确保在名为 `tranche_name` 的下,可用一个大小为 `num_lwlocks` 的 LWLocks 数组。使用GetNamedLWLockTranche获取指向此数组的指针。

可以在 PostgreSQL 源代码树中的 contrib/pg_stat_statements/pg_stat_statements.c 中找到一个 `shmem_request_hook` 示例。 为了避免可能的竞态条件,每个后台应在连接并初始化其共享内存分配时使用 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);
}

十一、在扩展中使用C++

尽管PostgreSQL后端是用C语言编写的,但只要遵循以下规则,也可以用C++编写扩展:

  • 所有要被后端调用的函数需提供一个C语言接口,后端通过这些C函数调用C++函数。比如:可被后端访问的函数必须加上 extern C 标志。任何在后端与C++代码间通过指针传递的函数都必须这样声明。
  • 使用合适的方法释放内存。比如:后端多使用 palloc()分配内存,因此要使用 pfree() 释放。此场景如果使用C++的 delete 则会失败。
  • 避免将异常传递到C代码 (在所有顶级extern C 函数中使用catch-all块)。即使C++代码没有显示的抛出任何异常这也是必要的,因为诸如内存不足(OOM)之类的事件会持续抛出异常。任何异常都必须被捕获并传递适当的错误到C接口。如果必要,编译C++代码时使用-fno-exceptions 选项以完全消除异常,在此种场景下,你必须检查C++代码中的错误,比如使用 new()后检查返回结果是否为NULL。
  • 如果从C++代码中调用后端函数,要确保C++调用栈中仅包含plain old data (POD)结构。这很必要,因为后端错误会生成一个长距离longjmp() ,它无法正确展开带有非POD对象的C++调用栈。

总之,供后端调用的C++接口函数要声明成 extern C ,并且要避免异常、内存、调用栈泄漏。

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
2月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
36 3
|
12天前
|
SQL 开发框架 .NET
突破T-SQL限制:利用CLR集成扩展RDS SQL Server的功能边界
CLR集成为SQL Server提供了强大的扩展能力,突破了T-SQL的限制,极大地拓展了SQL 的应用场景,如:复杂字符串处理、高性能计算、图像处理、机器学习集成、自定义加密解密等,使开发人员能够利用 .NET Framework的丰富功能来处理复杂的数据库任务。
|
18天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
31 6
|
1天前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
19 6
|
2月前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
43 10
|
1月前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
2月前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
63 7
|
2月前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
32 4
|
2月前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。
|
2月前
|
SQL Oracle 关系型数据库
SQL优化-使用联合索引和函数索引
在一次例行巡检中,发现一条使用 `to_char` 函数将日期转换为字符串的 SQL 语句 CPU 利用率很高。为了优化该语句,首先分析了 where 条件中各列的选择性,并创建了不同类型的索引,包括普通索引、函数索引和虚拟列索引。通过对比不同索引的执行计划,最终确定了使用复合索引(包含函数表达式)能够显著降低查询成本,提高执行效率。