英文:
PostgreSQL C Extension function: Table as argument and as Result
问题
我需要编写一个针对PostgreSQL的扩展函数,具体要求如下:
- 参数为一个包含3列double类型的表格
 - 结果为一个包含3列double类型的表格
 
请注意,函数的模式理想情况下应该是材料化模式(materialize mode),因为函数将通过转换整个输入表格的数据生成一个新表格。
我找到了关于将结果作为集合(多行)的相关信息。但是,测试函数时,结果并不是按列显示的,我只得到一个包含(val,val,val)的记录。
请问是否有人能够为我提供相关信息,最好是一个具有这种基本类型的简单示例函数?
谢谢!在PostgreSQL文档中,我尝试查找相关信息,但是并不清晰,特别是在涉及表格作为输入参数和以材料化模式返回时。
英文:
I need to write an extension function in C for PostgreSQL that:
- Has as argument a table of 3 columns of type double
 - Has as result a table of 3 columns of type double
 
Note that the mode should ideally be in materialize mode, since the function will generate a new table by transforming the data on the entire input table.
I did find information related to having the result as sets (multiple rows). But the test of the function does not result into the values as columns - I just get a record with (val,val,val).
Can anyone please direct me to information or preferably a simple example function that has this basic typology?
Thank you!
I tried to find information in the PostgreSQL documentation but it is not clear, specially when talking about tables as input arguments and returns in materialize mode.
答案1
得分: 1
以下是您要求的翻译部分:
根据LaurenzAlbe的建议,我创建了类似以下的东西。
请注意,我没有确切找到如何将表作为函数的输入接收的方法。这里的想法是将其封装成一个SQL函数,以便它可以创建一个临时表,填充这个临时表,然后将这个临时表的名称传递给C函数。
对于我的应用程序来说,这已经足够了。但如果有其他人愿意帮助,我很乐意讨论和改进。
谢谢!
接下来,要将此扩展链接到您的PostgreSQL中,您可以使用以下代码:
删除函数(如果存在):
DROP FUNCTION IF EXISTS public.add_table(oid);
创建或替换函数:
CREATE OR REPLACE FUNCTION public.add_table(
    oid,
    OUT x real,
    OUT y real,
    OUT z real)
    RETURNS SETOF record 
    LANGUAGE 'c'
    COST 1
    IMMUTABLE STRICT PARALLEL UNSAFE
    ROWS 1000
AS '...路径.../<dll名称>.dll', 'add_table'
;
ALTER FUNCTION public.add_table(oid)
    OWNER TO postgres;
要调用该函数:
-- 表包含3个实数列
SELECT * FROM add_table('test_table'::regclass);
这将返回一个所有值都增加了的表。
英文:
Following the suggestion from LaurenzAlbe, I created something like the following.
Note that I did not exactly find how to receive a table as input for a function. The idea here would be to wrap this into a SQL function so that it would create a temp table, fill this temp table, and then pass the name of this temp table to the C function.
For my application, this is enough. But I would love to debate this and improve if anyone else would be willing to help.
Thanks,
#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"
#include "funcapi.h"
#include "executor/executor.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/tuplestore.h"
#include "catalog/pg_type.h"
#include "executor/spi.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "miscadmin.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PGDLLEXPORT Datum add_table(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(add_table);
Datum add_table(PG_FUNCTION_ARGS) {
	ReturnSetInfo* rsinfo = (ReturnSetInfo*)fcinfo->resultinfo;
	/* check to see if caller supports us returning a tuplestore */
	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
		ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set")));
	if (!(rsinfo->allowedModes & SFRM_Materialize))
		ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("materialize mode required, but it is not allowed in this context")));
	/* The name of the table must be given as string 'tablename'::regclass */
	char* inputTableOid = PG_GETARG_OID(0);
	/* Executes the select statement to get input table */
	StringInfoData commandBuffer;
	initStringInfo(&commandBuffer);
	appendStringInfo(&commandBuffer, "SELECT * FROM %s", get_rel_name(inputTableOid));
	/* Starts SPI */
	int connRes = SPI_connect();
	if (connRes != SPI_OK_CONNECT) {
		elog(ERROR, "SPI_connect failed: %d", connRes);
	}
	int selectRes = SPI_exec(commandBuffer.data, 0);
	if (selectRes != SPI_OK_SELECT) {
		elog(ERROR, "SELECT failed: %d", selectRes);
	}
	/* Saves the select results */
	SPITupleTable* inputTupTable = SPI_tuptable; // The pointer to the input table - copies the global var that is filled by SPI_exec
	int inputNRows = SPI_processed; // The actual number of returned rows - copies the global var that is filled by SPI_exec
	TupleDesc inputTupleDesc = inputTupTable->tupdesc; // The TupleDesc of the input table
	int natts = inputTupleDesc->natts; // The number of attributes in the input tuple (cols)
	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
	MemoryContext oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
	/* Makes the output TupleDesc */
	TupleDesc tupdesc = CreateTemplateTupleDesc(3);
	TupleDescInitEntry(tupdesc, (AttrNumber)1, "x", FLOAT4OID, -1, 0);
	TupleDescInitEntry(tupdesc, (AttrNumber)2, "y", FLOAT4OID, -1, 0);
	TupleDescInitEntry(tupdesc, (AttrNumber)3, "z", FLOAT4OID, -1, 0);
	tupdesc = BlessTupleDesc(tupdesc);
	/* Checks if random access is allowed */
	bool randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
	/* Starts the tuplestore */
	Tuplestorestate* tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
	/* Set the output */
	rsinfo->returnMode = SFRM_Materialize;
	rsinfo->setResult = tupstore;
	rsinfo->setDesc = tupdesc;
	/* Returns to the old context */
	MemoryContextSwitchTo(oldcontext);
	/* Fills the output table */
	for (size_t i = 0; i < inputNRows; i++) {
		/* Getting the values of the current row */
		HeapTuple inputTuple = inputTupTable->vals[i]; // Gets the current input tuple (row)
		Datum* inputValues = palloc(natts * sizeof(Datum));
		bool* inputNulls = palloc(natts * sizeof(bool));
		heap_deform_tuple(inputTuple, inputTupleDesc, inputValues, inputNulls); // Gets the value of the current row
		/* Adds 1 to each column of the input row */
		Datum* outputValues = palloc(natts * sizeof(Datum));
		for (size_t j = 0; j < natts; j++) {
			outputValues[j] = Float4GetDatum(DatumGetFloat4(inputValues[j]) + 1);
		}
		/* Builds the output tuple (row) */
		HeapTuple outputTuple = heap_form_tuple(tupdesc, outputValues, inputNulls);
		/* Puts in the output tuplestore */
		tuplestore_puttuple(tupstore, outputTuple);
	}
	return (Datum)0;
}
Then, to add link this extension into your postgresql, you use the following code:
-- FUNCTION: public.add_table(oid)
DROP FUNCTION IF EXISTS public.add_table(oid);
CREATE OR REPLACE FUNCTION public.add_table(
	oid,
	OUT x real,
	OUT y real,
	OUT z real)
    RETURNS SETOF record 
    LANGUAGE 'c'
    COST 1
    IMMUTABLE STRICT PARALLEL UNSAFE
    ROWS 1000
AS '...Path.../<dllName>.dll', 'add_table'
;
ALTER FUNCTION public.add_table(oid)
    OWNER TO postgres;
To call the function:
-- The table contains 3 real columns
select * from add_table('test_table'::regclass);
This will return the table where all values are incremented.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论