Thread: Set Returning C-Function with cache over multiple calls (with different arguments)

Set Returning C-Function with cache over multiple calls (with different arguments)

From
Thilo Schneider
Date:
Dear list,

Currently I am working on a user C-Function which should create a cache object on the first call and afterwards return
aset of computed values for each argument combination it is called with. 

My Problem is how to get the cache object saved over multiple calls. Without the SRF I could use
fcinfo->flinfo->fn_extrafor my pointer to the data. This is now used by the FuncCallContext structure. This structure
isdestroyed every time SRF_RETURN_DONE is called, thus user_fctx also is not the way to go. 

As a minimal example look at the function provided below.

--------------------------------------------------- snip ---------------------------------------------------
PG_FUNCTION_INFO_V1(test);
Datum test(PG_FUNCTION_ARGS)
{
    MemoryContext old_context;
    FuncCallContext     *funcctx;


    if (SRF_IS_FIRSTCALL()) {
        funcctx = SRF_FIRSTCALL_INIT();

        // This is the structure potentially generated in previous calls
        str = funcctx->user_fctx;

        // If the structure does not exist or the geometry array has changed, it has to be created.
        if ( ! str) {
            elog(NOTICE, "create new");

            old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);

            // Fill str with data around here ...

            MemoryContextSwitchTo(old_context);

            funcctx->user_fctx = str;
        }

    }
    funcctx = SRF_PERCALL_SETUP();
    SRF_RETURN_DONE(funcctx);
}
--------------------------------------------------- snip ---------------------------------------------------

To make the problem perfectly clear the SQL-Code this should work with:

--------------------------------------------------- snip ---------------------------------------------------
CREATE OR REPLACE FUNCTION test(int, int[] )
  RETURNS SETOF int AS 'myfunc', 'test'
  LANGUAGE 'c' IMMUTABLE STRICT
  COST 1;

SELECT test(number, array(SELECT integers FROM another_table)) FROM numbers;
--------------------------------------------------- snip ---------------------------------------------------

As creating the cache object is by far the most expensive part in the desired function, it should be possible to create
thecache only once over the whole query - using only the arguments in the array, which do not change over multiple
calls.

Is there a way around this problem? Another pointer I could use and do not know of yet?

Thanks in advance,
Thilo Schneider

On Mon, Jan 11, 2010 at 2:45 AM, Thilo Schneider
<Thilo.Schneider@math.uni-giessen.de> wrote:
> Dear list,
>
> Currently I am working on a user C-Function which should create a cache object on the first call and afterwards
returna set of computed values for each argument combination it is called with. 
>
> My Problem is how to get the cache object saved over multiple calls. Without the SRF I could use
fcinfo->flinfo->fn_extrafor my pointer to the data. This is now used by the FuncCallContext structure. This structure
isdestroyed every time SRF_RETURN_DONE is called, thus user_fctx also is not the way to go. 
>
> As a minimal example look at the function provided below.
>
> --------------------------------------------------- snip ---------------------------------------------------
> PG_FUNCTION_INFO_V1(test);
> Datum test(PG_FUNCTION_ARGS)
> {
>        MemoryContext old_context;
>        FuncCallContext     *funcctx;
>
>
>        if (SRF_IS_FIRSTCALL()) {
>                funcctx = SRF_FIRSTCALL_INIT();
>
>                // This is the structure potentially generated in previous calls
>                str = funcctx->user_fctx;
>
>                // If the structure does not exist or the geometry array has changed, it has to be created.
>                if ( ! str) {
>                        elog(NOTICE, "create new");
>
>                        old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
>
>                        // Fill str with data around here ...
>
>                        MemoryContextSwitchTo(old_context);
>
>                        funcctx->user_fctx = str;
>                }
>
>        }
>        funcctx = SRF_PERCALL_SETUP();
>        SRF_RETURN_DONE(funcctx);
> }
> --------------------------------------------------- snip ---------------------------------------------------
>
> To make the problem perfectly clear the SQL-Code this should work with:
>
> --------------------------------------------------- snip ---------------------------------------------------
> CREATE OR REPLACE FUNCTION test(int, int[] )
>  RETURNS SETOF int AS 'myfunc', 'test'
>  LANGUAGE 'c' IMMUTABLE STRICT
>  COST 1;
>
> SELECT test(number, array(SELECT integers FROM another_table)) FROM numbers;
> --------------------------------------------------- snip ---------------------------------------------------
>
> As creating the cache object is by far the most expensive part in the desired function, it should be possible to
createthe cache only once over the whole query - using only the arguments in the array, which do not change over
multiplecalls. 
>
> Is there a way around this problem? Another pointer I could use and do not know of yet?

have you ruled out simply keeping a static pointer around and using malloc()?

merlin

Dear list,

I solved my own problem - as so often, once you write it down and press the send button you get the idea.

The problem was:

> Currently I am working on a user C-Function which should create a cache object on the first call and afterwards
returna set of computed values for each argument combination it is called with. 
>
> My Problem is how to get the cache object saved over multiple calls. Without the SRF I could use
fcinfo->flinfo->fn_extrafor my pointer to the data. This is now used by the FuncCallContext structure. This structure
isdestroyed every time SRF_RETURN_DONE is called, thus user_fctx also is not the way to go. 

My solution:

--------------------------------------------------- snip ---------------------------------------------------
struct myData {
    FuncCallContext *funcctx;
    // own Data
    int cachedObject;
} myData

PG_FUNCTION_INFO_V1(test);
Datum test(PG_FUNCTION_ARGS)
{
    MemoryContext old_context;
    FuncCallContext     *funcctx;
    myData *str;

    // Get fn_extra
    str = fcinfo->flinfo->fn_extra;
    if ( ! str) {
        elog(NOTICE, "create new");

        old_context = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
        // Fill str with data around here ...
        MemoryContextSwitchTo(old_context);
        str->funcctx = NULL;
    }

    // This is the situation the SRF-macros expect:
    fcinfo->flinfo->fn_extra = str->funcctx;

    if (SRF_IS_FIRSTCALL()) {
        funcctx = SRF_FIRSTCALL_INIT();

        // Your commands
    }
    funcctx = SRF_PERCALL_SETUP();

    // This is the macro SRF_RETURN_DONE(funcctx);
    // Before we finally return we save our str in fn_extra and fn_extra in str->funcctx.
    do {
        ReturnSetInfo *rsi;
        end_MultiFuncCall(fcinfo, funcctx);
        rsi = (ReturnSetInfo *) fcinfo->resultinfo;
        rsi->isDone = ExprEndResult;
        // -- Modify macro here --
        str->funcctx = fcinfo->flinfo->fn_extra;
        fcinfo->flinfo->fn_extra = str;
        // -- End modification --
        PG_RETURN_NULL();
    } while (0);

    // Of course, SRF_RETURN_DATUM has to be adapted the same way!
}
--------------------------------------------------- snip ---------------------------------------------------

Regards,
Thilo Schneider