英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论