Re: Command Triggers, patch v11

From: Thom Brown <thom(at)linux(dot)com>
To: Dimitri Fontaine <dimitri(at)2ndquadrant(dot)fr>
Cc: pgsql-hackers(at)postgresql(dot)org, Andres Freund <andres(at)anarazel(dot)de>, Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Subject: Re: Command Triggers, patch v11
Date: 2012-03-05 00:33:11
Message-ID: CAA-aLv66UQ3mqsk96AUhc9zqw8hwEPdCrGHv81+F3=puSiw5Jg@mail.gmail.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On 4 March 2012 15:50, Andres Freund <andres(at)anarazel(dot)de> wrote:
> Ok, I rebased my patch ontop of dim's current HEAD. There was only one trivial
> conflict in tablecmds.h. I had written the patch independently of the command
> triggers stuff because I though, and still do, that would make applying it
> easier.
>
> Attached are two versions of the patch, one based on command triggers and one
> without. Both pass regression tests for me.

I have conducted testing against Dimitri’s latest patch, along with
the incremental patch to fix the build, and also Andres’ CTAS patch.
I've attached a copy of how I configured the command triggers
(command_trigger_test_setup.txt), and also the set of tests I've been
running against the changes (command_trigger_regression.sql). I've
left comments in that last file where I haven't been able to conduct a
test for particular commands.

Creating a command trigger using ANY COMMAND results in oid,
schemaname, objectname (function parameters 4 & 5) not being set for
either BEFORE or AFTER.

There is no support for ALTER CONVERSION.

When trying to create an AFTER command trigger on CREATE INDEX, I get
the warning:

WARNING: CREATE INDEX CONCURRENTLY is not supported
DETAIL: The command trigger will not get fired.

This should probably say that it’s not supported on AFTER command
triggers yet rather than the general DDL itself.

Command triggers for AFTER creating rules don’t return OIDs.

thom(at)test=# CREATE RULE "_RETURN" AS
ON SELECT TO test2
DO INSTEAD
SELECT 234 id, 'test'::text stuff;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='CREATE
RULE' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='CREATE RULE'
objectid=<NULL> schemaname='public' objectname='_RETURN'
NOTICE: Command trigger: tg_when='AFTER' cmd_tag='CREATE RULE'
objectid=<NULL> schemaname='public' objectname='_RETURN'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='CREATE RULE'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
CREATE RULE

Command triggers for creating sequences don’t show the schema:

thom(at)test=# CREATE TEMP SEQUENCE test_seq2;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='CREATE
SEQUENCE' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='CREATE SEQUENCE'
objectid=<NULL> schemaname='<NULL>' objectname='test_seq2'
NOTICE: Command trigger: tg_when='AFTER' cmd_tag='CREATE SEQUENCE'
objectid=25130 schemaname='<NULL>' objectname='test_seq2'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='CREATE
SEQUENCE' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
CREATE SEQUENCE

Command triggers for AFTER creating extensions with IF NOT EXISTS
don’t fire, but do in the ANY COMMAND instance:

thom(at)test=# CREATE EXTENSION IF NOT EXISTS file_fdw;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='CREATE
EXTENSION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='CREATE EXTENSION'
objectid=<NULL> schemaname='<NULL>' objectname='file_fdw'
NOTICE: extension "file_fdw" already exists, skipping
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='CREATE
EXTENSION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
CREATE EXTENSION

Command triggers on CREATE TEXT SEARCH DICTIONARY show the name as garbage:

