PostgreSQL函数返回结果集

原创
2020/07/28 07:26
阅读数 636

有时我们需要定义返回结果集的函数,本文演示如何编码。

遂古之初,谁传道之?上下未形,何由考之?《天问》

 

1、函数定义

C语言定义部分

PG_FUNCTION_INFO_V1(quanzl_dummy_setof);

Datum
quanzl_dummy_setof(PG_FUNCTION_ARGS)
{...}

这里与一般的SQL函数的C语言定义没什么区别,目前调用协议也只有V1。

SQL定义,如果希望返回三个字段的值:

CREATE FUNCTION quanzl_dummy_setof(OUT int, OUT int, OUT int)
AS 'quanzl_dummy' LANGUAGE C;

可以指定出参名,不指定它们自动生成为:column1、column2、column3。

 

2、函数调用上下文FuncCallContext

src/include/funcapi.h,这里也定义了结果集函数需要的其他宏。

uint64		call_cntr;
uint64		max_calls;
void	   *user_fctx;

call_cntr:已调用次数,不包含本次,由PG函数调用系统自动维护。

max_calls:最大调用次数,但PG不会以它为依据结束函数的调用,它是给用户代码使用的,至少目前是这样。

user_fctx:可用于在调用间传递用户自定义数据,PG对它不做任何处理。

 

3、结果集函数的开始

FuncCallContext *funcctx;
char ***values;

if (SRF_IS_FIRSTCALL())
{
  TupleDesc	tupdesc;

  funcctx = SRF_FIRSTCALL_INIT();
  oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

  tupdesc = CreateTemplateTupleDesc(2);
  TupleDescInitEntry(tupdesc, (AttrNumber) 1, "col1",
               INT4OID, -1, 0);
  TupleDescInitEntry(tupdesc, (AttrNumber) 2, "col2",
               INT4OID, -1, 0);
  TupleDescInitEntry(tupdesc, (AttrNumber) 3, "col3",
               INT4OID, -1, 0);

  funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);

	... 生成 values 的数据 ...

  funcctx->user_fctx = values;
  funcctx->max_calls = length of values;

  MemoryContextSwitchTo(oldcontext);
}

多次调用内存上下文在SRF_FIRSTCALL_INIT之后才有效,这里顺序不能错。切换上下文不是必须的,一般我们会这么做,将函数专用的数据生成在这里,即使忘记回收调用结束后会自动清理。

TupleDesc的定义必须与SQL定义匹配,轻则结果错误,重则直接崩溃。

funcctx->attinmeta 照葫芦画瓢即可,没什么特别,后边生成元祖时会用得到。

values的生成根据业务需要编写,演示用可以写死一个固定数组。

 

4、单次调用与函数结束

funcctx = SRF_PERCALL_SETUP();
values = (char ***) funcctx->user_fctx;

if (funcctx->call_cntr < funcctx->max_calls)
{
  ... 返回单条结果 ...
}
else
  SRF_RETURN_DONE(funcctx);

第一个看名字也知道,是每次必须调用的。

第一次调用时生成的user_fctx,在后续调用中取出来继续利用。

在没有结束之前,每次调用只返回一条记录;当调用次数达到上限时,结束调用。

 

5、返回单条结果

tuple = BuildTupleFromCStrings(funcctx->attinmeta, values[funcctx->call_cntr]);
result = HeapTupleGetDatum(tuple);
SRF_RETURN_NEXT(funcctx, result);

实际实现根据业务需求的不同,要复杂的多,这里只是演示,所以简单这么一写。PG代码中有很多例子,搜索一下 SRF_IS_FIRSTCALL 可以发现很多。

 

6、完整示例

连起来看看我们的演示代码,会更清晰一些:

PG_FUNCTION_INFO_V1(quanzl_dummy_setof);

Datum
quanzl_dummy_setof(PG_FUNCTION_ARGS)
{
  FuncCallContext *funcctx;
  char ***values;

  if (SRF_IS_FIRSTCALL())
  {
    TupleDesc	tupdesc;

    funcctx = SRF_FIRSTCALL_INIT();
    oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

    tupdesc = CreateTemplateTupleDesc(2);
    TupleDescInitEntry(tupdesc, (AttrNumber) 1, "col1",
                 INT4OID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) 2, "col2",
                 INT4OID, -1, 0);
    TupleDescInitEntry(tupdesc, (AttrNumber) 3, "col3",
                 INT4OID, -1, 0);

    funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);

	  ... 生成 values 的数据 ...

    funcctx->user_fctx = values;
    funcctx->max_calls = length of values;

    MemoryContextSwitchTo(oldcontext);
  }

  funcctx = SRF_PERCALL_SETUP();
  values = (char ***) funcctx->user_fctx;

  if (funcctx->call_cntr < funcctx->max_calls)
  {
    tuple = BuildTupleFromCStrings(funcctx->attinmeta, values[funcctx->call_cntr]);
    result = HeapTupleGetDatum(tuple);

    SRF_RETURN_NEXT(funcctx, result);
  }
  else
    SRF_RETURN_DONE(funcctx);
}

返回结果集还有另一种写法,有时间再补充。

 

公众号同步发布

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部