Gaëtan
Gaëtan

Reputation: 859

PostgreSQL C extension: persistent data between function calls

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

Answers (1)

Daniel V&#233;rit&#233;
Daniel V&#233;rit&#233;

Reputation: 61506

For reference: C-Language Functions in PostgreSQL documentation.

Problems in your function

 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.

Solutions

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

Related Questions