Inserting NEW.* from a generic trigger using EXECUTE in PL/pgsql

后端 未结 3 1905
醉酒成梦
醉酒成梦 2020-12-17 14:40

I have a number of tables that use the Postgres \"Partitioning\" feature. I want to define a common BEFORE INSERT OF ROW trigger on each table that will 1) dynamically crea

相关标签:
3条回答
  • 2020-12-17 15:20

    I've managed to get this to work by dynamically compiling a function that accepts the NEW row as a parameter:

        EXECUTE 'create or replace function partition_insert(r ' || TG_TABLE_NAME || ') RETURNS void AS $FUNC$' || 
                'BEGIN ' ||
                    'insert into ' || TG_TABLE_NAME || ' SELECT r.*; ' ||
                'END $FUNC$ LANGUAGE plpgsql VOLATILE';
        PERFORM partition_insert(NEW);
    

    As Postgres functions are polymorphic, this will generate a different function for each table that uses this trigger.

    Despite being an ugly kludge, this seems to do the job.

    Although it looks like I could define each polymorphic variation up front when I build the system, because of caching, I must recompile the function whenever I create or drop a child table so that the function uses the latest insert RULE.

    EDIT: Additional wrinkles
    There's a little gotcha with this technique: If this EXECUTE/PERFORM action is rolled-back on the first attempt due to another error (for example, in my case a CHECK constraint failure) then the function containing this code seems to cache a reference to the rolled-back partition_insert() function it created using the EXECUTE and subsequent calls fail due to a cached object not being found.

    I resolved this by pre-creating stub versions of the function for each required table-type parameter when I define the database.

    0 讨论(0)
  • 2020-12-17 15:21

    You can use EXECUTE USING to pass NEW to it. Your example would be

    EXECUTE 'INSERT INTO ' || TG_RELID || '::regclass SELECT $1' USING NEW;
    

    (Note that I use TG_RELID casted to regclass instead of fiddling with TG_TABLE_SCHEMA and TABLE_NAME because it is easier to use, if nonstandard. But then, plpgsql is nonstandard anyway.)

    0 讨论(0)
  • 2020-12-17 15:33

    Yes, you can use EXECUTE ... USING in 8.4. For example:

    EXECUTE 'INSERT INTO ' || table_name || ' SELECT $1.*' USING NEW;

    In lower versions (I've only tested in 8.3), you can use:

    EXECUTE 'INSERT INTO ' || table_name ||
        ' SELECT (' || quote_literal(NEW) || '::' || TG_RELID::regclass || ').*';
    
    0 讨论(0)
提交回复
热议问题