如何使用C语言编写函数: 温馨提示37.9版
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_
宏完成),那么被反 TOAST 的拷贝将在每次循环中被释放。相应地,
如果你把这些值的引用保存在xxx
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 下的模块包含集合返回函数更加复杂的例子。
推荐阅读
-
35 岁实现财务*,腾讯程序员手握2300万提前退休?-1000万房产、1000万腾讯股票、加上300万的现金,一共2300万的财产。有网友算了一笔账,假设1000万的房产用于自住,剩下1300万资产按照平均税后20-50万不等进行计算,大约花上26-60年左右的时间才能赚到这笔钱。也就是说,普通人可能奋斗一辈子,才能赚到这笔钱。在很多人还在为中年危机而惶惶不可终日的时候,有的人的35岁,就已经安全着陆,试问哪个打工人不羡慕?但问题是有这样财富积累必然有像样的实力做靠山。没有人可以不劳而获。 看到这里,肯定有人说,那么对于普通人来说,卷可能真就成了唯一的出路。但是卷也有轻松的卷,“偷懒”的卷法,对于程序员而言,刨除掉一时无法改掉的开会传统占用的大部分时间,如何把有限的时间和精力放在真正重要的架构设计、需求设计上,而不是重复的造*,编码、改bug、手动测试。因此在科技改变生活的今天,学会使用AI工具成为程序员们的必备技能。 以全栈式全自动的软件开发工具飞算SoFlu软件机器人为例,作为全球首款面向微服务架构设计和最佳实践的软件机器人,SoFlu软件机器人改变了原来手工编码的作业模式,通过可视化拖拽方式以及参数配置就能实现等同于编写复杂代码的业务逻辑,在设计业务逻辑时就完成了微服务应用开发,做到“业务即图,图即代码”,实现“软件开发,十倍提效”。 而作为飞算SoFlu软件机器人的一个重要组成部分,FuncGPT(慧函数)支持所有类型函数创建。通过自然语言描述Java函数需求,实时生成高质量、高可读性的Java函数代码。生成代码可直接复制到IDEA,或一键导入Java全自动开发工具函数库。 FuncGPT(慧函数)具有五大核心能力: ● 自然语言:通过自然语言即可生成函数,降低软件开发门槛。 ● 秒级函数开发:全自动秒级开发函数,秒级完成,效率千倍提升。 ● 一站式函数开发应用:函数生成即可直接应用,效率、安全有保障。 ● 质量可靠:生成的函数代码符合业界规范、具备良好的可读性,符合最优解。 ● 灵活生成:可以根据开发人员的具体需求生成并支持调整和修改。 以“将文件流转换为File对象”这个需求为例,以下是FuncGPT(慧函数)秒级生成的代码: / 类名:FileConverter // 函数名:convertStreamToFile // 函数功能:将文件流转换为File对象 // POM依赖包:无 import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; public class FileConverter { /** * 将文件流转换为File对象 * @param inputStream 文件流 * @param filePath 文件路径 * @param fileName 文件名 * @return 转换后的File对象 * @throws IOException 如果发生I/O异常 */ public static File convertStreamToFile(InputStream inputStream, String filePath, String fileName) throws IOException { File file = new File(filePath + File.separator + fileName); // 创建File对象 try (FileOutputStream outputStream = new FileOutputStream(file)) { // 创建文件输出流 byte buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { // 从文件流读取数据并写入文件 outputStream.write(buffer, 0, bytesRead); } } return file; // 返回转换后的File对象 } } // 函数示例 // 将文件流转换为File对象示例 // 入参:inputStream,文件流 // 入参:filePath,文件路径 // 入参:fileName,文件名 // 出参:file,转换后的File对象 // 调用示例: // InputStream inputStream = new FileInputStream("example.txt"); // String filePath = "C:\\Users\\User\\Documents"; // String fileName = "example.txt"; // File file = FileConverter.convertStreamToFile(inputStream, filePath, fileName); // System.out.println(file.getAbsolutePath); // 输出结果:例如,将文件流转换为File对象后,文件的绝对路径为:C:\Users\User\Documents\example.txt // 则输出结果为:C:\Users\User\Documents\example.txt 通过分析,不难发现以上代码:
-
c 语言 更多 java c 语言 更多 数字函数 如何编写
-
如何在 C 语言中使用三角函数
-
如何在 C 语言中使用根函数
-
如何使用C语言编写一个小程序来理解并实例化高等数学中的映射、单射与一一对应函数的概念
-
如何帮助C语言初学者修复在编写函数时遇到的涉及数组运行时问题
-
如何使用C语言编写简单文件加密与解密程序
-
如何在C语言中编写一个函数来计算字符串的长度
-
南邮OJ Web任务大揭秘:层层挑战剖析 1. 挑战一:迷宫般的目录探索 题目作者似乎穷举了所有可能的目录组合,最终在404.php中的