Creating a PostgreSQL procedural language - Part 4 - Handling Input Parameters

March 05, 2020
Creating a PostgreSQL procedural language - Part 4 - Handling Input Parameters

One method to handle input parameters with PL/Julia is to rewrite the body of the user defined function or stored procedure on the fly with the values declared as global variables.

In order to do that, we need to create a new buffer big enough to hold the new global variable declarations in addition to the original body of the function or procedure.  Again, I’ll omit various error checks to keep the examples short but the whole of the changes are on GitHub.

The first thing we need to determine is how big the buffer will be.  It is simply the sum of the current body of code, plus the length of the input variable names, plus the string length of the values, plus all the whitespace and assignment operators.

    int compiled_len = 0;
    /*
     * Add the final carriage return to the length of the original
     * procedure.
     */
    compiled_len += strlen(procedure_code) + 1;

This next bit of code queries the system to determine how many input variables there are.

    FmgrInfo *arg_out_func;
    volatile MemoryContext proc_cxt = NULL;
    Oid *argtypes;
    char **argnames;
    char *argmodes;

    arg_out_func = (FmgrInfo *) palloc0(fcinfo->nargs * sizeof(FmgrInfo));
    proc_cxt = AllocSetContextCreate(TopMemoryContext,
            "PL/Julia function", 0, (1 * 1024), (8 * 1024));
    get_func_arg_info(procedure_tuple, &argtypes, &argnames, &argmodes);

Then we can look up the the details of each input parameter to get its name and its value, and then continue to sum up the size needed for our new buffer.

    char *value;

    for (i = 0; i < fcinfo->nargs; i ++)
    {
        Oid argtype = procedure_struct->proargtypes.values[i];
        type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));

        type_struct = (Form_pg_type) GETSTRUCT(type_tuple);
        fmgr_info_cxt(type_struct->typoutput, &(arg_out_func[i]), proc_cxt);

        value = OutputFunctionCall(&arg_out_func[i], fcinfo->args[i].value);

        /* Factor in length of an equal sign (=) and a line break (\n). */
        compiled_len += strlen(argnames[i]) + strlen(value) + 2;
    }

Once we are finished calculating the size of our new buffer, we will allocate the space in a variable named compiled_code.  The input parameters will be copied into this buffer as global variable declarations followed by the original body of the PL/Julia function.

    char *compiled_code;

    compiled_code = (char *) palloc0(compiled_len * sizeof(char));

    compiled_code[0] = '\0';
    for (i = 0; i < fcinfo->nargs; i ++)
    {
            strcat(compiled_code, argnames[i]);
            strcat(compiled_code, "=");
            value = OutputFunctionCall(&arg_out_func[i], fcinfo->args[i].value);
            strcat(compiled_code, value);
            strcat(compiled_code, "\n");
    }
    strcat(compiled_code, procedure_code);

The variable compiled_code can now be passed to Julia to execute and we can write a more useful square root function that can process its input parameter:

CREATE OR REPLACE FUNCTION test_julia(val integer)
RETURNS VOID
AS $$
    sqrt(val);
$$ LANGUAGE pljulia;

Now we can run queries like SELECT test_julia(8)!

Share this