欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

如何使用C语言编写函数: 温馨提示37.9版

最编程 2024-02-05 18:45:32
...

37.9.8. 返回集合

也提供了一种特殊的 API 来支持从 C 语言函数中返回集合(多个行)。 集合返回函数必须遵循版本-1 的调用规范。如上文所述,源文件中还 必须包括funcapi.h

对返回的每一个项,一个集合返回函数(SRF)都会 被调用一次。因此,这个SRF必须保存足够的状态来 记住它正在做什么并且在每次调用时返回下一个项。结构 FuncCallContext被提供来帮助控制这个过程。在 一个函数中,fcinfo->flinfo->fn_extra被用来在 多次调用中保持指向FuncCallContext的指针。

typedef struct FuncCallContext
{
    /*
     * 本次调用以前已经被调用过多少次
     *
     * SRF_FIRSTCALL_INIT() 会为你把 call_cntr 初始化为 0,
     * 并且在每次调用 SRF_RETURN_NEXT() 时增加。
     */
    uint64 call_cntr;

    /*
     * 可选:最大调用次数
     *
     * 这里的 max_calls 只是为了方便,设置它是可选的。
     * 如果没有设置,你必须提供替代的方法来了解函数什么时候做完。
     */
    uint64 max_calls;

    /*
     * 可选:指向结果槽的指针
     *
     * 这已经被废弃并且只为向后兼容而存在,也就是那些使用被废弃的
     * TupleDescGetSlot() 的用户定义 SRF。.
     */
    TupleTableSlot *slot;

    /*
     * 可选:指向用户提供的上下文信息的指针
     *
     * user_fctx 是一个指向你自己的数据的指针,它可用来在函数的多次
     * 调用之间保存任意的上下文信息。
     */
    void *user_fctx;

    /*
     * 可选:指向包含属性类型输入元数据的结构的指针
     *
     * attinmeta 被用在返回元组(即组合数据类型)时,在返回基本数据类型
     * 时不会使用。只有想用BuildTupleFromCStrings()创建返回元组时才需要它。
     */
    AttInMetadata *attinmeta;

    /*
     * 用于保存必须在多次调用间都存在的结构的内存上下文
     *
     * SRF_FIRSTCALL_INIT() 会为你设置 multi_call_memory_ctx,并且由
     * SRF_RETURN_DONE() 来清理。对于任何需要在 SRF 的多次调用间都
     * 存在的内存来说,它是最合适的内存上下文。
     */
    MemoryContext multi_call_memory_ctx;

    /*
     * 可选:指向包含元组描述的结构的指针
     *
     * tuple_desc 被用在返回元组(即组合数据类型)时,并且只有在用
     * heap_form_tuple() 而不是 BuildTupleFromCStrings() 构建元组时才需要它。
     * 注意这里存储的 TupleDesc 指针通常已经被先运行过 BlessTupleDesc()。
     */
    TupleDesc tuple_desc;

} FuncCallContext;

SRF使用一些自动操纵FuncCallContext (并且期望通过fn_extra找到它)的函数和宏。可使用:

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,它们将自动被释放。不过, 如果你想要分配任何需要在多次调用间都存在的数据结构,需要把它们 放在其他地方。对于任何需要在SRF结束运行之前都存 在的数据来说,multi_call_memory_ctx引用的内存 上下文是一个合适的位置。在大部分情况中,这意味着应该在做第一次 调用设置时就切换到multi_call_memory_ctx中。

警告

虽然函数的实参在多次调用之间保持不变,但如果在瞬时上下文中 反 TOAST 了参数(通常由 PG_GETARG_xxx 宏完成),那么被反 TOAST 的拷贝将在每次循环中被释放。相应地, 如果你把这些值的引用保存在user_fctx中,你也必 须在反 TOAST 之后把它们拷贝到 multi_call_memory_ctx中,或者确保你只在那个 上下文中反 TOAST 这些值。

一个完整的伪代码例子:

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);
        /* 这里是一次性设置代码: */
        user code
        if returning composite
            build TupleDesc, and perhaps AttInMetadata
        endif returning composite
        user code
        MemoryContextSwitchTo(oldcontext);
    }

    /* 这里是每一次都要做的设置代码: */
    user code
    funcctx = SRF_PERCALL_SETUP();
    user code

    /* 这里只是一种测试是否执行完的方法: */
    if (funcctx->call_cntr < funcctx->max_calls)
    {
        /* 这里返回另一个项: */
        user code
        obtain result Datum
        SRF_RETURN_NEXT(funcctx, result);
    }
    else
    {
        /* 这里已经完成了项的返回并且需要进行清理: */
        user code
        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;

    /* 只在第一次函数调用时做的事情 */
    if (SRF_IS_FIRSTCALL())
    {
        MemoryContext   oldcontext;

        /* 创建一个函数上下文,让它在多次调用间都保持存在 */
        funcctx = SRF_FIRSTCALL_INIT();

        /* 切换到适合多次函数调用的内存上下文 */
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

        /* 要返回的元组总数 */
        funcctx->max_calls = PG_GETARG_UINT32(0);

        /* 为结果类型构造一个元组描述符 */
        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")));

        /*
         * 生成后面需要用来从原始 C 字符串产生元组的属性元数据
         */
        attinmeta = TupleDescGetAttInMetadata(tupdesc);
        funcctx->attinmeta = attinmeta;

        MemoryContextSwitchTo(oldcontext);
    }

    /* 在每一次函数调用都要完成的事情 */
    funcctx = SRF_PERCALL_SETUP();

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

    if (call_cntr < max_calls)    /* 如果还有要发送的 */
    {
        char       **values;
        HeapTuple    tuple;
        Datum        result;

        /*
         * 为构建返回元组准备一个值数组。这应该是一个 C
         * 字符串数组,之后类型输入函数会处理它。
         */
        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));

        /* 构建一个元组 */
        tuple = BuildTupleFromCStrings(attinmeta, values);

        /* 把元组变成 datum */
        result = HeapTupleGetDatum(tuple);

        /* 清理(实际并不必要) */
        pfree(values[0]);
        pfree(values[1]);
        pfree(values[2]);
        pfree(values);

        SRF_RETURN_NEXT(funcctx, result);
    }
    else    /* 如果没有要发送的 */
    {
        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类型。

源码中的目录contrib/tablefunc 下的模块包含集合返回函数更加复杂的例子。

推荐阅读