2

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 ?

1 Answer 1

2

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;
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much ! I would hug you through the internet if I could !

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.