thom(at)test=# CREATE TEXT SEARCH DICTIONARY test_stem (
test(# TEMPLATE = snowball,
test(# language = 'english', stopwords = 'english'
test(# );
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='CREATE TEXT
SEARCH DICTIONARY' objectid=<NULL> schemaname='<NULL>'
objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='CREATE TEXT SEARCH
DICTIONARY' objectid=<NULL> schemaname='thom' objectname='�Ч�l '
NOTICE: Command trigger: tg_when='AFTER' cmd_tag='CREATE TEXT SEARCH
DICTIONARY' objectid=25139 schemaname='thom' objectname='�Ч�l '
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='CREATE TEXT
SEARCH DICTIONARY' objectid=<NULL> schemaname='<NULL>'
objectname='<NULL>'
CREATE TEXT SEARCH DICTIONARY

Command triggers for BEFORE CREATE TYPE (exluding ANY COMMAND) don’t
fire if the type isn’t created due to an error:

thom(at)test=# CREATE TYPE thom.type_test AS
(a integer,
b integer,
c text);
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='CREATE
TYPE' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ERROR: type "type_test" already exists

The ANY COMMAND trigger fires on creating roles, but there’s no
corresponding allowance to create the trigger explicitly for creating
roles.

Command triggers for AFTER CREATE VIEW don’t show the schema:

thom(at)test=# CREATE VIEW view_test AS SELECT id, stuff FROM public.test;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='CREATE
VIEW' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='CREATE VIEW'
objectid=<NULL> schemaname='<NULL>' objectname='view_test'
NOTICE: Command trigger: tg_when='AFTER' cmd_tag='CREATE VIEW'
objectid=25155 schemaname='<NULL>' objectname='view_test'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='CREATE VIEW'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
CREATE VIEW

Command triggers for BEFORE and AFTER ALTER DOMAIN show a garbage name
and no schema when dropping a constraint:

thom(at)test=# ALTER DOMAIN us_postal_code DROP CONSTRAINT dummy_constraint;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
DOMAIN' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='ALTER DOMAIN'
objectid=25085 schemaname='<NULL>' objectname='�'
NOTICE: Command trigger: tg_when='AFTER' cmd_tag='ALTER DOMAIN'
objectid=25085 schemaname='<NULL>' objectname='�'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER
DOMAIN' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER DOMAIN

Continuing with this same trigger, we do get a schema but a garbage
name for OWNER TO:

thom(at)test=# ALTER DOMAIN us_postal_code OWNER TO test;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
DOMAIN' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='ALTER DOMAIN'
objectid=25085 schemaname='public' objectname='�'
NOTICE: Command trigger: tg_when='AFTER' cmd_tag='ALTER DOMAIN'
objectid=25085 schemaname='public' objectname='�'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER
DOMAIN' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER DOMAIN

When an ALTER EXTENSION fails to upgrade, the AFTER ANY COMMAND
trigger fires, but not command triggers specifically for ALTER
EXTENSION:

thom(at)test=# ALTER EXTENSION file_fdw UPDATE TO '1.0';
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
EXTENSION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='ALTER EXTENSION'
objectid=25087 schemaname='<NULL>' objectname='file_fdw'
NOTICE: version "1.0" of extension "file_fdw" is already installed
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER
EXTENSION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER EXTENSION

Same on ALTER EXTENSION, when failing to add a member, the BEFORE ANY
COMMAND trigger fires, but not the one specifically for ALTER
EXTENSION:

thom(at)test=# ALTER EXTENSION file_fdw ADD COLLATION en_gb_test;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
EXTENSION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ERROR: collation "en_gb_test" for encoding "UTF8" does not exist

Specific command triggers against ALTER FOREIGN TABLE (i.e. not ANY
COMMAND) for BEFORE and AFTER aren’t working when renaming columns:

thom(at)test=# ALTER FOREIGN TABLE test.dict2 RENAME COLUMN word TO words;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
TABLE' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER TABLE'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER TABLE

Specific command triggers agains ALTER FUNCTION (i.e. not ANY COMMAND)
don’t fire for any changes except renaming, changing owner or changing
schema. Everything else fails to trigger (cost, rows, setting
configuration parameters, setting strict, security invoker etc.).:

thom(at)test=# ALTER FUNCTION test.testfunc2() COST 77;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
FUNCTION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER
FUNCTION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER FUNCTION
thom(at)test=# ALTER FUNCTION srf_test() ROWS 5;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
FUNCTION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER
FUNCTION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER FUNCTION
thom(at)test=# ALTER FUNCTION srf_test() RESET ALL;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
FUNCTION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER
FUNCTION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER FUNCTION
thom(at)test=# ALTER FUNCTION srf_test() SET work_mem TO '1MB';
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
FUNCTION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER
FUNCTION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER FUNCTION

There doesn’t appear to be command trigger support for ALTER LARGE OBJECT.

Specific command triggers on ALTER SEQUENCE don’t fire:

thom(at)test=# ALTER SEQUENCE test_seq OWNER TO test;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
SEQUENCE' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER
SEQUENCE' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER SEQUENCE

Specific command triggers on ALTER TABLE don’t fire for renaming columns:

thom(at)test=# ALTER TABLE testnew.test9 RENAME COLUMN stuff TO things;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
TABLE' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER TABLE'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER TABLE

Command triggers on ALTER TYPE when changing owner produce a garbage name:

thom(at)test=# ALTER TYPE testnew.type_test OWNER TO test;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER TYPE'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='ALTER TYPE'
objectid=25170 schemaname='testnew' objectname='�'
NOTICE: Command trigger: tg_when='AFTER' cmd_tag='ALTER TYPE'
objectid=25170 schemaname='testnew' objectname='�'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER TYPE'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER TYPE

Also renaming attributes doesn’t fire specific triggers:

thom(at)test=# ALTER TYPE public.type_test2 RENAME ATTRIBUTE a TO z;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER TYPE'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER TYPE'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER TYPE

Specific command triggers on ALTER VIEW don’t fire for any type of change:

thom(at)test=# ALTER VIEW view_test OWNER TO test;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER VIEW'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER VIEW'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER VIEW
thom(at)test=# ALTER VIEW testnew.view_test2 ALTER COLUMN id SET DEFAULT 9;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER VIEW'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER VIEW'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER VIEW

Specific command triggers on DROP AGGREGATE don’t fire in the IF
EXISTS scenario if the target object doesn’t exist:

thom(at)test=# DROP AGGREGATE IF EXISTS avgtest2(bigint);
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='DROP
AGGREGATE' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: aggregate avgtest2(pg_catalog.int8) does not exist, skipping
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='DROP
AGGREGATE' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
DROP AGGREGATE

This appears to be a general thing, as the same occurs for DROP CAST
IF EXISTS and DROP COLLATION IF EXISTS. These do, however, fire if
the object does exist.

When adding objects to an extension, then dropping the extension with
a cascade, the objects are dropped with it, but triggers aren’t fired
to the removal of those dependant objects:

thom(at)test=# ALTER EXTENSION file_fdw ADD TABLE dep_test;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='ALTER
EXTENSION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='ALTER EXTENSION'
objectid=25207 schemaname='<NULL>' objectname='file_fdw'
NOTICE: Command trigger: tg_when='AFTER' cmd_tag='ALTER EXTENSION'
objectid=25207 schemaname='<NULL>' objectname='file_fdw'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='ALTER
EXTENSION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
ALTER EXTENSION
thom(at)test=# DROP EXTENSION file_fdw CASCADE;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='DROP
EXTENSION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='DROP EXTENSION'
objectid=25207 schemaname='<NULL>' objectname='file_fdw'
NOTICE: Command trigger: tg_when='AFTER' cmd_tag='DROP EXTENSION'
objectid=<NULL> schemaname='<NULL>' objectname='file_fdw'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='DROP
EXTENSION' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
DROP EXTENSION

Using DROP OWNED BY allows objects to be dropped without their
respective specific triggers firing.

thom(at)test=# \dt
List of relations
Schema | Name | Type | Owner
--------+-----------------+-------+-----------
thom | role_test_table | table | role_test
thom | seq_table | table | test
(2 rows)

thom(at)test=# DROP OWNED BY role_test;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='DROP OWNED'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='DROP OWNED'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
DROP OWNED

Using DROP SCHEMA … CASACDE also allows objects to be dropped without
their respective specific triggers firing:

thom(at)test=# DROP SCHEMA test6 CASCADE;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='DROP
SCHEMA' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='DROP SCHEMA'
objectid=25250 schemaname='<NULL>' objectname='test6'
NOTICE: drop cascades to table test6.test
NOTICE: Command trigger: tg_when='AFTER' cmd_tag='DROP SCHEMA'
objectid=<NULL> schemaname='<NULL>' objectname='test6'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='DROP SCHEMA'
objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
DROP SCHEMA

Command triggers on all DROP commands for TEXT SEARCH
CONFIGURATION/DICTIONARY/PARSER/TEMPLATE show the schema name as the
relation name:

thom(at)test=# DROP TEXT SEARCH PARSER testnew.test_parser2;
NOTICE: Command trigger on any: tg_when='BEFORE' cmd_tag='DROP TEXT
SEARCH PARSER' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
NOTICE: Command trigger: tg_when='BEFORE' cmd_tag='DROP TEXT SEARCH
PARSER' objectid=25265 schemaname='testnew' objectname='testnew'
NOTICE: Command trigger: tg_when='AFTER' cmd_tag='DROP TEXT SEARCH
PARSER' objectid=<NULL> schemaname='testnew' objectname='testnew'
NOTICE: Command trigger on any: tg_when='AFTER' cmd_tag='DROP TEXT
SEARCH PARSER' objectid=<NULL> schemaname='<NULL>' objectname='<NULL>'
DROP TEXT SEARCH PARSER

Still no command triggers firing for CREATE TABLE AS:

thom(at)test=# CREATE TABLE ctas_test AS SELECT 1::int id, ''::text test;
CREATE TABLE AS

Or for SELECT * INTO... :

thom(at)test=# SELECT * INTO another_test FROM ctas_test;
SELECT INTO

Regards

--
Thom

Attachment Content-Type Size
command_trigger_test_setup.txt text/plain 17.6 KB
command_trigger_regression.sql text/x-sql 15.2 KB

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Robert Haas 2012-03-05 01:53:53 Re: Parameterized-path cost comparisons need some work
Previous Message Tom Lane 2012-03-04 21:11:24 Re: Collect frequency statistics for arrays