sqlpostgresqlplpgsqlpostgresql-15

Using a script variable to generate a stored procedure in Postgresql


I would like to use a script variable to set the data type, so that I can generate a few functions with different types, like so:

\set bucket_data_type DATE

Then run a script to generate this function:

CREATE OR REPLACE FUNCTION insert(
    ids BIGINT[],
    types TEXT[],
    buckets :bucket_data_type[]
) RETURNS VOID
LANGUAGE PLPGSQL
AS $$
BEGIN
   EXECUTE 
    FORMAT('INSERT INTO myTable(id, type, buckets)
    SELECT _.id, _.type, _.bucket
    FROM(
        SELECT unnest(%L::bigint[]) AS monitor_id,
               unnest(%L::text[]) AS feature_type,
               unnest(%L::'|| :bucket_data_type ||'[]) AS bucket
        ) _
        ON CONFLICT DO NOTHING;',
           ids, types, buckets);
END
$$;

and then again with \set bucket_data_type TIMESTAMP, for example.

It works for the parameter definitions, but not the SQL inside the FORMAT block.

I am expecting something like

\set bucket_data_type DATE

<run create script as per above>

to return

CREATE OR REPLACE FUNCTION insert(
    ids BIGINT[],
    types TEXT[],
    buckets DATE[]
) RETURNS VOID
LANGUAGE PLPGSQL
AS $$
BEGIN
   EXECUTE 
    FORMAT('INSERT INTO myTable(id, type, buckets)
    SELECT _.id, _.type, _.bucket
    FROM(
        SELECT unnest(%L::bigint[]) AS monitor_id,
               unnest(%L::text[]) AS feature_type,
               unnest(%L::DATE[]) AS bucket
        ) _
        ON CONFLICT DO NOTHING;',
           ids, types, buckets);
END
$$;

but I'm unable to get unnest(%L::DATE[]) AS bucket to appear.

I either get format errors like:

ERROR:  syntax error at or near ":"
LINE 15:                unnest(%L::'|| :bucket_data_type ||'[]) AS bu...

or I simply get :bucket_data_type as a string inside the execute block.

I've tried SELECT set_config('tsp.bucket_data_type', :'bucket_data_type', false); as well (as this works in DO BLOCKS where things are string literals).

Is this even possible?


Solution

  • Here is a solution that works with psql:

    \set bucket_data_type date
    
    SELECT 'CREATE OR REPLACE FUNCTION insert(
        ids BIGINT[],
        types TEXT[],
        buckets ' || :'bucket_data_type' || '[]
    ) RETURNS VOID
    LANGUAGE PLPGSQL
    AS $$
    BEGIN
       EXECUTE 
        FORMAT(''INSERT INTO myTable(id, type, buckets)
        SELECT _.id, _.type, _.bucket
        FROM(
            SELECT unnest(%L::bigint[]) AS monitor_id,
                   unnest(%L::text[]) AS feature_type,
                   unnest(%L::'|| :'bucket_data_type' ||'[]) AS bucket
            ) _
            ON CONFLICT DO NOTHING;'',
               ids, types, buckets);
    END
    $$' \gexec
    

    The SELECT statement composes the statement that creates the function, and \gexec executes the statement.