Reputation: 859
I am trying to understand how to write C functions to create extensions for PostgreSQl but so far I am lost.
I want to write a very basic function that takes an integer and increments it 10 times. I am planning to use this as an example for Set Returning Functions and data persistence across calls using memory contexts. My problem here is data persistence between calls: the function is called once for each result and the memory is flushed between each calls, meaning that my integer disappears and the final result is incorrect.
Here is what I wrote so far:
/**
* Function that returns a set of integers (argument +10, step 1)
* Arguments:
* - int32 i = the original number to increment
*/
PG_FUNCTION_INFO_V1(addTen);
Datum addTen(PG_FUNCTION_ARGS) {
int32 i;
FuncCallContext *funcctx;
int call_cntr;
int max_calls;
// Code executed only on first call of the function
if (SRF_IS_FIRSTCALL()) {
MemoryContext oldcontext;
// Initializing the function context for cross call persistence
funcctx = SRF_FIRSTCALL_INIT();
// Context memory for multi calls
oldcontext = MemoryContextSwitchTo(funcctx -> multi_call_memory_ctx);
// Getting argument (original integer)
if (PG_ARGISNULL(0))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Table cannot be NULL")));
i = (int32) palloc(sizeof(int32));
i = PG_GETARG_INT32(0);
// Alloacting space to save the integer and make it persistent between calls
funcctx->user_fctx = &i;
// Maximum number of calls
funcctx -> max_calls = 10;
MemoryContextSwitchTo(oldcontext);
}
// Code executed on each call (first one included)
// Retrieving values from function context
funcctx = SRF_PERCALL_SETUP();
call_cntr = funcctx->call_cntr;
max_calls = funcctx -> max_calls;
if (call_cntr < max_calls) {
int32* temp = funcctx->user_fctx;
i = *temp;
SRF_RETURN_NEXT(funcctx, i + call_cntr);
} else { // Done
SRF_RETURN_DONE(funcctx);
}
}
As you can see, this is a very silly function. I could simply use PG_GETARG_INT32(0) on each call and voila, it would work. But I really want to understand how I am supposed to keep data between calls and this simple example seems to be a good way to do it.
What I tried here was to use the user_fctx field of the function context to get my integer back on each call. The problem is that it is a pointer, and the integer it points is erased between calls. How should I tell Postgres not to erase my integer, or where should I store it ?
Upvotes: 2
Views: 538
Reputation: 61506
For reference: C-Language Functions in PostgreSQL documentation.
i = (int32) palloc(sizeof(int32));
palloc() returns a pointer, not an integer. It's a bug to assign its return value to an integer.
i = PG_GETARG_INT32(0);
It's correct per se, but it overrides the value previously put into i
, which is then definitely lost.
// Alloacting space to save the integer and make it persistent between calls
funcctx->user_fctx = &i;
i
is a local variable that doesn't persist across calls. Storing its address to reuse it in a subsequent function call is a bug.
To allocate the space to keep the int32 value across calls, you want something like this:
funcctx->user_fctx = (void*)palloc(sizeof(int32));
funcctx->user_fctx
is of type void*
, per documentation, so you'll need to cast it to the type of what it points to, at each use.
To assign it, knowing that funcctx->user_fctx
has been allocated for an int32 variable:
*((int32*)funcctx->user_fctx) = PG_GETARG_INT32(0);
It can also be read with the same syntax:
i = *((int32*)funcctx->user_fctxt);
The way you wrote it in two steps is also correct and equivalent to the previous line:
int32* temp = funcctx->user_fctx;
i = *temp;
Upvotes: 2