*** a/doc/src/sgml/catalogs.sgml --- b/doc/src/sgml/catalogs.sgml *************** *** 99,104 **** --- 99,109 ---- + pg_cmdtrigger + command triggers + + + pg_constraint check constraints, unique constraints, primary key constraints, foreign key constraints *************** *** 1858,1863 **** --- 1863,1944 ---- + + <structname>pg_cmdtrigger</structname> + + + pg_cmdtrigger + + + + The catalog pg_cmdtrigger stores command triggers. + + + + <structname>pg_cmdtrigger</> Columns + + + + + Name + Type + References + Description + + + + + + ctgname + name + + Trigger's name (unique) + + + + ctgcommand + name + + Command TAG of the command this trigger fires for. + + + + ctgfoid + oid + pg_proc.oid + + The OID of the function that is called by this command trigger. + + + + + contype + char + + + B = BEFORE + A = AFTER + + + + + ctgenabled + char + + + Controls in which modes + the command trigger fires. + O = trigger fires in origin and local modes, + D = trigger is disabled, + R = trigger fires in replica mode, + A = trigger fires always. + + + + +
+
+ <structname>pg_constraint</structname> *** a/doc/src/sgml/plperl.sgml --- b/doc/src/sgml/plperl.sgml *************** *** 634,640 **** SELECT init_hosts_query(); SELECT query_hosts('192.168.1.0/30'); SELECT release_hosts_query(); ! query_hosts ----------------- (1,192.168.1.1) (2,192.168.1.2) --- 634,640 ---- SELECT query_hosts('192.168.1.0/30'); SELECT release_hosts_query(); ! query_hosts ----------------- (1,192.168.1.1) (2,192.168.1.2) *************** *** 1026,1031 **** $$ LANGUAGE plperl; --- 1026,1039 ---- PL/Perl Triggers + + Trigger Procedures on Data Modification in PL/Perl + + + trigger + in PL/Perl + + PL/Perl can be used to write trigger functions. In a trigger function, the hash reference $_TD contains information about the *************** *** 1209,1214 **** CREATE TRIGGER test_valid_id_trig --- 1217,1297 ---- FOR EACH ROW EXECUTE PROCEDURE valid_id(); + + + + Trigger Procedures on Commands in PL/Perl + + + command trigger + in PL/Perl + + + + Command trigger procedures can be written in PL/Perl. + PostgreSQL requires that a procedure that is to be called + as a trigger must be declared as a function with no arguments + and a return type of command_trigger. + + + + The information from the trigger manager is passed to the procedure body + in the following variables: + + + + + $_TD->{when} + + + The timing of the event that fires the trigger, either the + string BEFORE or AFTER. + + + + + + $_TD->{tag} + + + The command tag for which the trigger is fired. + + + + + + $_TD->{objectid} + + + The object ID of the object that caused the trigger procedure + to be invoked. + + + + + + $_TD->{objectname} + + + The name of the objectthat caused the trigger procedure + to be invoked. + + + + + + $_TD->{schemaname} + + + The schema of the object that caused the trigger procedure to be + invoked, or NULL if the object is not qualified. + + + + + + + *** a/doc/src/sgml/plpgsql.sgml --- b/doc/src/sgml/plpgsql.sgml *************** *** 3377,3383 **** RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id; in PL/pgSQL ! PL/pgSQL can be used to define trigger procedures. A trigger procedure is created with the CREATE FUNCTION command, declaring it as a function with --- 3377,3386 ---- in PL/pgSQL ! ! Triggers on data change ! ! PL/pgSQL can be used to define trigger procedures. A trigger procedure is created with the CREATE FUNCTION command, declaring it as a function with *************** *** 3924,3929 **** UPDATE sales_fact SET units_sold = units_sold * 2; --- 3927,4035 ---- SELECT * FROM sales_summary_bytime; + + + + Triggers on commands + + + PL/pgSQL can be used to define command + trigger procedures. A command trigger procedure is created with the + CREATE FUNCTION command, declaring it as a function with + no arguments and a return type of command trigger. + + + + When a PL/pgSQL function is called as a + command trigger, several special variables are created automatically + in the top-level block. They are: + + + + TG_TAG + + + Data type text; variable that contains the command tag + for which the trigger is fired. + + + + + + TG_WHEN + + + Data type text; a string of + BEFORE or AFTER depending on + the trigger's definition. + + + + + + TG_OBJECTID + + + Data type oid; the object ID of the object that caused + the trigger invocation. NULL when the function is + called AFTER a drop command. + + + + + + TG_SCHEMANAME + + + Data type name; the name of the schema of the object + that caused the trigger invocation. Can be NULL + for objects not located in a schema. + + + + + + TG_OBJECTNAME + + + Data type name; the name of the object that caused the trigger + invocation. Can be NULL. + + + + + + + + The command trigger function's return value is not used. + + + + shows an example of a + command trigger procedure in PL/pgSQL. + + + + A <application>PL/pgSQL</application> Command Trigger Procedure + + + This example trigger simply raises a NOTICE message + each time a supported command is executed. + + + + CREATE OR REPLACE FUNCTION snitch() RETURNS command trigger AS $$ + BEGIN + RAISE NOTICE 'snitch: % % %.% [%]', + tg_when, tg_tag, tg_schemaname, tg_objectname, tg_objectid; + END; + $$ LANGUAGE plpgsql; + + CREATE COMMAND TRIGGER snitch_before BEFORE ANY COMMAND EXECUTE PROCEDURE snitch(); + CREATE COMMAND TRIGGER snitch_after AFTER ANY COMMAND EXECUTE PROCEDURE snitch(); + + + *** a/doc/src/sgml/plpython.sgml --- b/doc/src/sgml/plpython.sgml *************** *** 441,447 **** return (1, 2, 3, 4, 5) $$ LANGUAGE plpythonu; SELECT return_arr(); ! return_arr ------------- {1,2,3,4,5} (1 row) --- 441,447 ---- $$ LANGUAGE plpythonu; SELECT return_arr(); ! return_arr ------------- {1,2,3,4,5} (1 row) *************** *** 756,761 **** $$ LANGUAGE plpythonu; --- 756,769 ---- in PL/Python + + Trigger Procedures on Data Modification in PL/Python + + + trigger + in PL/Python + + When a function is used as a trigger, the dictionary TD contains trigger-related values: *************** *** 861,866 **** $$ LANGUAGE plpythonu; --- 869,948 ---- "MODIFY" to indicate you've modified the new row. Otherwise the return value is ignored. + + + + Trigger Procedures on Commands in PL/Python + + + command trigger + in PL/Python + + + + Command trigger procedures can be written in PL/Python. + PostgreSQL requires that a procedure that is to be called + as a trigger must be declared as a function with no arguments + and a return type of command_trigger. + + + + The information from the trigger manager is passed to the procedure body + in the following variables: + + + + + TD["when"] + + + The timing of the event that fires the trigger, either the + string BEFORE or AFTER. + + + + + + TD["tag"] + + + The command tag for which the trigger is fired. + + + + + + TD["objectid"] + + + The object ID of the object that caused the trigger procedure + to be invoked. + + + + + + TD["objectname"] + + + The name of the objectthat caused the trigger procedure + to be invoked. + + + + + + TD["schemaname"] + + + The schema of the object that caused the trigger procedure to be + invoked, or NULL if the object is not qualified. + + + + + + *** a/doc/src/sgml/pltcl.sgml --- b/doc/src/sgml/pltcl.sgml *************** *** 516,525 **** SELECT 'doesn''t' AS ret Trigger Procedures in PL/Tcl ! ! trigger ! in PL/Tcl ! Trigger procedures can be written in PL/Tcl. --- 516,529 ---- Trigger Procedures in PL/Tcl ! ! ! Trigger Procedures on Data Modification in PL/Tcl ! ! ! trigger ! in PL/Tcl ! Trigger procedures can be written in PL/Tcl. *************** *** 709,714 **** CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab --- 713,792 ---- name; that's supplied from the trigger arguments. This lets the trigger procedure be reused with different tables. + + + + Trigger Procedures on Commands in PL/Tcl + + + command trigger + in PL/Tcl + + + + Command trigger procedures can be written in PL/Tcl. + PostgreSQL requires that a procedure that is to be called + as a trigger must be declared as a function with no arguments + and a return type of command_trigger. + + + + The information from the trigger manager is passed to the procedure body + in the following variables: + + + + + $TG_when + + + The timing of the event that fires the trigger, either the + string BEFORE or AFTER. + + + + + + $TG_tag + + + The command tag for which the trigger is fired. + + + + + + $TG_objectid + + + The object ID of the object that caused the trigger procedure + to be invoked. + + + + + + $TG_objectname + + + The name of the objectthat caused the trigger procedure + to be invoked. + + + + + + $TG_schemaname + + + The schema of the object that caused the trigger procedure to be + invoked, or NULL if the object is not qualified. + + + + + + *** a/doc/src/sgml/ref/allfiles.sgml --- b/doc/src/sgml/ref/allfiles.sgml *************** *** 8,13 **** Complete list of usable sgml source files in this directory. --- 8,14 ---- + *************** *** 50,55 **** Complete list of usable sgml source files in this directory. --- 51,57 ---- + *************** *** 88,93 **** Complete list of usable sgml source files in this directory. --- 90,96 ---- + *** /dev/null --- b/doc/src/sgml/ref/alter_command_trigger.sgml *************** *** 0 **** --- 1,103 ---- + + + + + ALTER COMMAND TRIGGER + 7 + SQL - Language Statements + + + + ALTER COMMAND TRIGGER + change the definition of a trigger + + + + ALTER COMMAND TRIGGER + + + + + ALTER COMMAND TRIGGER name SET enabled + ALTER COMMAND TRIGGER name RENAME TO newname + + where enabled can be one of: + + ENABLE + ENABLE ALWAYS + ENABLE REPLICA + DISABLE + + and where command can be one of the same list as in . + + + + + + Description + + + ALTER COMMAND TRIGGER changes properties of an + existing command trigger. + + + + You must be superuser to alter a command trigger. + + + + + Parameters + + + + name + + + The name of an existing trigger to alter. + + + + + + newname + + + The new name of the command trigger. + + + + + + enabled + + + When to enable this trigger. See + the session_replication_role parameter. + + + + + + + + Compatibility + + + ALTER COMMAND TRIGGER is a PostgreSQL + extension of the SQL standard. + + + + + See Also + + + + + + + *** /dev/null --- b/doc/src/sgml/ref/create_command_trigger.sgml *************** *** 0 **** --- 1,323 ---- + + + + + CREATE COMMAND TRIGGER + 7 + SQL - Language Statements + + + + CREATE COMMAND TRIGGER + define a new trigger + + + + CREATE COMMAND TRIGGER + + + + + CREATE COMMAND TRIGGER name { BEFORE | AFTER } ANY COMMAND + EXECUTE PROCEDURE function_name () + + CREATE COMMAND TRIGGER name { BEFORE | AFTER } command + EXECUTE PROCEDURE function_name () + + where command can be one of: + + ALTER AGGREGATE + ALTER COLLATION + ALTER CONVERSION + ALTER DOMAIN + ALTER EXTENSION + ALTER FOREIGN DATA WRAPPER + ALTER FOREIGN TABLE + ALTER FUNCTION + ALTER LANGUAGE + ALTER OPERATOR + ALTER OPERATOR CLASS + ALTER OPERATOR FAMILY + ALTER SCHEMA + ALTER SEQUENCE + ALTER SERVER + ALTER TABLE + ALTER TEXT SEARCH CONFIGURATION + ALTER TEXT SEARCH DICTIONARY + ALTER TEXT SEARCH PARSER + ALTER TEXT SEARCH TEMPLATE + ALTER TRIGGER + ALTER TYPE + ALTER USER MAPPING + ALTER VIEW + CLUSTER + CREATE AGGREGATE + CREATE CAST + CREATE COLLATION + CREATE CONVERSION + CREATE DOMAIN + CREATE EXTENSION + CREATE FOREIGN DATA WRAPPER + CREATE FOREIGN TABLE + CREATE FUNCTION + CREATE INDEX + CREATE LANGUAGE + CREATE OPERATOR + CREATE OPERATOR CLASS + CREATE OPERATOR FAMILY + CREATE RULE + CREATE SCHEMA + CREATE SEQUENCE + CREATE SERVER + CREATE TABLE + CREATE TABLE AS + CREATE TEXT SEARCH CONFIGURATION + CREATE TEXT SEARCH DICTIONARY + CREATE TEXT SEARCH PARSER + CREATE TEXT SEARCH TEMPLATE + CREATE TRIGGER + CREATE TYPE + CREATE USER MAPPING + CREATE VIEW + DROP AGGREGATE + DROP CAST + DROP COLLATION + DROP CONVERSION + DROP DOMAIN + DROP EXTENSION + DROP FOREIGN DATA WRAPPER + DROP FOREIGN TABLE + DROP FUNCTION + DROP INDEX + DROP LANGUAGE + DROP OPERATOR + DROP OPERATOR CLASS + DROP OPERATOR FAMILY + DROP RULE + DROP SCHEMA + DROP SEQUENCE + DROP SERVER + DROP TABLE + DROP TEXT SEARCH CONFIGURATION + DROP TEXT SEARCH DICTIONARY + DROP TEXT SEARCH PARSER + DROP TEXT SEARCH TEMPLATE + DROP TRIGGER + DROP TYPE + DROP USER MAPPING + DROP VIEW + LOAD + REINDEX + SELECT INTO + VACUUM + + + + + + Description + + + CREATE COMMAND TRIGGER creates a new command trigger. + The trigger will be associated with the specified command and will + execute the specified + function function_name when + that command is run. + + + + The command trigger can be specified to fire before or after the command + is executed. A command trigger's function must return command + trigger. It can then only abort the execution of the command by + raising an exception. + + + + If multiple triggers of the same kind are defined for the same event, + they will be fired in alphabetical order by name. + + + + Note that objects dropped by the effect of DROP + CASCADE will not result in a command trigger firing, only the + top-level command for the main object will fire a command trigger. That + also applies to other dependencies following, as in DROP OWNED + BY. + + + + Refer to for more information about triggers. + + + + + Parameters + + + + name + + + The name to give the new trigger. This must be distinct from the name + of any other trigger for the same table. The name cannot be + schema-qualified. + + + + + + BEFORE + AFTER + + + Determines whether the function is called before or after the command + is executed. + + + + + + command + + + The tag of the command the trigger is for. Supported commands are + mainly those acting on database objects, plus some more facilities. + That leaves out the following list of non supported commands. + + + Commands that refer to global objects, such as databases, tablespaces + and roles, are not supported. + + + Commands that exercise their own transaction control are only + supported in BEFORE command triggers. This is the + case for VACUUM, CLUSTER, + CREATE INDEX CONCURRENTLY, and REINDEX + DATABASE. + + + The command ALTER TYPE ... ADD VALUE ... prevents + transaction control entirely, thus no command trigger will get fired + when it's used. + + + Commands that are related to transaction control (such + as BEGIN or COMMIT), related to + prepared plans + (e.g. PREPARE, DEALLOCATE), + cursors management + (e.g. DECLARE, FETCH), setting + variables (SET), the LISTEN + feature, and security are not supported either. + + + Command triggers on CREATE COMMAND + TRIGGER, ALTER COMMAND TRIGGER + and DROP COMMAND TRIGGER are not supported so as + not to be able to take over control from a superuser. + + + Triggers on ANY command support more commands than + just this list, and will only provide the command + tag argument as NOT NULL. Supporting more + commands is made so that you can actually block + commands in one go. + + + + + + function_name + + + A user-supplied function that is declared as taking no argument and + returning type command trigger. + + + If your command trigger is implemented in C then it + will be called with an argument, of + type internal, which is a pointer to + the Node * parse tree. + + + + + + + + + Notes + + + To create a trigger on a command, the user must be superuser. + + + + Use to remove a command trigger. + + + + + Examples + + + Forbids the execution of any DDL command: + + + CREATE OR REPLACE FUNCTION abort_any_command() + RETURNS command trigger LANGUAGE plpgsql AS $$ + BEGIN + RAISE EXCEPTION 'command % is disabled', tg_tag; + END; + $$; + + CREATE COMMAND TRIGGER abort_ddl + BEFORE ANY COMMAND + EXECUTE PROCEDURE abort_any_command(); + + + Execute the function enforce_local_style each time + a CREATE TABLE command is run: + + + CREATE OR REPLACE FUNCTION enforce_local_style() + RETURNS command trigger LANGUAGE plpgsql AS $$ + BEGIN + IF substring(tg_objectname, 0, 4) NOT IN ('ab_', 'cz_', 'fr_') + THEN + RAISE EXCEPTION 'invalid relation name: %', tg_objectname; + END IF; + END; + $$; + + CREATE COMMAND TRIGGER check_style + BEFORE CREATE TABLE + EXECUTE PROCEDURE enforce_local_style(); + + + + + + Compatibility + + + CREATE COMMAND TRIGGER is a + PostgreSQL extension of the SQL + standard. + + + + + + See Also + + + + + + + + *** a/doc/src/sgml/ref/create_trigger.sgml --- b/doc/src/sgml/ref/create_trigger.sgml *************** *** 35,40 **** CREATE [ CONSTRAINT ] TRIGGER name --- 35,41 ---- UPDATE [ OF column_name [, ... ] ] DELETE TRUNCATE + *** /dev/null --- b/doc/src/sgml/ref/drop_command_trigger.sgml *************** *** 0 **** --- 1,120 ---- + + + + + DROP COMMAND TRIGGER + 7 + SQL - Language Statements + + + + DROP COMMAND TRIGGER + remove a command trigger + + + + DROP COMMAND TRIGGER + + + + + DROP COMMAND TRIGGER [ IF EXISTS ] name [ CASCADE | RESTRICT ] + + + + + Description + + + DROP COMMAND TRIGGER removes an existing trigger definition. + To execute this command, the current user must be superuser. + + + + + Parameters + + + + + IF EXISTS + + + Do not throw an error if the command trigger does not exist. A notice + is issued in this case. + + + + + + name + + + The name of the command trigger to remove. + + + + + + command + + + The name of the command for which the trigger is defined. + + + + + + CASCADE + + + Automatically drop objects that depend on the trigger. + + + + + + RESTRICT + + + Refuse to drop the trigger if any objects depend on it. This is + the default. + + + + + + + + Examples + + + Destroy the trigger snitch on any command: + + + DROP COMMAND TRIGGER snitch ON ANY COMMAND; + + + + + Compatibility + + + The DROP COMMAND TRIGGER statement is a + PostgreSQL extension. + + + + + See Also + + + + + + + + *** a/doc/src/sgml/reference.sgml --- b/doc/src/sgml/reference.sgml *************** *** 62,67 **** --- 62,68 ---- &alterTSParser; &alterTSTemplate; &alterTrigger; + &alterCommandTrigger; &alterType; &alterUser; &alterUserMapping; *************** *** 104,109 **** --- 105,111 ---- &createTSParser; &createTSTemplate; &createTrigger; + &createCommandTrigger; &createType; &createUser; &createUserMapping; *************** *** 142,147 **** --- 144,150 ---- &dropTSParser; &dropTSTemplate; &dropTrigger; + &dropCommandTrigger; &dropType; &dropUser; &dropUserMapping; *** a/doc/src/sgml/trigger.sgml --- b/doc/src/sgml/trigger.sgml *************** *** 27,32 **** --- 27,50 ---- plain SQL function language. + + PostgreSQL offers both triggers on commands + (see ) and triggers on data manipulation + (see ). + + + + Overview of Command Trigger Behavior + + + A trigger is a specification that the database should automatically + execute a particular function whenever a certain command is performed. + The whole set of PostgreSQL commands is not + supported for triggers, see + for details. + + + Overview of Trigger Behavior *** a/src/backend/bootstrap/bootparse.y --- b/src/backend/bootstrap/bootparse.y *************** *** 291,297 **** Boot_DeclareIndexStmt: $10, NULL, NIL, NIL, false, false, false, false, false, ! false, false, true, false, false); do_end(); } ; --- 291,297 ---- $10, NULL, NIL, NIL, false, false, false, false, false, ! false, false, true, false, false, NULL); do_end(); } ; *************** *** 310,316 **** Boot_DeclareUniqueIndexStmt: $11, NULL, NIL, NIL, true, false, false, false, false, ! false, false, true, false, false); do_end(); } ; --- 310,316 ---- $11, NULL, NIL, NIL, true, false, false, false, false, ! false, false, true, false, false, NULL); do_end(); } ; *** a/src/backend/catalog/Makefile --- b/src/backend/catalog/Makefile *************** *** 31,37 **** POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \ pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \ pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \ ! pg_statistic.h pg_rewrite.h pg_trigger.h pg_description.h \ pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \ pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \ pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ --- 31,37 ---- pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \ pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \ pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \ ! pg_statistic.h pg_rewrite.h pg_trigger.h pg_cmdtrigger.h pg_description.h \ pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \ pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \ pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ *** a/src/backend/catalog/dependency.c --- b/src/backend/catalog/dependency.c *************** *** 26,31 **** --- 26,32 ---- #include "catalog/pg_attrdef.h" #include "catalog/pg_authid.h" #include "catalog/pg_cast.h" + #include "catalog/pg_cmdtrigger.h" #include "catalog/pg_collation.h" #include "catalog/pg_collation_fn.h" #include "catalog/pg_constraint.h" *************** *** 53,58 **** --- 54,60 ---- #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" #include "catalog/pg_user_mapping.h" + #include "commands/cmdtrigger.h" #include "commands/comment.h" #include "commands/dbcommands.h" #include "commands/defrem.h" *************** *** 158,164 **** static const Oid object_classes[MAX_OCLASS] = { ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ DefaultAclRelationId, /* OCLASS_DEFACL */ ! ExtensionRelationId /* OCLASS_EXTENSION */ }; --- 160,167 ---- ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ DefaultAclRelationId, /* OCLASS_DEFACL */ ! ExtensionRelationId, /* OCLASS_EXTENSION */ ! CmdTriggerRelationId /* OCLASS_CMDTRIGGER */ }; *************** *** 1096,1101 **** doDeletion(const ObjectAddress *object) --- 1099,1108 ---- break; } + case OCLASS_CMDTRIGGER: + RemoveCmdTriggerById(object->objectId); + break; + case OCLASS_PROC: RemoveFunctionById(object->objectId); break; *************** *** 2248,2253 **** getObjectClass(const ObjectAddress *object) --- 2255,2263 ---- case ExtensionRelationId: return OCLASS_EXTENSION; + + case CmdTriggerRelationId: + return OCLASS_CMDTRIGGER; } /* shouldn't get here */ *************** *** 2882,2887 **** getObjectDescription(const ObjectAddress *object) --- 2892,2936 ---- break; } + case OCLASS_CMDTRIGGER: + { + Relation trigDesc; + ScanKeyData skey[1]; + SysScanDesc tgscan; + HeapTuple tup; + Form_pg_cmdtrigger trig; + + trigDesc = heap_open(CmdTriggerRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + tgscan = systable_beginscan(trigDesc, CmdTriggerOidIndexId, true, + SnapshotNow, 1, skey); + + tup = systable_getnext(tgscan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for command trigger %u", + object->objectId); + + trig = (Form_pg_cmdtrigger) GETSTRUCT(tup); + + if (strcmp(NameStr(trig->ctgcommand), "ANY") == 0) + appendStringInfo(&buffer, _("trigger %s on any command"), + NameStr(trig->ctgname)); + else + appendStringInfo(&buffer, _("trigger %s on command %s"), + NameStr(trig->ctgname), + NameStr(trig->ctgcommand)); + + systable_endscan(tgscan); + heap_close(trigDesc, AccessShareLock); + break; + } + default: appendStringInfo(&buffer, "unrecognized object %u %u %d", object->classId, *** a/src/backend/catalog/index.c --- b/src/backend/catalog/index.c *************** *** 2789,2795 **** IndexGetRelation(Oid indexId, bool missing_ok) * reindex_index - This routine is used to recreate a single index */ void ! reindex_index(Oid indexId, bool skip_constraint_checks) { Relation iRel, heapRelation, --- 2789,2795 ---- * reindex_index - This routine is used to recreate a single index */ void ! reindex_index(Oid indexId, bool skip_constraint_checks, CommandContext cmd) { Relation iRel, heapRelation, *************** *** 2828,2833 **** reindex_index(Oid indexId, bool skip_constraint_checks) --- 2828,2843 ---- */ CheckTableNotInUse(iRel, "REINDEX INDEX"); + /* Call BEFORE REINDEX INDEX command triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = indexId; + cmd->objectname = RelationGetRelationName(iRel); + cmd->schemaname = get_namespace_name(RelationGetNamespace(iRel)); + + ExecBeforeCommandTriggers(cmd); + } + /* * All predicate locks on the index are about to be made invalid. Promote * them to relation locks on the heap. *************** *** 2923,2928 **** reindex_index(Oid indexId, bool skip_constraint_checks) --- 2933,2942 ---- /* Close rels, but keep locks */ index_close(iRel, NoLock); heap_close(heapRelation, NoLock); + + /* Call AFTER REINDEX INDEX command triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } /* *************** *** 2955,2961 **** reindex_index(Oid indexId, bool skip_constraint_checks) * index rebuild. */ bool ! reindex_relation(Oid relid, int flags) { Relation rel; Oid toast_relid; --- 2969,2975 ---- * index rebuild. */ bool ! reindex_relation(Oid relid, int flags, CommandContext cmd) { Relation rel; Oid toast_relid; *************** *** 2972,2977 **** reindex_relation(Oid relid, int flags) --- 2986,3002 ---- toast_relid = rel->rd_rel->reltoastrelid; + /* Call BEFORE REINDEX command triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = relid; + cmd->objectname = RelationGetRelationName(rel); + cmd->schemaname = get_namespace_name(RelationGetNamespace(rel)); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Get the list of index OIDs for this relation. (We trust to the * relcache to get this with a sequential scan if ignoring system *************** *** 3033,3039 **** reindex_relation(Oid relid, int flags) if (is_pg_class) RelationSetIndexList(rel, doneIndexes, InvalidOid); ! reindex_index(indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS)); CommandCounterIncrement(); --- 3058,3064 ---- if (is_pg_class) RelationSetIndexList(rel, doneIndexes, InvalidOid); ! reindex_index(indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS), NULL); CommandCounterIncrement(); *************** *** 3068,3074 **** reindex_relation(Oid relid, int flags) * still hold the lock on the master table. */ if ((flags & REINDEX_REL_PROCESS_TOAST) && OidIsValid(toast_relid)) ! result |= reindex_relation(toast_relid, flags); return result; } --- 3093,3103 ---- * still hold the lock on the master table. */ if ((flags & REINDEX_REL_PROCESS_TOAST) && OidIsValid(toast_relid)) ! result |= reindex_relation(toast_relid, flags, NULL); ! ! /* Call AFTER REINDEX command triggers */ ! if (CommandFiresAfterTriggers(cmd)) ! ExecAfterCommandTriggers(cmd); return result; } *** a/src/backend/catalog/objectaddress.c --- b/src/backend/catalog/objectaddress.c *************** *** 21,26 **** --- 21,27 ---- #include "catalog/objectaddress.h" #include "catalog/pg_authid.h" #include "catalog/pg_cast.h" + #include "catalog/pg_cmdtrigger.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" *************** *** 44,49 **** --- 45,51 ---- #include "catalog/pg_ts_parser.h" #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" + #include "commands/cmdtrigger.h" #include "commands/dbcommands.h" #include "commands/defrem.h" #include "commands/extension.h" *************** *** 204,209 **** static ObjectPropertyType ObjectProperty[] = --- 206,217 ---- InvalidAttrNumber }, { + CmdTriggerRelationId, + CmdTriggerOidIndexId, + -1, + InvalidAttrNumber + }, + { TSConfigRelationId, TSConfigOidIndexId, TSCONFIGOID, *************** *** 249,254 **** static ObjectAddress get_object_address_type(ObjectType objtype, --- 257,264 ---- List *objname, bool missing_ok); static ObjectAddress get_object_address_opcf(ObjectType objtype, List *objname, List *objargs, bool missing_ok); + static ObjectAddress get_object_address_cmdtrigger(ObjectType objtype, + List *objname, bool missing_ok); static ObjectPropertyType *get_object_property_data(Oid class_id); /* *************** *** 292,298 **** get_object_address(ObjectType objtype, List *objname, List *objargs, */ inval_count = SharedInvalidMessageCounter; ! /* Look up object address. */ switch (objtype) { case OBJECT_INDEX: --- 302,308 ---- */ inval_count = SharedInvalidMessageCounter; ! /* Look up object address. */ switch (objtype) { case OBJECT_INDEX: *************** *** 317,322 **** get_object_address(ObjectType objtype, List *objname, List *objargs, --- 327,336 ---- address = get_object_address_relobject(objtype, objname, &relation, missing_ok); break; + case OBJECT_CMDTRIGGER: + address = get_object_address_cmdtrigger(objtype, objname, + missing_ok); + break; case OBJECT_DATABASE: case OBJECT_EXTENSION: case OBJECT_TABLESPACE: *************** *** 902,907 **** get_object_address_opcf(ObjectType objtype, --- 916,941 ---- } /* + * Find the ObjectAddress for a command trigger. + */ + static ObjectAddress + get_object_address_cmdtrigger(ObjectType objtype, List *objname, bool missing_ok) + { + char *name; + ObjectAddress address; + + Assert(list_length(objname) == 1); /* command triggers are not schema qualified */ + + name = strVal(linitial(objname)); + + address.classId = CmdTriggerRelationId; + address.objectId = get_cmdtrigger_oid(name, missing_ok); + address.objectSubId = 0; + + return address; + } + + /* * Check ownership of an object previously identified by get_object_address. */ void *************** *** 1054,1059 **** check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, --- 1088,1094 ---- break; case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: + case OBJECT_CMDTRIGGER: /* We treat these object types as being owned by superusers */ if (!superuser_arg(roleid)) ereport(ERROR, *** a/src/backend/catalog/pg_aggregate.c --- b/src/backend/catalog/pg_aggregate.c *************** *** 23,28 **** --- 23,29 ---- #include "catalog/pg_proc.h" #include "catalog/pg_proc_fn.h" #include "catalog/pg_type.h" + #include "commands/cmdtrigger.h" #include "miscadmin.h" #include "parser/parse_coerce.h" #include "parser/parse_func.h" *************** *** 50,56 **** AggregateCreate(const char *aggName, List *aggfinalfnName, List *aggsortopName, Oid aggTransType, ! const char *agginitval) { Relation aggdesc; HeapTuple tup; --- 51,58 ---- List *aggfinalfnName, List *aggsortopName, Oid aggTransType, ! const char *agginitval, ! CommandContext cmd) { Relation aggdesc; HeapTuple tup; *************** *** 222,227 **** AggregateCreate(const char *aggName, --- 224,240 ---- aclcheck_error(aclresult, ACL_KIND_TYPE, format_type_be(finaltype)); + /* + * Call BEFORE CREATE AGGREGATE triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = (char *)aggName; + cmd->schemaname = get_namespace_name(aggNamespace); + + ExecBeforeCommandTriggers(cmd); + } /* * Everything looks okay. Try to create the pg_proc entry for the *************** *** 317,322 **** AggregateCreate(const char *aggName, --- 330,342 ---- referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + + /* Call AFTER CREATE AGGREGATE triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = procOid; + ExecAfterCommandTriggers(cmd); + } } /* *** a/src/backend/catalog/pg_collation.c --- b/src/backend/catalog/pg_collation.c *************** *** 23,32 **** --- 23,34 ---- #include "catalog/pg_collation.h" #include "catalog/pg_collation_fn.h" #include "catalog/pg_namespace.h" + #include "commands/cmdtrigger.h" #include "mb/pg_wchar.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/rel.h" + #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/tqual.h" *************** *** 40,46 **** Oid CollationCreate(const char *collname, Oid collnamespace, Oid collowner, int32 collencoding, ! const char *collcollate, const char *collctype) { Relation rel; TupleDesc tupDesc; --- 42,49 ---- CollationCreate(const char *collname, Oid collnamespace, Oid collowner, int32 collencoding, ! const char *collcollate, const char *collctype, ! CommandContext cmd) { Relation rel; TupleDesc tupDesc; *************** *** 90,95 **** CollationCreate(const char *collname, Oid collnamespace, --- 93,110 ---- errmsg("collation \"%s\" already exists", collname))); + /* + * Call BEFORE CREATE COLLATION triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = (char *)collname; + cmd->schemaname = get_namespace_name(collnamespace); + + ExecBeforeCommandTriggers(cmd); + } + /* open pg_collation */ rel = heap_open(CollationRelationId, RowExclusiveLock); tupDesc = RelationGetDescr(rel); *************** *** 141,146 **** CollationCreate(const char *collname, Oid collnamespace, --- 156,167 ---- heap_freetuple(tup); heap_close(rel, RowExclusiveLock); + /* Call AFTER CREATE COLLATION triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = oid; + ExecAfterCommandTriggers(cmd); + } return oid; } *** a/src/backend/catalog/pg_operator.c --- b/src/backend/catalog/pg_operator.c *************** *** 336,342 **** OperatorCreate(const char *operatorName, Oid restrictionId, Oid joinId, bool canMerge, ! bool canHash) { Relation pg_operator_desc; HeapTuple tup; --- 336,343 ---- Oid restrictionId, Oid joinId, bool canMerge, ! bool canHash, ! CommandContext cmd) { Relation pg_operator_desc; HeapTuple tup; *************** *** 433,438 **** OperatorCreate(const char *operatorName, --- 434,451 ---- operatorName); /* + * Call BEFORE CREATE AGGREGARE triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = (char *)operatorName; + cmd->schemaname = get_namespace_name(operatorNamespace); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Set up the other operators. If they do not currently exist, create * shells in order to get ObjectId's. */ *************** *** 564,569 **** OperatorCreate(const char *operatorName, --- 577,589 ---- if (OidIsValid(commutatorId) || OidIsValid(negatorId)) OperatorUpd(operatorObjectId, commutatorId, negatorId); + + /* Call AFTER CREATE OPERATOR triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = operatorObjectId; + ExecAfterCommandTriggers(cmd); + } } /* *** a/src/backend/catalog/pg_shdepend.c --- b/src/backend/catalog/pg_shdepend.c *************** *** 1327,1337 **** shdepReassignOwned(List *roleids, Oid newrole) switch (sdepForm->classid) { case CollationRelationId: ! AlterCollationOwner_oid(sdepForm->objid, newrole); break; case ConversionRelationId: ! AlterConversionOwner_oid(sdepForm->objid, newrole); break; case TypeRelationId: --- 1327,1337 ---- switch (sdepForm->classid) { case CollationRelationId: ! AlterCollationOwner_oid(sdepForm->objid, newrole, NULL); break; case ConversionRelationId: ! AlterConversionOwner_oid(sdepForm->objid, newrole, NULL); break; case TypeRelationId: *************** *** 1357,1363 **** shdepReassignOwned(List *roleids, Oid newrole) break; case ProcedureRelationId: ! AlterFunctionOwner_oid(sdepForm->objid, newrole); break; case LanguageRelationId: --- 1357,1363 ---- break; case ProcedureRelationId: ! AlterFunctionOwner_oid(sdepForm->objid, newrole, NULL); break; case LanguageRelationId: *** a/src/backend/catalog/pg_type.c --- b/src/backend/catalog/pg_type.c *************** *** 678,684 **** GenerateTypeDependencies(Oid typeNamespace, * ALTER TYPE RENAME TO command. */ void ! RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace) { Relation pg_type_desc; HeapTuple tuple; --- 678,685 ---- * ALTER TYPE RENAME TO command. */ void ! RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace, ! CommandContext cmd) { Relation pg_type_desc; HeapTuple tuple; *************** *** 705,710 **** RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace) --- 706,721 ---- (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("type \"%s\" already exists", newTypeName))); + /* Call BEFORE ALTER TYPE triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = typeOid; + cmd->objectname = NameStr(typ->typname); + cmd->schemaname = get_namespace_name(typeNamespace); + + ExecBeforeCommandTriggers(cmd); + } + /* OK, do the rename --- tuple is a copy, so OK to scribble on it */ namestrcpy(&(typ->typname), newTypeName); *************** *** 721,729 **** RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace) { char *arrname = makeArrayTypeName(newTypeName, typeNamespace); ! RenameTypeInternal(arrayOid, arrname, typeNamespace); pfree(arrname); } } --- 732,747 ---- { char *arrname = makeArrayTypeName(newTypeName, typeNamespace); ! RenameTypeInternal(arrayOid, arrname, typeNamespace, NULL); pfree(arrname); } + + /* Call AFTER ALTER TYPE triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = (char *)newTypeName; + ExecAfterCommandTriggers(cmd); + } } *************** *** 824,830 **** moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace) newname = makeArrayTypeName(typeName, typeNamespace); /* Apply the rename */ ! RenameTypeInternal(typeOid, newname, typeNamespace); /* * We must bump the command counter so that any subsequent use of --- 842,848 ---- newname = makeArrayTypeName(typeName, typeNamespace); /* Apply the rename */ ! RenameTypeInternal(typeOid, newname, typeNamespace, NULL); /* * We must bump the command counter so that any subsequent use of *** a/src/backend/commands/Makefile --- b/src/backend/commands/Makefile *************** *** 12,18 **** subdir = src/backend/commands top_builddir = ../../.. include $(top_builddir)/src/Makefile.global ! OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ collationcmds.o constraint.o conversioncmds.o copy.o createas.o \ dbcommands.o define.o discard.o dropcmds.o explain.o extension.o \ foreigncmds.o functioncmds.o \ --- 12,19 ---- top_builddir = ../../.. include $(top_builddir)/src/Makefile.global ! OBJS = aggregatecmds.o alter.o analyze.o async.o \ ! cmdtrigger.o cluster.o comment.o \ collationcmds.o constraint.o conversioncmds.o copy.o createas.o \ dbcommands.o define.o discard.o dropcmds.o explain.o extension.o \ foreigncmds.o functioncmds.o \ *** a/src/backend/commands/aggregatecmds.c --- b/src/backend/commands/aggregatecmds.c *************** *** 28,33 **** --- 28,34 ---- #include "catalog/pg_aggregate.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" + #include "commands/cmdtrigger.h" #include "commands/defrem.h" #include "miscadmin.h" #include "parser/parse_func.h" *************** *** 46,52 **** * "args" defines the input type(s). */ void ! DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) { char *aggName; Oid aggNamespace; --- 47,54 ---- * "args" defines the input type(s). */ void ! DefineAggregate(List *name, List *args, bool oldstyle, List *parameters, ! CommandContext cmd) { char *aggName; Oid aggNamespace; *************** *** 203,209 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) finalfuncName, /* final function name */ sortoperatorName, /* sort operator name */ transTypeId, /* transition data type */ ! initval); /* initial condition */ } --- 205,212 ---- finalfuncName, /* final function name */ sortoperatorName, /* sort operator name */ transTypeId, /* transition data type */ ! initval, /* initial condition */ ! cmd); } *************** *** 212,218 **** DefineAggregate(List *name, List *args, bool oldstyle, List *parameters) * Rename an aggregate. */ void ! RenameAggregate(List *name, List *args, const char *newname) { Oid procOid; Oid namespaceOid; --- 215,221 ---- * Rename an aggregate. */ void ! RenameAggregate(List *name, List *args, const char *newname, CommandContext cmd) { Oid procOid; Oid namespaceOid; *************** *** 258,263 **** RenameAggregate(List *name, List *args, const char *newname) --- 261,276 ---- aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* Call BEFORE ALTER AGGREGATE triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(procForm->proname)); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + /* rename */ namestrcpy(&(((Form_pg_proc) GETSTRUCT(tup))->proname), newname); simple_heap_update(rel, &tup->t_self, tup); *************** *** 265,277 **** RenameAggregate(List *name, List *args, const char *newname) heap_close(rel, NoLock); heap_freetuple(tup); } /* * Change aggregate owner */ void ! AlterAggregateOwner(List *name, List *args, Oid newOwnerId) { Oid procOid; --- 278,297 ---- heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER AGGREGATE triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } /* * Change aggregate owner */ void ! AlterAggregateOwner(List *name, List *args, Oid newOwnerId, CommandContext cmd) { Oid procOid; *************** *** 279,283 **** AlterAggregateOwner(List *name, List *args, Oid newOwnerId) procOid = LookupAggNameTypeNames(name, args, false); /* The rest is just like a function */ ! AlterFunctionOwner_oid(procOid, newOwnerId); } --- 299,303 ---- procOid = LookupAggNameTypeNames(name, args, false); /* The rest is just like a function */ ! AlterFunctionOwner_oid(procOid, newOwnerId, cmd); } *** a/src/backend/commands/alter.c --- b/src/backend/commands/alter.c *************** *** 20,25 **** --- 20,26 ---- #include "catalog/pg_largeobject.h" #include "catalog/pg_namespace.h" #include "commands/alter.h" + #include "commands/cmdtrigger.h" #include "commands/collationcmds.h" #include "commands/conversioncmds.h" #include "commands/dbcommands.h" *************** *** 47,68 **** void ExecRenameStmt(RenameStmt *stmt) { switch (stmt->renameType) { case OBJECT_AGGREGATE: ! RenameAggregate(stmt->object, stmt->objarg, stmt->newname); break; case OBJECT_COLLATION: ! RenameCollation(stmt->object, stmt->newname); break; case OBJECT_CONSTRAINT: ! RenameConstraint(stmt); break; case OBJECT_CONVERSION: ! RenameConversion(stmt->object, stmt->newname); break; case OBJECT_DATABASE: --- 48,76 ---- void ExecRenameStmt(RenameStmt *stmt) { + CommandContextData cmd; + InitCommandContext(&cmd, (Node *)stmt); + switch (stmt->renameType) { case OBJECT_AGGREGATE: ! RenameAggregate(stmt->object, stmt->objarg, stmt->newname, &cmd); break; case OBJECT_COLLATION: ! RenameCollation(stmt->object, stmt->newname, &cmd); break; case OBJECT_CONSTRAINT: ! RenameConstraint(stmt, &cmd); break; case OBJECT_CONVERSION: ! RenameConversion(stmt->object, stmt->newname, &cmd); ! break; ! ! case OBJECT_CMDTRIGGER: ! RenameCmdTrigger(stmt->object, stmt->newname); break; case OBJECT_DATABASE: *************** *** 70,96 **** ExecRenameStmt(RenameStmt *stmt) break; case OBJECT_FDW: ! RenameForeignDataWrapper(stmt->subname, stmt->newname); break; case OBJECT_FOREIGN_SERVER: ! RenameForeignServer(stmt->subname, stmt->newname); break; case OBJECT_FUNCTION: ! RenameFunction(stmt->object, stmt->objarg, stmt->newname); break; case OBJECT_LANGUAGE: ! RenameLanguage(stmt->subname, stmt->newname); break; case OBJECT_OPCLASS: ! RenameOpClass(stmt->object, stmt->subname, stmt->newname); break; case OBJECT_OPFAMILY: ! RenameOpFamily(stmt->object, stmt->subname, stmt->newname); break; case OBJECT_ROLE: --- 78,104 ---- break; case OBJECT_FDW: ! RenameForeignDataWrapper(stmt->subname, stmt->newname, &cmd); break; case OBJECT_FOREIGN_SERVER: ! RenameForeignServer(stmt->subname, stmt->newname, &cmd); break; case OBJECT_FUNCTION: ! RenameFunction(stmt->object, stmt->objarg, stmt->newname, &cmd); break; case OBJECT_LANGUAGE: ! RenameLanguage(stmt->subname, stmt->newname, &cmd); break; case OBJECT_OPCLASS: ! RenameOpClass(stmt->object, stmt->subname, stmt->newname, &cmd); break; case OBJECT_OPFAMILY: ! RenameOpFamily(stmt->object, stmt->subname, stmt->newname, &cmd); break; case OBJECT_ROLE: *************** *** 98,104 **** ExecRenameStmt(RenameStmt *stmt) break; case OBJECT_SCHEMA: ! RenameSchema(stmt->subname, stmt->newname); break; case OBJECT_TABLESPACE: --- 106,112 ---- break; case OBJECT_SCHEMA: ! RenameSchema(stmt->subname, stmt->newname, &cmd); break; case OBJECT_TABLESPACE: *************** *** 110,146 **** ExecRenameStmt(RenameStmt *stmt) case OBJECT_VIEW: case OBJECT_INDEX: case OBJECT_FOREIGN_TABLE: ! RenameRelation(stmt); break; case OBJECT_COLUMN: case OBJECT_ATTRIBUTE: ! renameatt(stmt); break; case OBJECT_TRIGGER: ! renametrig(stmt); break; case OBJECT_TSPARSER: ! RenameTSParser(stmt->object, stmt->newname); break; case OBJECT_TSDICTIONARY: ! RenameTSDictionary(stmt->object, stmt->newname); break; case OBJECT_TSTEMPLATE: ! RenameTSTemplate(stmt->object, stmt->newname); break; case OBJECT_TSCONFIGURATION: ! RenameTSConfiguration(stmt->object, stmt->newname); break; case OBJECT_DOMAIN: case OBJECT_TYPE: ! RenameType(stmt); break; default: --- 118,154 ---- case OBJECT_VIEW: case OBJECT_INDEX: case OBJECT_FOREIGN_TABLE: ! RenameRelation(stmt, &cmd); break; case OBJECT_COLUMN: case OBJECT_ATTRIBUTE: ! renameatt(stmt, &cmd); break; case OBJECT_TRIGGER: ! renametrig(stmt, &cmd); break; case OBJECT_TSPARSER: ! RenameTSParser(stmt->object, stmt->newname, &cmd); break; case OBJECT_TSDICTIONARY: ! RenameTSDictionary(stmt->object, stmt->newname, &cmd); break; case OBJECT_TSTEMPLATE: ! RenameTSTemplate(stmt->object, stmt->newname, &cmd); break; case OBJECT_TSCONFIGURATION: ! RenameTSConfiguration(stmt->object, stmt->newname, &cmd); break; case OBJECT_DOMAIN: case OBJECT_TYPE: ! RenameType(stmt, &cmd); break; default: *************** *** 156,195 **** ExecRenameStmt(RenameStmt *stmt) void ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) { switch (stmt->objectType) { case OBJECT_AGGREGATE: AlterFunctionNamespace(stmt->object, stmt->objarg, true, ! stmt->newschema); break; case OBJECT_COLLATION: ! AlterCollationNamespace(stmt->object, stmt->newschema); break; case OBJECT_CONVERSION: ! AlterConversionNamespace(stmt->object, stmt->newschema); break; case OBJECT_EXTENSION: ! AlterExtensionNamespace(stmt->object, stmt->newschema); break; case OBJECT_FUNCTION: AlterFunctionNamespace(stmt->object, stmt->objarg, false, ! stmt->newschema); break; case OBJECT_OPERATOR: ! AlterOperatorNamespace(stmt->object, stmt->objarg, stmt->newschema); break; case OBJECT_OPCLASS: ! AlterOpClassNamespace(stmt->object, stmt->addname, stmt->newschema); break; case OBJECT_OPFAMILY: ! AlterOpFamilyNamespace(stmt->object, stmt->addname, stmt->newschema); break; case OBJECT_SEQUENCE: --- 164,206 ---- void ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) { + CommandContextData cmd; + InitCommandContext(&cmd, (Node *)stmt); + switch (stmt->objectType) { case OBJECT_AGGREGATE: AlterFunctionNamespace(stmt->object, stmt->objarg, true, ! stmt->newschema, &cmd); break; case OBJECT_COLLATION: ! AlterCollationNamespace(stmt->object, stmt->newschema, &cmd); break; case OBJECT_CONVERSION: ! AlterConversionNamespace(stmt->object, stmt->newschema, &cmd); break; case OBJECT_EXTENSION: ! AlterExtensionNamespace(stmt->object, stmt->newschema, &cmd); break; case OBJECT_FUNCTION: AlterFunctionNamespace(stmt->object, stmt->objarg, false, ! stmt->newschema, &cmd); break; case OBJECT_OPERATOR: ! AlterOperatorNamespace(stmt->object, stmt->objarg, stmt->newschema, &cmd); break; case OBJECT_OPCLASS: ! AlterOpClassNamespace(stmt->object, stmt->addname, stmt->newschema, &cmd); break; case OBJECT_OPFAMILY: ! AlterOpFamilyNamespace(stmt->object, stmt->addname, stmt->newschema, &cmd); break; case OBJECT_SEQUENCE: *************** *** 200,223 **** ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) break; case OBJECT_TSPARSER: ! AlterTSParserNamespace(stmt->object, stmt->newschema); break; case OBJECT_TSDICTIONARY: ! AlterTSDictionaryNamespace(stmt->object, stmt->newschema); break; case OBJECT_TSTEMPLATE: ! AlterTSTemplateNamespace(stmt->object, stmt->newschema); break; case OBJECT_TSCONFIGURATION: ! AlterTSConfigurationNamespace(stmt->object, stmt->newschema); break; case OBJECT_TYPE: case OBJECT_DOMAIN: ! AlterTypeNamespace(stmt->object, stmt->newschema, stmt->objectType); break; default: --- 211,234 ---- break; case OBJECT_TSPARSER: ! AlterTSParserNamespace(stmt->object, stmt->newschema, &cmd); break; case OBJECT_TSDICTIONARY: ! AlterTSDictionaryNamespace(stmt->object, stmt->newschema, &cmd); break; case OBJECT_TSTEMPLATE: ! AlterTSTemplateNamespace(stmt->object, stmt->newschema, &cmd); break; case OBJECT_TSCONFIGURATION: ! AlterTSConfigurationNamespace(stmt->object, stmt->newschema, &cmd); break; case OBJECT_TYPE: case OBJECT_DOMAIN: ! AlterTypeNamespace(stmt->object, stmt->newschema, stmt->objectType, &cmd); break; default: *************** *** 239,244 **** ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) --- 250,258 ---- * * Returns the OID of the object's previous namespace, or InvalidOid if * object doesn't have a schema. + * + * Doesn't run any command trigger for those sub-commands, so just pass a NULL + * CommandContext to functions implementing the ALTER. */ Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid) *************** *** 275,289 **** AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid) } case OCLASS_PROC: ! oldNspOid = AlterFunctionNamespace_oid(objid, nspOid); break; case OCLASS_TYPE: ! oldNspOid = AlterTypeNamespace_oid(objid, nspOid); break; case OCLASS_COLLATION: ! oldNspOid = AlterCollationNamespace_oid(objid, nspOid); break; case OCLASS_CONVERSION: --- 289,303 ---- } case OCLASS_PROC: ! oldNspOid = AlterFunctionNamespace_oid(objid, nspOid, NULL); break; case OCLASS_TYPE: ! oldNspOid = AlterTypeNamespace_oid(objid, nspOid, NULL); break; case OCLASS_COLLATION: ! oldNspOid = AlterCollationNamespace_oid(objid, nspOid, NULL); break; case OCLASS_CONVERSION: *************** *** 353,359 **** Oid AlterObjectNamespace(Relation rel, int oidCacheId, int nameCacheId, Oid objid, Oid nspOid, int Anum_name, int Anum_namespace, int Anum_owner, ! AclObjectKind acl_kind) { Oid classId = RelationGetRelid(rel); Oid oldNspOid; --- 367,373 ---- AlterObjectNamespace(Relation rel, int oidCacheId, int nameCacheId, Oid objid, Oid nspOid, int Anum_name, int Anum_namespace, int Anum_owner, ! AclObjectKind acl_kind, CommandContext cmd) { Oid classId = RelationGetRelid(rel); Oid oldNspOid; *************** *** 423,428 **** AlterObjectNamespace(Relation rel, int oidCacheId, int nameCacheId, --- 437,452 ---- getObjectDescriptionOids(classId, objid), get_namespace_name(nspOid)))); + /* Call BEFORE ALTER OBJECT triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = objid; + cmd->objectname = pstrdup(NameStr(*(DatumGetName(name)))); + cmd->schemaname = get_namespace_name(oldNspOid); + + ExecBeforeCommandTriggers(cmd); + } + /* Build modified tuple */ values = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(Datum)); nulls = palloc0(RelationGetNumberOfAttributes(rel) * sizeof(bool)); *************** *** 445,450 **** AlterObjectNamespace(Relation rel, int oidCacheId, int nameCacheId, --- 469,480 ---- changeDependencyFor(classId, objid, NamespaceRelationId, oldNspOid, nspOid); + /* Call AFTER ALTER OBJECT triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->schemaname = get_namespace_name(nspOid); + ExecAfterCommandTriggers(cmd); + } return oldNspOid; } *************** *** 457,475 **** void ExecAlterOwnerStmt(AlterOwnerStmt *stmt) { Oid newowner = get_role_oid(stmt->newowner, false); switch (stmt->objectType) { case OBJECT_AGGREGATE: ! AlterAggregateOwner(stmt->object, stmt->objarg, newowner); break; case OBJECT_COLLATION: ! AlterCollationOwner(stmt->object, newowner); break; case OBJECT_CONVERSION: ! AlterConversionOwner(stmt->object, newowner); break; case OBJECT_DATABASE: --- 487,508 ---- ExecAlterOwnerStmt(AlterOwnerStmt *stmt) { Oid newowner = get_role_oid(stmt->newowner, false); + CommandContextData cmd; + + InitCommandContext(&cmd, (Node *)stmt); switch (stmt->objectType) { case OBJECT_AGGREGATE: ! AlterAggregateOwner(stmt->object, stmt->objarg, newowner, &cmd); break; case OBJECT_COLLATION: ! AlterCollationOwner(stmt->object, newowner, &cmd); break; case OBJECT_CONVERSION: ! AlterConversionOwner(stmt->object, newowner, &cmd); break; case OBJECT_DATABASE: *************** *** 477,487 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt) break; case OBJECT_FUNCTION: ! AlterFunctionOwner(stmt->object, stmt->objarg, newowner); break; case OBJECT_LANGUAGE: ! AlterLanguageOwner(strVal(linitial(stmt->object)), newowner); break; case OBJECT_LARGEOBJECT: --- 510,520 ---- break; case OBJECT_FUNCTION: ! AlterFunctionOwner(stmt->object, stmt->objarg, newowner, &cmd); break; case OBJECT_LANGUAGE: ! AlterLanguageOwner(strVal(linitial(stmt->object)), newowner, &cmd); break; case OBJECT_LARGEOBJECT: *************** *** 493,511 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt) AlterOperatorOwner(stmt->object, (TypeName *) linitial(stmt->objarg), (TypeName *) lsecond(stmt->objarg), ! newowner); break; case OBJECT_OPCLASS: ! AlterOpClassOwner(stmt->object, stmt->addname, newowner); break; case OBJECT_OPFAMILY: ! AlterOpFamilyOwner(stmt->object, stmt->addname, newowner); break; case OBJECT_SCHEMA: ! AlterSchemaOwner(strVal(linitial(stmt->object)), newowner); break; case OBJECT_TABLESPACE: --- 526,544 ---- AlterOperatorOwner(stmt->object, (TypeName *) linitial(stmt->objarg), (TypeName *) lsecond(stmt->objarg), ! newowner, &cmd); break; case OBJECT_OPCLASS: ! AlterOpClassOwner(stmt->object, stmt->addname, newowner, &cmd); break; case OBJECT_OPFAMILY: ! AlterOpFamilyOwner(stmt->object, stmt->addname, newowner, &cmd); break; case OBJECT_SCHEMA: ! AlterSchemaOwner(strVal(linitial(stmt->object)), newowner, &cmd); break; case OBJECT_TABLESPACE: *************** *** 514,537 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt) case OBJECT_TYPE: case OBJECT_DOMAIN: /* same as TYPE */ ! AlterTypeOwner(stmt->object, newowner, stmt->objectType); break; case OBJECT_TSDICTIONARY: ! AlterTSDictionaryOwner(stmt->object, newowner); break; case OBJECT_TSCONFIGURATION: ! AlterTSConfigurationOwner(stmt->object, newowner); break; case OBJECT_FDW: AlterForeignDataWrapperOwner(strVal(linitial(stmt->object)), ! newowner); break; case OBJECT_FOREIGN_SERVER: ! AlterForeignServerOwner(strVal(linitial(stmt->object)), newowner); break; default: --- 547,570 ---- case OBJECT_TYPE: case OBJECT_DOMAIN: /* same as TYPE */ ! AlterTypeOwner(stmt->object, newowner, stmt->objectType, &cmd); break; case OBJECT_TSDICTIONARY: ! AlterTSDictionaryOwner(stmt->object, newowner, &cmd); break; case OBJECT_TSCONFIGURATION: ! AlterTSConfigurationOwner(stmt->object, newowner, &cmd); break; case OBJECT_FDW: AlterForeignDataWrapperOwner(strVal(linitial(stmt->object)), ! newowner, &cmd); break; case OBJECT_FOREIGN_SERVER: ! AlterForeignServerOwner(strVal(linitial(stmt->object)), newowner, &cmd); break; default: *** a/src/backend/commands/cluster.c --- b/src/backend/commands/cluster.c *************** *** 100,105 **** static void reform_and_rewrite_tuple(HeapTuple tuple, --- 100,109 ---- void cluster(ClusterStmt *stmt, bool isTopLevel) { + CommandContextData cmd; + + InitCommandContext(&cmd, (Node *)stmt); + if (stmt->relation != NULL) { /* This is the single-relation case. */ *************** *** 173,179 **** cluster(ClusterStmt *stmt, bool isTopLevel) heap_close(rel, NoLock); /* Do the job */ ! cluster_rel(tableOid, indexOid, false, stmt->verbose, -1, -1); } else { --- 177,183 ---- heap_close(rel, NoLock); /* Do the job */ ! cluster_rel(tableOid, indexOid, false, stmt->verbose, -1, -1, &cmd); } else { *************** *** 185,190 **** cluster(ClusterStmt *stmt, bool isTopLevel) --- 189,198 ---- List *rvs; ListCell *rv; + /* Call BEFORE CLUSTER command triggers, all slots are NULL */ + if (CommandFiresTriggers(&cmd)) + ExecBeforeCommandTriggers(&cmd); + /* * We cannot run this form of CLUSTER inside a user transaction block; * we'd be holding locks way too long. *************** *** 223,229 **** cluster(ClusterStmt *stmt, bool isTopLevel) /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); cluster_rel(rvtc->tableOid, rvtc->indexOid, true, stmt->verbose, ! -1, -1); PopActiveSnapshot(); CommitTransactionCommand(); } --- 231,237 ---- /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); cluster_rel(rvtc->tableOid, rvtc->indexOid, true, stmt->verbose, ! -1, -1, NULL); PopActiveSnapshot(); CommitTransactionCommand(); } *************** *** 255,261 **** cluster(ClusterStmt *stmt, bool isTopLevel) */ void cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose, ! int freeze_min_age, int freeze_table_age) { Relation OldHeap; --- 263,269 ---- */ void cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose, ! int freeze_min_age, int freeze_table_age, CommandContext cmd) { Relation OldHeap; *************** *** 376,381 **** cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose, --- 384,399 ---- if (OidIsValid(indexOid)) check_index_is_clusterable(OldHeap, indexOid, recheck, AccessExclusiveLock); + /* Call BEFORE CLUSTER command trigger */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = RelationGetRelid(OldHeap); + cmd->objectname = RelationGetRelationName(OldHeap); + cmd->schemaname = get_namespace_name(RelationGetNamespace(OldHeap)); + + ExecBeforeCommandTriggers(cmd); + } + /* * All predicate locks on the tuples or pages are about to be made * invalid, because we move tuples around. Promote them to relation *************** *** 389,394 **** cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose, --- 407,417 ---- verbose); /* NB: rebuild_relation does heap_close() on OldHeap */ + + /* + * NB: we don't run AFTER CLUSTER command triggers because of transaction + * control issues + */ } /* *************** *** 1432,1438 **** finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE; if (check_constraints) reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS; ! reindex_relation(OIDOldHeap, reindex_flags); /* Destroy new heap with old filenode */ object.classId = RelationRelationId; --- 1455,1461 ---- reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE; if (check_constraints) reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS; ! reindex_relation(OIDOldHeap, reindex_flags, NULL); /* Destroy new heap with old filenode */ object.classId = RelationRelationId; *************** *** 1486,1498 **** finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u", OIDOldHeap); RenameRelationInternal(newrel->rd_rel->reltoastrelid, ! NewToastName); /* ... and its index too */ snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u_index", OIDOldHeap); RenameRelationInternal(toastidx, ! NewToastName); } relation_close(newrel, NoLock); } --- 1509,1521 ---- snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u", OIDOldHeap); RenameRelationInternal(newrel->rd_rel->reltoastrelid, ! NewToastName, NULL); /* ... and its index too */ snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u_index", OIDOldHeap); RenameRelationInternal(toastidx, ! NewToastName, NULL); } relation_close(newrel, NoLock); } *** /dev/null --- b/src/backend/commands/cmdtrigger.c *************** *** 0 **** --- 1,732 ---- + /*------------------------------------------------------------------------- + * + * cmdtrigger.c + * PostgreSQL COMMAND TRIGGER support code. + * + * Portions Copyright (c) 2011, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/commands/cmdtrigger.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "access/heapam.h" + #include "access/sysattr.h" + #include "catalog/catalog.h" + #include "catalog/dependency.h" + #include "catalog/indexing.h" + #include "catalog/objectaccess.h" + #include "catalog/pg_cmdtrigger.h" + #include "catalog/pg_language.h" + #include "catalog/pg_proc.h" + #include "catalog/pg_trigger.h" + #include "catalog/pg_type.h" + #include "commands/cmdtrigger.h" + #include "commands/dbcommands.h" + #include "commands/trigger.h" + #include "parser/parse_func.h" + #include "pgstat.h" + #include "miscadmin.h" + #include "utils/acl.h" + #include "utils/builtins.h" + #include "utils/fmgroids.h" + #include "utils/lsyscache.h" + #include "utils/memutils.h" + #include "utils/rel.h" + #include "utils/tqual.h" + #include "utils/syscache.h" + #include "tcop/utility.h" + + static void check_cmdtrigger_name(const char *trigname, Relation tgrel); + + /* + * Check permission: command triggers are only available for superusers. Raise + * an exception when requirements are not fullfilled. + * + * It's not clear how to accept that database owners be able to create command + * triggers, a superuser could run a command that fires a trigger's procedure + * written by the database owner and now running with superuser privileges. + */ + static void + CheckCmdTriggerPrivileges() + { + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to use command triggers")))); + } + + /* + * Insert Command Trigger Tuple + * + * Insert the new pg_cmdtrigger row, and return the OID assigned to the new + * row. + */ + static Oid + InsertCmdTriggerTuple(Relation tgrel, + char *command, char *trigname, Oid funcoid, char ctgtype) + { + Oid trigoid; + HeapTuple tuple; + Datum values[Natts_pg_trigger]; + bool nulls[Natts_pg_trigger]; + ObjectAddress myself, referenced; + + /* + * Build the new pg_trigger tuple. + */ + memset(nulls, false, sizeof(nulls)); + + values[Anum_pg_cmdtrigger_ctgcommand - 1] = NameGetDatum(command); + values[Anum_pg_cmdtrigger_ctgname - 1] = NameGetDatum(trigname); + values[Anum_pg_cmdtrigger_ctgfoid - 1] = ObjectIdGetDatum(funcoid); + values[Anum_pg_cmdtrigger_ctgtype - 1] = CharGetDatum(ctgtype); + values[Anum_pg_cmdtrigger_ctgenabled - 1] = CharGetDatum(TRIGGER_FIRES_ON_ORIGIN); + + tuple = heap_form_tuple(tgrel->rd_att, values, nulls); + + simple_heap_insert(tgrel, tuple); + + CatalogUpdateIndexes(tgrel, tuple); + + /* remember oid for record dependencies */ + trigoid = HeapTupleGetOid(tuple); + + heap_freetuple(tuple); + + /* + * Record dependencies for trigger. Always place a normal dependency on + * the function. + */ + myself.classId = CmdTriggerRelationId; + myself.objectId = trigoid; + myself.objectSubId = 0; + + referenced.classId = ProcedureRelationId; + referenced.objectId = funcoid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + return trigoid; + } + + /* + * Create a trigger. Returns the OID of the created trigger. + */ + Oid + CreateCmdTrigger(CreateCmdTrigStmt *stmt, const char *queryString) + { + Relation tgrel; + Oid funcoid, trigoid; + Oid funcrettype; + + CheckCmdTriggerPrivileges(); + + /* + * Find and validate the trigger function. When the function is coded in C + * it receives an internal argument which is the parse tree as a Node *. + * + * Only C coded functions can accept an argument of type internal, so we + * don't have to explicitely check about the prolang here. + */ + funcoid = LookupFuncName(stmt->funcname, 0, NULL, true); + + /* we need the trigger type to validate the return type */ + funcrettype = get_func_rettype(funcoid); + + /* + * Generate the trigger's OID now, so that we can use it in the name if + * needed. + */ + tgrel = heap_open(CmdTriggerRelationId, RowExclusiveLock); + + /* + * Scan pg_cmdtrigger for existing triggers on command. We do this only + * to give a nice error message if there's already a trigger of the + * same name. (The unique index on ctgcommand/ctgname would complain + * anyway.) + * + * NOTE that this is cool only because we have AccessExclusiveLock on + * the relation, so the trigger set won't be changing underneath us. + */ + check_cmdtrigger_name(stmt->trigname, tgrel); + + /* + * Add some restrictions. We don't allow for AFTER command triggers on + * commands that do their own transaction management, such as VACUUM and + * CREATE INDEX CONCURRENTLY, because RAISE EXCEPTION at this point is + * meaningless, the work as already been commited. + * + * CREATE INDEX CONCURRENTLY has no specific command tag and can not be + * captured here, so we just document that not AFTER command trigger + * will get run. + */ + if (stmt->timing == CMD_TRIGGER_FIRED_AFTER + && (strcmp(stmt->command, "VACUUM") == 0)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("AFTER VACUUM command triggers are not implemented"))); + + if (stmt->timing == CMD_TRIGGER_FIRED_AFTER + && (strcmp(stmt->command, "CLUSTER") == 0)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("AFTER CLUSTER command triggers are not implemented"))); + + if (stmt->timing == CMD_TRIGGER_FIRED_AFTER + && (strcmp(stmt->command, "CREATE INDEX") == 0)) + ereport(WARNING, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("AFTER CREATE INDEX CONCURRENTLY triggers are not supported"), + errdetail("The command trigger will not fire on concurrently-created indexes."))); + + if (strcmp(stmt->command, "REINDEX") == 0) + ereport(WARNING, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("REINDEX DATABASE triggers are not supported"), + errdetail("The command trigger will not fire on REINDEX DATABASE."))); + + if (funcrettype != CMDTRIGGEROID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("function \"%s\" must return type \"command_trigger\"", + NameListToString(stmt->funcname)))); + + trigoid = InsertCmdTriggerTuple(tgrel, stmt->command, stmt->trigname, + funcoid, stmt->timing); + + heap_close(tgrel, RowExclusiveLock); + + return trigoid; + } + + /* + * Guts of command trigger deletion. + */ + void + RemoveCmdTriggerById(Oid trigOid) + { + Relation tgrel; + SysScanDesc tgscan; + ScanKeyData skey[1]; + HeapTuple tup; + + tgrel = heap_open(CmdTriggerRelationId, RowExclusiveLock); + + /* + * Find the trigger to delete. + */ + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(trigOid)); + + tgscan = systable_beginscan(tgrel, CmdTriggerOidIndexId, true, + SnapshotNow, 1, skey); + + tup = systable_getnext(tgscan); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for command trigger %u", trigOid); + + /* + * Delete the pg_cmdtrigger tuple. + */ + simple_heap_delete(tgrel, &tup->t_self); + + systable_endscan(tgscan); + heap_close(tgrel, RowExclusiveLock); + } + + /* + * ALTER TRIGGER foo ON COMMAND ... ENABLE|DISABLE|ENABLE ALWAYS|REPLICA + */ + void + AlterCmdTrigger(AlterCmdTrigStmt *stmt) + { + Relation tgrel; + SysScanDesc tgscan; + ScanKeyData skey[1]; + HeapTuple tup; + Form_pg_cmdtrigger cmdForm; + char tgenabled = pstrdup(stmt->tgenabled)[0]; /* works with gram.y */ + + CheckCmdTriggerPrivileges(); + + tgrel = heap_open(CmdTriggerRelationId, RowExclusiveLock); + ScanKeyInit(&skey[0], + Anum_pg_cmdtrigger_ctgname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->trigname)); + + tgscan = systable_beginscan(tgrel, CmdTriggerNameIndexId, true, + SnapshotNow, 1, skey); + + tup = systable_getnext(tgscan); + + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("trigger \"%s\" does not exist, skipping", + stmt->trigname))); + + /* Copy tuple so we can modify it below */ + tup = heap_copytuple(tup); + cmdForm = (Form_pg_cmdtrigger) GETSTRUCT(tup); + + systable_endscan(tgscan); + + cmdForm->ctgenabled = tgenabled; + + simple_heap_update(tgrel, &tup->t_self, tup); + CatalogUpdateIndexes(tgrel, tup); + + heap_close(tgrel, RowExclusiveLock); + heap_freetuple(tup); + } + + + /* + * Rename command trigger + */ + void + RenameCmdTrigger(List *name, const char *newname) + { + SysScanDesc tgscan; + ScanKeyData skey[1]; + HeapTuple tup; + Relation rel; + Form_pg_cmdtrigger cmdForm; + char *trigname; + + Assert(list_length(name) == 1); + trigname = strVal((Value *)linitial(name)); + + CheckCmdTriggerPrivileges(); + + rel = heap_open(CmdTriggerRelationId, RowExclusiveLock); + + /* newname must be available */ + check_cmdtrigger_name(newname, rel); + + /* get existing tuple */ + ScanKeyInit(&skey[0], + Anum_pg_cmdtrigger_ctgname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(trigname)); + + tgscan = systable_beginscan(rel, CmdTriggerNameIndexId, true, + SnapshotNow, 1, skey); + + tup = systable_getnext(tgscan); + + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("command trigger \"%s\" does not exist, skipping", + trigname))); + + /* Copy tuple so we can modify it below */ + tup = heap_copytuple(tup); + cmdForm = (Form_pg_cmdtrigger) GETSTRUCT(tup); + + systable_endscan(tgscan); + + /* rename */ + namestrcpy(&(cmdForm->ctgname), newname); + simple_heap_update(rel, &tup->t_self, tup); + CatalogUpdateIndexes(rel, tup); + + heap_freetuple(tup); + heap_close(rel, NoLock); + } + + /* + * get_cmdtrigger_oid - Look up a trigger by name to find its OID. + * + * If missing_ok is false, throw an error if trigger not found. If + * true, just return InvalidOid. + */ + Oid + get_cmdtrigger_oid(const char *trigname, bool missing_ok) + { + Relation tgrel; + ScanKeyData skey[1]; + SysScanDesc tgscan; + HeapTuple tup; + Oid oid; + + /* + * Find the trigger, verify permissions, set up object address + */ + tgrel = heap_open(CmdTriggerRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_cmdtrigger_ctgname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(trigname)); + + tgscan = systable_beginscan(tgrel, CmdTriggerNameIndexId, true, + SnapshotNow, 1, skey); + + tup = systable_getnext(tgscan); + + if (!HeapTupleIsValid(tup)) + { + if (!missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("command trigger \"%s\" does not exist, skipping", + trigname))); + oid = InvalidOid; + } + else + { + oid = HeapTupleGetOid(tup); + } + + systable_endscan(tgscan); + heap_close(tgrel, AccessShareLock); + return oid; + } + + /* + * Scan pg_cmdtrigger for existing triggers on command. We do this only to + * give a nice error message if there's already a trigger of the same name. + */ + void + check_cmdtrigger_name(const char *trigname, Relation tgrel) + { + SysScanDesc tgscan; + ScanKeyData skey[1]; + HeapTuple tuple; + + ScanKeyInit(&skey[0], + Anum_pg_cmdtrigger_ctgname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(trigname)); + + tgscan = systable_beginscan(tgrel, CmdTriggerNameIndexId, true, + SnapshotNow, 1, skey); + + tuple = systable_getnext(tgscan); + + if (HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("command trigger \"%s\" already exists", trigname))); + systable_endscan(tgscan); + } + + /* + * Functions to execute the command triggers. + * + * We call the functions that matches the command triggers definitions in + * alphabetical order, and give them those arguments: + * + * command tag, text + * objectId, oid + * schemaname, text + * objectname, text + * + */ + + /* + * Scan the catalogs and fill in the CommandContext procedures that we will + * have to call before and after the command. + */ + static bool + ListCommandTriggers(CommandContext cmd, bool list_any_triggers) + { + int count = 0; + Relation rel, irel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + char *tag; + + if (list_any_triggers) + { + tag = "ANY"; + cmd->before_any = cmd->after_any = NIL; + } + else + { + tag = cmd->tag; + cmd->before = cmd->after = NIL; + } + + rel = heap_open(CmdTriggerRelationId, AccessShareLock); + irel = index_open(CmdTriggerCommandNameIndexId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_cmdtrigger_ctgcommand, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(tag)); + + scandesc = systable_beginscan_ordered(rel, irel, SnapshotNow, 1, entry); + + while (HeapTupleIsValid(tuple = systable_getnext_ordered(scandesc, ForwardScanDirection))) + { + Form_pg_cmdtrigger form = (Form_pg_cmdtrigger) GETSTRUCT(tuple); + + if (form->ctgenabled == TRIGGER_DISABLED) + { + continue; + } + else if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA) + { + if (form->ctgenabled == TRIGGER_FIRES_ON_ORIGIN) + continue; + } + else /* ORIGIN or LOCAL role */ + { + if (form->ctgenabled == TRIGGER_FIRES_ON_REPLICA) + continue; + } + + switch (form->ctgtype) + { + case CMD_TRIGGER_FIRED_BEFORE: + { + if (list_any_triggers) + cmd->before_any = lappend_oid(cmd->before_any, form->ctgfoid); + else + cmd->before = lappend_oid(cmd->before, form->ctgfoid); + break; + } + case CMD_TRIGGER_FIRED_AFTER: + { + if (list_any_triggers) + cmd->after_any = lappend_oid(cmd->after_any, form->ctgfoid); + else + cmd->after = lappend_oid(cmd->after, form->ctgfoid); + break; + } + } + count++; + } + systable_endscan_ordered(scandesc); + + index_close(irel, AccessShareLock); + heap_close(rel, AccessShareLock); + + return count > 0; + } + + static void + call_cmdtrigger_procedure(CommandContext cmd, RegProcedure proc, const char *when) + { + FmgrInfo flinfo; + FunctionCallInfoData fcinfo; + PgStat_FunctionCallUsage fcusage; + CommandTriggerData trigdata; + + fmgr_info(proc, &flinfo); + + /* + * Prepare the command trigger function context from the Command Context. + * We prepare a dedicated Node here so as not to publish internal data. + */ + trigdata.type = T_CommandTriggerData; + trigdata.when = (char *)when; + trigdata.tag = cmd->tag; + trigdata.objectId = cmd->objectId; + trigdata.schemaname = cmd->schemaname; + trigdata.objectname = cmd->objectname; + trigdata.parsetree = cmd->parsetree; + + /* + * Call the function, passing no arguments but setting a context. + */ + InitFunctionCallInfoData(fcinfo, &flinfo, 0, InvalidOid, + (Node *) &trigdata, NULL); + + pgstat_init_function_usage(&fcinfo, &fcusage); + FunctionCallInvoke(&fcinfo); + pgstat_end_function_usage(&fcusage, true); + + return; + } + + /* + * Execute the procedures attached to the command. We pass the list of + * procedures to use (either cmd->before or cmd->after) explicitely. + * + * The when argument allows to fill the trigger special variables. + */ + static void + exec_command_triggers_internal(CommandContext cmd, char when) + { + List *procs[2]; + char *whenstr; + ListCell *cell; + int i; + + switch (when) + { + case CMD_TRIGGER_FIRED_BEFORE: + whenstr = "BEFORE"; + procs[0] = cmd->before_any; + procs[1] = cmd->before; + break; + + case CMD_TRIGGER_FIRED_AFTER: + whenstr = "AFTER"; + procs[0] = cmd->after; + procs[1] = cmd->after_any; + break; + + default: + elog(ERROR, "unrecognized command trigger condition: %c", when); + break; + } + + for(i=0; i<2; i++) + { + foreach(cell, procs[i]) + { + Oid proc = lfirst_oid(cell); + + call_cmdtrigger_procedure(cmd, (RegProcedure)proc, whenstr); + } + } + } + + /* + * Routine to call to setup a CommandContextData structure. + * + * This ensures that cmd->before and cmd->after are set to meaningful values. + */ + void + InitCommandContext(CommandContext cmd, const Node *stmt) + { + cmd->tag = (char *) CreateCommandTag((Node *)stmt); + cmd->parsetree = (Node *)stmt; + cmd->objectId = InvalidOid; + cmd->objectname = NULL; + cmd->schemaname = NULL; + cmd->before = NIL; + cmd->after = NIL; + cmd->before_any = NIL; + cmd->after_any = NIL; + cmd->oldmctx = NULL; + cmd->cmdmctx = NULL; + + /* Specifically drop support for command triggers on command triggers */ + switch (nodeTag(stmt)) + { + case T_RenameStmt: + if (((RenameStmt *) stmt)->renameType == OBJECT_CMDTRIGGER) + return; + + case T_DropStmt: + if (((DropStmt *) stmt)->removeType == OBJECT_CMDTRIGGER) + return; + + case T_IndexStmt: + if (((IndexStmt *)stmt)->concurrent) + return; + + default: + ListCommandTriggers(cmd, true); /* list ANY command triggers */ + ListCommandTriggers(cmd, false); /* and triggers for this command tag */ + } + } + + /* + * InitCommandContext() must have been called when CommandFiresTriggers() is + * called. When CommandFiresTriggers() returns false, cmd structure needs not + * be initialized further. + * + * There's no place where we can skip BEFORE command trigger initialization + * when we have an AFTER command triggers to run, because objectname and + * schemaname are needed in both places, so we check both here. + * + * Integration is always on the form: + * + * if (CommandFiresTriggers(cmd) + * { + * cmd->objectname = pstrdup(...); + * ... + * + * ExecBeforeCommandTriggers(cmd); + * } + * + * The same applies to after command triggers, so that we are able to switch + * Memory contexts all from here. + */ + bool + CommandFiresTriggers(CommandContext cmd) + { + if (cmd == NULL) + return false; + + if (cmd->before != NIL || cmd->before_any != NIL + || cmd->after != NIL || cmd->after_any != NIL) + { + cmd->oldmctx = CurrentMemoryContext; + cmd->cmdmctx = + AllocSetContextCreate(CurrentMemoryContext, + "CommandTriggerContext", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + MemoryContextSwitchTo(cmd->cmdmctx); + + return true; + } + return false; + } + + /* + * It's still interresting to avoid preparing the Command Context for AFTER + * command triggers when we have none to Execute, so we provide this API too. + */ + bool + CommandFiresAfterTriggers(CommandContext cmd) + { + if (cmd == NULL) + return false; + + if (cmd->before != NIL || cmd->before_any != NIL + || cmd->after != NIL || cmd->after_any != NIL) + { + MemoryContextSwitchTo(cmd->cmdmctx); + return true; + } + return false; + } + + /* + * In the various Exec...CommandTriggers functions, we still protect against + * and empty procedure list so as not to create a MemoryContext then switch to + * it unnecessarily. + */ + void + ExecBeforeCommandTriggers(CommandContext cmd) + { + if (cmd == NULL) + return; + + /* that will execute under command trigger memory context */ + exec_command_triggers_internal(cmd, CMD_TRIGGER_FIRED_BEFORE); + + /* switch back to the command Memory Context now */ + MemoryContextSwitchTo(cmd->oldmctx); + } + + void + ExecAfterCommandTriggers(CommandContext cmd) + { + if (cmd == NULL) + return; + + /* that will execute under command trigger memory context */ + exec_command_triggers_internal(cmd, CMD_TRIGGER_FIRED_AFTER); + + /* switch back to the command Memory Context now */ + MemoryContextSwitchTo(cmd->oldmctx); + } *** a/src/backend/commands/collationcmds.c --- b/src/backend/commands/collationcmds.c *************** *** 22,27 **** --- 22,28 ---- #include "catalog/pg_collation.h" #include "catalog/pg_collation_fn.h" #include "commands/alter.h" + #include "commands/cmdtrigger.h" #include "commands/collationcmds.h" #include "commands/dbcommands.h" #include "commands/defrem.h" *************** *** 34,46 **** #include "utils/syscache.h" static void AlterCollationOwner_internal(Relation rel, Oid collationOid, ! Oid newOwnerId); /* * CREATE COLLATION */ void ! DefineCollation(List *names, List *parameters) { char *collName; Oid collNamespace; --- 35,47 ---- #include "utils/syscache.h" static void AlterCollationOwner_internal(Relation rel, Oid collationOid, ! Oid newOwnerId, CommandContext cmd); /* * CREATE COLLATION */ void ! DefineCollation(List *names, List *parameters, CommandContext cmd) { char *collName; Oid collNamespace; *************** *** 137,154 **** DefineCollation(List *names, List *parameters) GetUserId(), GetDatabaseEncoding(), collcollate, ! collctype); ! /* check that the locales can be loaded */ ! CommandCounterIncrement(); ! (void) pg_newlocale_from_collation(newoid); } /* * Rename collation */ void ! RenameCollation(List *name, const char *newname) { Oid collationOid; Oid namespaceOid; --- 138,160 ---- GetUserId(), GetDatabaseEncoding(), collcollate, ! collctype, ! cmd); ! /* before or instead of command trigger might have cancelled the command */ ! if (OidIsValid(newoid)) ! { ! /* check that the locales can be loaded */ ! CommandCounterIncrement(); ! (void) pg_newlocale_from_collation(newoid); ! } } /* * Rename collation */ void ! RenameCollation(List *name, const char *newname, CommandContext cmd) { Oid collationOid; Oid namespaceOid; *************** *** 200,220 **** RenameCollation(List *name, const char *newname) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); /* rename */ namestrcpy(&(((Form_pg_collation) GETSTRUCT(tup))->collname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); heap_freetuple(tup); - heap_close(rel, RowExclusiveLock); } /* * Change collation owner, by name */ void ! AlterCollationOwner(List *name, Oid newOwnerId) { Oid collationOid; Relation rel; --- 206,242 ---- aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* Call BEFORE ALTER COLLATION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr((((Form_pg_collation) GETSTRUCT(tup))->collname))); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + /* rename */ namestrcpy(&(((Form_pg_collation) GETSTRUCT(tup))->collname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); heap_freetuple(tup); heap_close(rel, RowExclusiveLock); + + /* Call AFTER ALTER COLLATION triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } /* * Change collation owner, by name */ void ! AlterCollationOwner(List *name, Oid newOwnerId, CommandContext cmd) { Oid collationOid; Relation rel; *************** *** 223,229 **** AlterCollationOwner(List *name, Oid newOwnerId) collationOid = get_collation_oid(name, false); ! AlterCollationOwner_internal(rel, collationOid, newOwnerId); heap_close(rel, RowExclusiveLock); } --- 245,251 ---- collationOid = get_collation_oid(name, false); ! AlterCollationOwner_internal(rel, collationOid, newOwnerId, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 232,244 **** AlterCollationOwner(List *name, Oid newOwnerId) * Change collation owner, by oid */ void ! AlterCollationOwner_oid(Oid collationOid, Oid newOwnerId) { Relation rel; rel = heap_open(CollationRelationId, RowExclusiveLock); ! AlterCollationOwner_internal(rel, collationOid, newOwnerId); heap_close(rel, RowExclusiveLock); } --- 254,266 ---- * Change collation owner, by oid */ void ! AlterCollationOwner_oid(Oid collationOid, Oid newOwnerId, CommandContext cmd) { Relation rel; rel = heap_open(CollationRelationId, RowExclusiveLock); ! AlterCollationOwner_internal(rel, collationOid, newOwnerId, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 250,256 **** AlterCollationOwner_oid(Oid collationOid, Oid newOwnerId) * open and suitably locked; it will not be closed. */ static void ! AlterCollationOwner_internal(Relation rel, Oid collationOid, Oid newOwnerId) { Form_pg_collation collForm; HeapTuple tup; --- 272,279 ---- * open and suitably locked; it will not be closed. */ static void ! AlterCollationOwner_internal(Relation rel, Oid collationOid, Oid newOwnerId, ! CommandContext cmd) { Form_pg_collation collForm; HeapTuple tup; *************** *** 291,296 **** AlterCollationOwner_internal(Relation rel, Oid collationOid, Oid newOwnerId) --- 314,329 ---- get_namespace_name(collForm->collnamespace)); } + /* Call BEFORE ALTER COLLATION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(collForm->collname)); + cmd->schemaname = get_namespace_name(collForm->collnamespace); + + ExecBeforeCommandTriggers(cmd); + } + /* * Modify the owner --- okay to scribble on tup because it's a copy */ *************** *** 303,310 **** AlterCollationOwner_internal(Relation rel, Oid collationOid, Oid newOwnerId) /* Update owner dependency reference */ changeDependencyOnOwner(CollationRelationId, collationOid, newOwnerId); - } heap_freetuple(tup); } --- 336,346 ---- /* Update owner dependency reference */ changeDependencyOnOwner(CollationRelationId, collationOid, newOwnerId); + /* Call AFTER ALTER COLLATION triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); + } heap_freetuple(tup); } *************** *** 312,318 **** AlterCollationOwner_internal(Relation rel, Oid collationOid, Oid newOwnerId) * Execute ALTER COLLATION SET SCHEMA */ void ! AlterCollationNamespace(List *name, const char *newschema) { Oid collOid, nspOid; --- 348,354 ---- * Execute ALTER COLLATION SET SCHEMA */ void ! AlterCollationNamespace(List *name, const char *newschema, CommandContext cmd) { Oid collOid, nspOid; *************** *** 321,334 **** AlterCollationNamespace(List *name, const char *newschema) nspOid = LookupCreationNamespace(newschema); ! AlterCollationNamespace_oid(collOid, nspOid); } /* * Change collation schema, by oid */ Oid ! AlterCollationNamespace_oid(Oid collOid, Oid newNspOid) { Oid oldNspOid; Relation rel; --- 357,370 ---- nspOid = LookupCreationNamespace(newschema); ! AlterCollationNamespace_oid(collOid, nspOid, cmd); } /* * Change collation schema, by oid */ Oid ! AlterCollationNamespace_oid(Oid collOid, Oid newNspOid, CommandContext cmd) { Oid oldNspOid; Relation rel; *************** *** 374,380 **** AlterCollationNamespace_oid(Oid collOid, Oid newNspOid) Anum_pg_collation_collname, Anum_pg_collation_collnamespace, Anum_pg_collation_collowner, ! ACL_KIND_COLLATION); heap_close(rel, RowExclusiveLock); --- 410,416 ---- Anum_pg_collation_collname, Anum_pg_collation_collnamespace, Anum_pg_collation_collowner, ! ACL_KIND_COLLATION, cmd); heap_close(rel, RowExclusiveLock); *** a/src/backend/commands/conversioncmds.c --- b/src/backend/commands/conversioncmds.c *************** *** 31,37 **** #include "utils/syscache.h" static void AlterConversionOwner_internal(Relation rel, Oid conversionOid, ! Oid newOwnerId); /* * CREATE CONVERSION --- 31,37 ---- #include "utils/syscache.h" static void AlterConversionOwner_internal(Relation rel, Oid conversionOid, ! Oid newOwnerId, CommandContext cmd); /* * CREATE CONVERSION *************** *** 39,45 **** static void AlterConversionOwner_internal(Relation rel, Oid conversionOid, void CreateConversionCommand(CreateConversionStmt *stmt) { ! Oid namespaceId; char *conversion_name; AclResult aclresult; int from_encoding; --- 39,45 ---- void CreateConversionCommand(CreateConversionStmt *stmt) { ! Oid namespaceId, convOid; char *conversion_name; AclResult aclresult; int from_encoding; *************** *** 50,55 **** CreateConversionCommand(CreateConversionStmt *stmt) --- 50,56 ---- List *func_name = stmt->func_name; static Oid funcargs[] = {INT4OID, INT4OID, CSTRINGOID, INTERNALOID, INT4OID}; char result[1]; + CommandContextData cmd; /* Convert list of names to a name and namespace */ namespaceId = QualifiedNameGetCreationNamespace(stmt->conversion_name, *************** *** 109,127 **** CreateConversionCommand(CreateConversionStmt *stmt) CStringGetDatum(result), Int32GetDatum(0)); /* * All seem ok, go ahead (possible failure would be a duplicate conversion * name) */ ! ConversionCreate(conversion_name, namespaceId, GetUserId(), ! from_encoding, to_encoding, funcoid, stmt->def); } /* * Rename conversion */ void ! RenameConversion(List *name, const char *newname) { Oid conversionOid; Oid namespaceOid; --- 110,147 ---- CStringGetDatum(result), Int32GetDatum(0)); + /* Call BEFORE CREATE CONVERSION command triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = conversion_name; + cmd.schemaname = get_namespace_name(namespaceId); + + ExecBeforeCommandTriggers(&cmd); + } + /* * All seem ok, go ahead (possible failure would be a duplicate conversion * name) */ ! convOid = ConversionCreate(conversion_name, namespaceId, GetUserId(), ! from_encoding, to_encoding, funcoid, stmt->def); ! ! /* Call AFTER CREATE CONVERSION command triggers */ ! if (CommandFiresAfterTriggers(&cmd)) ! { ! cmd.objectId = convOid; ! ExecAfterCommandTriggers(&cmd); ! } } /* * Rename conversion */ void ! RenameConversion(List *name, const char *newname, CommandContext cmd) { Oid conversionOid; Oid namespaceOid; *************** *** 159,164 **** RenameConversion(List *name, const char *newname) --- 179,194 ---- aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* Call BEFORE ALTER CONVERSION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr((((Form_pg_conversion) GETSTRUCT(tup))->conname))); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + /* rename */ namestrcpy(&(((Form_pg_conversion) GETSTRUCT(tup))->conname), newname); simple_heap_update(rel, &tup->t_self, tup); *************** *** 166,178 **** RenameConversion(List *name, const char *newname) heap_close(rel, NoLock); heap_freetuple(tup); } /* * Change conversion owner, by name */ void ! AlterConversionOwner(List *name, Oid newOwnerId) { Oid conversionOid; Relation rel; --- 196,215 ---- heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER CONVERSION triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } /* * Change conversion owner, by name */ void ! AlterConversionOwner(List *name, Oid newOwnerId, CommandContext cmd) { Oid conversionOid; Relation rel; *************** *** 181,187 **** AlterConversionOwner(List *name, Oid newOwnerId) conversionOid = get_conversion_oid(name, false); ! AlterConversionOwner_internal(rel, conversionOid, newOwnerId); heap_close(rel, NoLock); } --- 218,224 ---- conversionOid = get_conversion_oid(name, false); ! AlterConversionOwner_internal(rel, conversionOid, newOwnerId, cmd); heap_close(rel, NoLock); } *************** *** 190,202 **** AlterConversionOwner(List *name, Oid newOwnerId) * Change conversion owner, by oid */ void ! AlterConversionOwner_oid(Oid conversionOid, Oid newOwnerId) { Relation rel; rel = heap_open(ConversionRelationId, RowExclusiveLock); ! AlterConversionOwner_internal(rel, conversionOid, newOwnerId); heap_close(rel, NoLock); } --- 227,239 ---- * Change conversion owner, by oid */ void ! AlterConversionOwner_oid(Oid conversionOid, Oid newOwnerId, CommandContext cmd) { Relation rel; rel = heap_open(ConversionRelationId, RowExclusiveLock); ! AlterConversionOwner_internal(rel, conversionOid, newOwnerId, cmd); heap_close(rel, NoLock); } *************** *** 208,214 **** AlterConversionOwner_oid(Oid conversionOid, Oid newOwnerId) * open and suitably locked; it will not be closed. */ static void ! AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId) { Form_pg_conversion convForm; HeapTuple tup; --- 245,252 ---- * open and suitably locked; it will not be closed. */ static void ! AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId, ! CommandContext cmd) { Form_pg_conversion convForm; HeapTuple tup; *************** *** 249,254 **** AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId) --- 287,302 ---- get_namespace_name(convForm->connamespace)); } + /* Call BEFORE ALTER CONVERSION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(convForm->conname)); + cmd->schemaname = get_namespace_name(convForm->connamespace); + + ExecBeforeCommandTriggers(cmd); + } + /* * Modify the owner --- okay to scribble on tup because it's a copy */ *************** *** 261,266 **** AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId) --- 309,318 ---- /* Update owner dependency reference */ changeDependencyOnOwner(ConversionRelationId, conversionOid, newOwnerId); + + /* Call AFTER ALTER CONVERSION triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } heap_freetuple(tup); *************** *** 270,276 **** AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId) * Execute ALTER CONVERSION SET SCHEMA */ void ! AlterConversionNamespace(List *name, const char *newschema) { Oid convOid, nspOid; --- 322,328 ---- * Execute ALTER CONVERSION SET SCHEMA */ void ! AlterConversionNamespace(List *name, const char *newschema, CommandContext cmd) { Oid convOid, nspOid; *************** *** 288,294 **** AlterConversionNamespace(List *name, const char *newschema) Anum_pg_conversion_conname, Anum_pg_conversion_connamespace, Anum_pg_conversion_conowner, ! ACL_KIND_CONVERSION); heap_close(rel, RowExclusiveLock); } --- 340,346 ---- Anum_pg_conversion_conname, Anum_pg_conversion_connamespace, Anum_pg_conversion_conowner, ! ACL_KIND_CONVERSION, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 309,315 **** AlterConversionNamespace_oid(Oid convOid, Oid newNspOid) Anum_pg_conversion_conname, Anum_pg_conversion_connamespace, Anum_pg_conversion_conowner, ! ACL_KIND_CONVERSION); heap_close(rel, RowExclusiveLock); --- 361,367 ---- Anum_pg_conversion_conname, Anum_pg_conversion_connamespace, Anum_pg_conversion_conowner, ! ACL_KIND_CONVERSION, NULL); heap_close(rel, RowExclusiveLock); *** a/src/backend/commands/createas.c --- b/src/backend/commands/createas.c *************** *** 47,52 **** typedef struct --- 47,53 ---- CommandId output_cid; /* cmin to insert in output tuples */ int hi_options; /* heap_insert performance options */ BulkInsertState bistate; /* bulk insert state */ + CommandContext cmd; /* Command Context, for command triggers */ } DR_intorel; static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo); *************** *** 69,79 **** ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, PlannedStmt *plan; QueryDesc *queryDesc; ScanDirection dir; /* * Create the tuple receiver object and insert info it will need */ ! dest = CreateIntoRelDestReceiver(into); /* * The contained Query could be a SELECT, or an EXECUTE utility command. --- 70,83 ---- PlannedStmt *plan; QueryDesc *queryDesc; ScanDirection dir; + CommandContextData cmd; /* * Create the tuple receiver object and insert info it will need */ ! InitCommandContext(&cmd, (Node *)stmt); ! ! dest = CreateIntoRelDestReceiver(into, &cmd); /* * The contained Query could be a SELECT, or an EXECUTE utility command. *************** *** 187,193 **** GetIntoRelEFlags(IntoClause *intoClause) * self->into to be filled in immediately for other callers. */ DestReceiver * ! CreateIntoRelDestReceiver(IntoClause *intoClause) { DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel)); --- 191,197 ---- * self->into to be filled in immediately for other callers. */ DestReceiver * ! CreateIntoRelDestReceiver(IntoClause *intoClause, CommandContext cmd) { DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel)); *************** *** 197,202 **** CreateIntoRelDestReceiver(IntoClause *intoClause) --- 201,207 ---- self->pub.rDestroy = intorel_destroy; self->pub.mydest = DestIntoRel; self->into = intoClause; + self->cmd = cmd; /* other private fields will be set during intorel_startup */ return (DestReceiver *) self; *************** *** 210,215 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) --- 215,221 ---- { DR_intorel *myState = (DR_intorel *) self; IntoClause *into = myState->into; + CommandContext cmd = myState->cmd; CreateStmt *create; Oid intoRelationId; Relation intoRelationDesc; *************** *** 303,309 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) /* * Actually create the target table */ ! intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid); /* * If necessary, create a TOAST table for the target table. Note that --- 309,315 ---- /* * Actually create the target table */ ! intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid, cmd); /* * If necessary, create a TOAST table for the target table. Note that *************** *** 408,413 **** intorel_shutdown(DestReceiver *self) --- 414,426 ---- if (myState->hi_options & HEAP_INSERT_SKIP_WAL) heap_sync(myState->rel); + /* Call AFTER CREATE TABLE AS command triggers */ + if (CommandFiresAfterTriggers(myState->cmd)) + { + myState->cmd->objectId = RelationGetRelid(myState->rel); + ExecAfterCommandTriggers(myState->cmd); + } + /* close rel, but keep lock until commit */ heap_close(myState->rel, NoLock); myState->rel = NULL; *** a/src/backend/commands/dbcommands.c --- b/src/backend/commands/dbcommands.c *************** *** 49,54 **** --- 49,55 ---- #include "storage/ipc.h" #include "storage/procarray.h" #include "storage/smgr.h" + #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" *************** *** 733,740 **** createdb_failure_callback(int code, Datum arg) * DROP DATABASE */ void ! dropdb(const char *dbname, bool missing_ok) { Oid db_id; bool db_istemplate; Relation pgdbrel; --- 734,743 ---- * DROP DATABASE */ void ! dropdb(const DropdbStmt *stmt) { + const char *dbname = stmt->dbname; + bool missing_ok = stmt->missing_ok; Oid db_id; bool db_istemplate; Relation pgdbrel; *** a/src/backend/commands/dropcmds.c --- b/src/backend/commands/dropcmds.c *************** *** 21,37 **** --- 21,44 ---- #include "catalog/objectaddress.h" #include "catalog/pg_class.h" #include "catalog/pg_proc.h" + #include "commands/cmdtrigger.h" #include "commands/defrem.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_type.h" + #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" + #include "utils/lsyscache.h" #include "utils/syscache.h" static void does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs); + static void get_object_name(CommandContext cmd, + ObjectType objtype, + Oid objectId, List *objname); + /* * Drop one or more objects. * *************** *** 49,54 **** RemoveObjects(DropStmt *stmt) --- 56,63 ---- ObjectAddresses *objects; ListCell *cell1; ListCell *cell2 = NULL; + int i = 0, n = list_length(stmt->objects); + CommandContext *cmds = (CommandContext *) palloc(n * sizeof(CommandContext)); objects = new_object_addresses(); *************** *** 59,64 **** RemoveObjects(DropStmt *stmt) --- 68,74 ---- List *objargs = NIL; Relation relation = NULL; Oid namespaceId; + CommandContextData cmd; if (stmt->arguments) { *************** *** 77,82 **** RemoveObjects(DropStmt *stmt) --- 87,93 ---- if (!OidIsValid(address.objectId)) { does_not_exist_skipping(stmt->removeType, objname, objargs); + cmds[i++] = NULL; continue; } *************** *** 115,126 **** RemoveObjects(DropStmt *stmt) --- 126,161 ---- if (relation) heap_close(relation, NoLock); + /* + * Call BEFORE DROP command triggers + */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = address.objectId; + get_object_name(&cmd, stmt->removeType, address.objectId, objname); + cmd.schemaname = get_namespace_name(namespaceId); + + ExecBeforeCommandTriggers(&cmd); + } + cmds[i++] = &cmd; + add_exact_object_address(&address, objects); } /* Here we really delete them. */ performMultipleDeletions(objects, stmt->behavior, 0); + /* Call AFTER DROP command triggers */ + for(i = 0; iobjectId = InvalidOid; + ExecAfterCommandTriggers(cmds[i]); + } + } free_object_addresses(objects); } *************** *** 206,211 **** does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs) --- 241,251 ---- args = NameListToString(list_truncate(objname, list_length(objname) - 1)); break; + case OBJECT_CMDTRIGGER: + msg = gettext_noop("trigger \"%s\" for command \"%s\" does not exist, skipping"); + name = NameListToString(objname); + args = strVal(linitial(objargs)); + break; case OBJECT_RULE: msg = gettext_noop("rule \"%s\" for relation \"%s\" does not exist, skipping"); name = strVal(llast(objname)); *************** *** 240,242 **** does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs) --- 280,335 ---- else ereport(NOTICE, (errmsg(msg, name, args))); } + + /* + * Fill in the CommandContext name, with the non-qualified part fo the name. + */ + static void + get_object_name(CommandContext cmd, ObjectType objtype, + Oid objectId, List *objname) + { + switch (objtype) + { + case OBJECT_TYPE: + case OBJECT_DOMAIN: + cmd->objectname = format_type_be_without_namespace(objectId); + break; + case OBJECT_CAST: + cmd->objectname = NULL; + break; + case OBJECT_RULE: + case OBJECT_TRIGGER: + cmd->objectname = pstrdup(strVal(llast(objname))); + break; + case OBJECT_COLLATION: + case OBJECT_CONVERSION: + case OBJECT_SCHEMA: + case OBJECT_TSPARSER: + case OBJECT_TSDICTIONARY: + case OBJECT_TSTEMPLATE: + case OBJECT_TSCONFIGURATION: + case OBJECT_EXTENSION: + case OBJECT_FUNCTION: + case OBJECT_AGGREGATE: + case OBJECT_OPERATOR: + case OBJECT_LANGUAGE: + case OBJECT_CMDTRIGGER: + case OBJECT_FDW: + case OBJECT_FOREIGN_SERVER: + case OBJECT_OPCLASS: + case OBJECT_OPFAMILY: + { + int len = list_length(objname); + if (len == 1) + cmd->objectname = pstrdup(strVal(linitial(objname))); + else if (len == 2) + cmd->objectname = pstrdup(strVal(lsecond(objname))); + else + elog(ERROR, "unexpected name list length (%d)", len); + break; + } + default: + elog(ERROR, "unexpected object type (%d)", (int)objtype); + break; + } + } *** a/src/backend/commands/explain.c --- b/src/backend/commands/explain.c *************** *** 169,175 **** ExplainQuery(ExplainStmt *stmt, const char *queryString, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("EXPLAIN option BUFFERS requires ANALYZE"))); ! /* if the timing was not set explicitly, set default value */ es.timing = (timing_set) ? es.timing : es.analyze; --- 169,175 ---- ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("EXPLAIN option BUFFERS requires ANALYZE"))); ! /* if the timing was not set explicitly, set default value */ es.timing = (timing_set) ? es.timing : es.analyze; *************** *** 423,429 **** ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, * AS, we'd better use the appropriate tuple receiver. */ if (into) ! dest = CreateIntoRelDestReceiver(into); else dest = None_Receiver; --- 423,429 ---- * AS, we'd better use the appropriate tuple receiver. */ if (into) ! dest = CreateIntoRelDestReceiver(into, NULL); else dest = None_Receiver; *** a/src/backend/commands/extension.c --- b/src/backend/commands/extension.c *************** *** 39,44 **** --- 39,45 ---- #include "catalog/pg_namespace.h" #include "catalog/pg_type.h" #include "commands/alter.h" + #include "commands/cmdtrigger.h" #include "commands/comment.h" #include "commands/extension.h" #include "commands/schemacmds.h" *************** *** 1190,1206 **** CreateExtension(CreateExtensionStmt *stmt) List *requiredSchemas; Oid extensionOid; ListCell *lc; /* Check extension name validity before any filesystem access */ check_valid_extension_name(stmt->extname); /* * Check for duplicate extension name. The unique index on * pg_extension.extname would catch this anyway, and serves as a backstop * in case of race conditions; but this is a friendlier error message, and * besides we need a check to support IF NOT EXISTS. */ ! if (get_extension_oid(stmt->extname, true) != InvalidOid) { if (stmt->if_not_exists) { --- 1191,1222 ---- List *requiredSchemas; Oid extensionOid; ListCell *lc; + CommandContextData cmd; /* Check extension name validity before any filesystem access */ check_valid_extension_name(stmt->extname); /* + * Call BEFORE CREATE EXTENSION triggers + */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = stmt->extname; + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } + /* * Check for duplicate extension name. The unique index on * pg_extension.extname would catch this anyway, and serves as a backstop * in case of race conditions; but this is a friendlier error message, and * besides we need a check to support IF NOT EXISTS. */ ! extensionOid = get_extension_oid(stmt->extname, true); ! if ( extensionOid != InvalidOid) { if (stmt->if_not_exists) { *************** *** 1208,1213 **** CreateExtension(CreateExtensionStmt *stmt) --- 1224,1236 ---- (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("extension \"%s\" already exists, skipping", stmt->extname))); + + /* Call AFTER CREATE EXTENSION triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = extensionOid; + ExecAfterCommandTriggers(&cmd); + } return; } else *************** *** 1467,1472 **** CreateExtension(CreateExtensionStmt *stmt) --- 1490,1502 ---- */ ApplyExtensionUpdates(extensionOid, pcontrol, versionName, updateVersions); + + /* Call AFTER CREATE EXTENSION triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = extensionOid; + ExecAfterCommandTriggers(&cmd); + } } /* *************** *** 2186,2192 **** pg_extension_config_dump(PG_FUNCTION_ARGS) * Execute ALTER EXTENSION SET SCHEMA */ void ! AlterExtensionNamespace(List *names, const char *newschema) { char *extensionName; Oid extensionOid; --- 2216,2222 ---- * Execute ALTER EXTENSION SET SCHEMA */ void ! AlterExtensionNamespace(List *names, const char *newschema, CommandContext cmd) { char *extensionName; Oid extensionOid; *************** *** 2247,2252 **** AlterExtensionNamespace(List *names, const char *newschema) --- 2277,2292 ---- systable_endscan(extScan); + /* Call BEFORE ALTER EXTENSION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = extensionOid; + cmd->objectname = extensionName; + cmd->schemaname = NULL; + + ExecBeforeCommandTriggers(cmd); + } + /* * If the extension is already in the target schema, just silently do * nothing. *************** *** 2342,2347 **** AlterExtensionNamespace(List *names, const char *newschema) --- 2382,2391 ---- /* update dependencies to point to the new schema */ changeDependencyFor(ExtensionRelationId, extensionOid, NamespaceRelationId, oldNspOid, nspOid); + + /* Call AFTER ALTER EXTENSION triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } /* *************** *** 2363,2368 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt) --- 2407,2413 ---- Datum datum; bool isnull; ListCell *lc; + CommandContextData cmd; /* * We use global variables to track the extension being created, so we can *************** *** 2415,2420 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt) --- 2460,2479 ---- stmt->extname); /* + * Call BEFORE ALTER EXTENSION triggers + */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = extensionOid; + cmd.objectname = stmt->extname; + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } + + /* * Read the primary control file. Note we assume that it does not contain * any non-ASCII data, so there is no need to worry about encoding at this * point. *************** *** 2480,2485 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt) --- 2539,2548 ---- */ ApplyExtensionUpdates(extensionOid, control, oldVersionName, updateVersions); + + /* Call AFTER ALTER EXTENSION triggers */ + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } /* *************** *** 2649,2654 **** ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt) --- 2712,2718 ---- ObjectAddress object; Relation relation; Oid oldExtension; + CommandContextData cmd; extension.classId = ExtensionRelationId; extension.objectId = get_extension_oid(stmt->extname, false); *************** *** 2677,2682 **** ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt) --- 2741,2753 ---- */ oldExtension = getExtensionOfObject(object.classId, object.objectId); + InitCommandContext(&cmd, (Node *)stmt); + + /* Init the command context no matter what, that's cheap here */ + cmd.objectId = extension.objectId; + cmd.objectname = stmt->extname; + cmd.schemaname = NULL; + if (stmt->action > 0) { /* *************** *** 2689,2694 **** ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt) --- 2760,2769 ---- getObjectDescription(&object), get_extension_name(oldExtension)))); + /* Call BEFORE ALTER EXTENSION command triggers */ + if (CommandFiresTriggers(&cmd)) + ExecBeforeCommandTriggers(&cmd); + /* * OK, add the dependency. */ *************** *** 2706,2711 **** ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt) --- 2781,2790 ---- getObjectDescription(&object), stmt->extname))); + /* Call BEFORE ALTER EXTENSION command triggers */ + if (CommandFiresTriggers(&cmd)) + ExecBeforeCommandTriggers(&cmd); + /* * OK, drop the dependency. */ *************** *** 2723,2726 **** ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt) --- 2802,2809 ---- */ if (relation != NULL) relation_close(relation, NoLock); + + /* Call AFTER ALTER EXTENSION command triggers */ + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } *** a/src/backend/commands/foreigncmds.c --- b/src/backend/commands/foreigncmds.c *************** *** 204,210 **** GetUserOidFromMapping(const char *username, bool missing_ok) * Rename foreign-data wrapper */ void ! RenameForeignDataWrapper(const char *oldname, const char *newname) { HeapTuple tup; Relation rel; --- 204,211 ---- * Rename foreign-data wrapper */ void ! RenameForeignDataWrapper(const char *oldname, const char *newname, ! CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 228,233 **** RenameForeignDataWrapper(const char *oldname, const char *newname) --- 229,244 ---- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FDW, oldname); + /* Call BEFORE ALTER FOREIGN DATA WRAPPER triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(oldname); + cmd->schemaname = NULL; + + ExecBeforeCommandTriggers(cmd); + } + /* rename */ namestrcpy(&(((Form_pg_foreign_data_wrapper) GETSTRUCT(tup))->fdwname), newname); simple_heap_update(rel, &tup->t_self, tup); *************** *** 235,240 **** RenameForeignDataWrapper(const char *oldname, const char *newname) --- 246,258 ---- heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER FOREIGN DATA WRAPPER triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } *************** *** 242,248 **** RenameForeignDataWrapper(const char *oldname, const char *newname) * Rename foreign server */ void ! RenameForeignServer(const char *oldname, const char *newname) { HeapTuple tup; Relation rel; --- 260,266 ---- * Rename foreign server */ void ! RenameForeignServer(const char *oldname, const char *newname, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 266,271 **** RenameForeignServer(const char *oldname, const char *newname) --- 284,299 ---- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, oldname); + /* Call BEFORE ALTER SERVER triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(oldname); + cmd->schemaname = NULL; + + ExecBeforeCommandTriggers(cmd); + } + /* rename */ namestrcpy(&(((Form_pg_foreign_server) GETSTRUCT(tup))->srvname), newname); simple_heap_update(rel, &tup->t_self, tup); *************** *** 273,278 **** RenameForeignServer(const char *oldname, const char *newname) --- 301,313 ---- heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER SERVER triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } *************** *** 283,289 **** RenameForeignServer(const char *oldname, const char *newname) * superuser. */ static void ! AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) { Form_pg_foreign_data_wrapper form; --- 318,325 ---- * superuser. */ static void ! AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId, ! CommandContext cmd) { Form_pg_foreign_data_wrapper form; *************** *** 305,310 **** AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerI --- 341,356 ---- NameStr(form->fdwname)), errhint("The owner of a foreign-data wrapper must be a superuser."))); + /* Call BEFORE ALTER FOREIGN DATA WRAPPER triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(form->fdwname)); + cmd->schemaname = NULL; + + ExecBeforeCommandTriggers(cmd); + } + if (form->fdwowner != newOwnerId) { form->fdwowner = newOwnerId; *************** *** 317,322 **** AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerI --- 363,372 ---- HeapTupleGetOid(tup), newOwnerId); } + + /* Call AFTER ALTER FOREIGN DATA WRAPPER triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } /* *************** *** 325,331 **** AlterForeignDataWrapperOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerI * Note restrictions in the "_internal" function, above. */ void ! AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId) { HeapTuple tup; Relation rel; --- 375,382 ---- * Note restrictions in the "_internal" function, above. */ void ! AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId, ! CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 339,345 **** AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId) (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("foreign-data wrapper \"%s\" does not exist", name))); ! AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId); heap_freetuple(tup); --- 390,396 ---- (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("foreign-data wrapper \"%s\" does not exist", name))); ! AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId, cmd); heap_freetuple(tup); *************** *** 366,372 **** AlterForeignDataWrapperOwner_oid(Oid fwdId, Oid newOwnerId) (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("foreign-data wrapper with OID %u does not exist", fwdId))); ! AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId); heap_freetuple(tup); --- 417,423 ---- (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("foreign-data wrapper with OID %u does not exist", fwdId))); ! AlterForeignDataWrapperOwner_internal(rel, tup, newOwnerId, NULL); heap_freetuple(tup); *************** *** 377,383 **** AlterForeignDataWrapperOwner_oid(Oid fwdId, Oid newOwnerId) * Internal workhorse for changing a foreign server's owner */ static void ! AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) { Form_pg_foreign_server form; --- 428,435 ---- * Internal workhorse for changing a foreign server's owner */ static void ! AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId, ! CommandContext cmd) { Form_pg_foreign_server form; *************** *** 411,416 **** AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) --- 463,478 ---- } } + /* Call BEFORE ALTER SERVER triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(form->srvname)); + cmd->schemaname = NULL; + + ExecBeforeCommandTriggers(cmd); + } + form->srvowner = newOwnerId; simple_heap_update(rel, &tup->t_self, tup); *************** *** 419,424 **** AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) --- 481,490 ---- /* Update owner dependency reference */ changeDependencyOnOwner(ForeignServerRelationId, HeapTupleGetOid(tup), newOwnerId); + + /* Call AFTER ALTER SERVER triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } } *************** *** 426,432 **** AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) * Change foreign server owner -- by name */ void ! AlterForeignServerOwner(const char *name, Oid newOwnerId) { HeapTuple tup; Relation rel; --- 492,498 ---- * Change foreign server owner -- by name */ void ! AlterForeignServerOwner(const char *name, Oid newOwnerId, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 440,446 **** AlterForeignServerOwner(const char *name, Oid newOwnerId) (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("server \"%s\" does not exist", name))); ! AlterForeignServerOwner_internal(rel, tup, newOwnerId); heap_freetuple(tup); --- 506,512 ---- (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("server \"%s\" does not exist", name))); ! AlterForeignServerOwner_internal(rel, tup, newOwnerId, cmd); heap_freetuple(tup); *************** *** 465,471 **** AlterForeignServerOwner_oid(Oid srvId, Oid newOwnerId) (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("foreign server with OID %u does not exist", srvId))); ! AlterForeignServerOwner_internal(rel, tup, newOwnerId); heap_freetuple(tup); --- 531,537 ---- (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("foreign server with OID %u does not exist", srvId))); ! AlterForeignServerOwner_internal(rel, tup, newOwnerId, NULL); heap_freetuple(tup); *************** *** 578,583 **** CreateForeignDataWrapper(CreateFdwStmt *stmt) --- 644,650 ---- Oid ownerId; ObjectAddress myself; ObjectAddress referenced; + CommandContextData cmd; rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock); *************** *** 601,606 **** CreateForeignDataWrapper(CreateFdwStmt *stmt) --- 668,685 ---- errmsg("foreign-data wrapper \"%s\" already exists", stmt->fdwname))); + /* Call BEFORE CREATE FOREIGN DATA WRAPPER triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = stmt->fdwname; + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } + /* * Insert tuple into pg_foreign_data_wrapper. */ *************** *** 669,674 **** CreateForeignDataWrapper(CreateFdwStmt *stmt) --- 748,760 ---- ForeignDataWrapperRelationId, fdwId, 0, NULL); heap_close(rel, RowExclusiveLock); + + /* Call AFTER CREATE FOREIGN DATA WRAPPER triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = fdwId; + ExecAfterCommandTriggers(&cmd); + } } *************** *** 691,696 **** AlterForeignDataWrapper(AlterFdwStmt *stmt) --- 777,783 ---- bool validator_given; Oid fdwhandler; Oid fdwvalidator; + CommandContextData cmd; rel = heap_open(ForeignDataWrapperRelationId, RowExclusiveLock); *************** *** 713,718 **** AlterForeignDataWrapper(AlterFdwStmt *stmt) --- 800,817 ---- fdwForm = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp); fdwId = HeapTupleGetOid(tp); + /* Call BEFORE ALTER FOREIGN DATA WRAPPER triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = fdwId; + cmd.objectname = stmt->fdwname; + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } + memset(repl_val, 0, sizeof(repl_val)); memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); *************** *** 830,835 **** AlterForeignDataWrapper(AlterFdwStmt *stmt) --- 929,938 ---- } heap_close(rel, RowExclusiveLock); + + /* Call AFTER ALTER FOREIGN DATA WRAPPER triggers */ + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } *************** *** 874,879 **** CreateForeignServer(CreateForeignServerStmt *stmt) --- 977,983 ---- ObjectAddress myself; ObjectAddress referenced; ForeignDataWrapper *fdw; + CommandContextData cmd; rel = heap_open(ForeignServerRelationId, RowExclusiveLock); *************** *** 899,904 **** CreateForeignServer(CreateForeignServerStmt *stmt) --- 1003,1020 ---- if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname); + /* Call BEFORE CREATE SERVER triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = stmt->servername; + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } + /* * Insert tuple into pg_foreign_server. */ *************** *** 966,971 **** CreateForeignServer(CreateForeignServerStmt *stmt) --- 1082,1094 ---- ForeignServerRelationId, srvId, 0, NULL); heap_close(rel, RowExclusiveLock); + + /* Call AFTER CREATE SERVER triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = srvId; + ExecAfterCommandTriggers(&cmd); + } } *************** *** 982,987 **** AlterForeignServer(AlterForeignServerStmt *stmt) --- 1105,1111 ---- bool repl_repl[Natts_pg_foreign_server]; Oid srvId; Form_pg_foreign_server srvForm; + CommandContextData cmd; rel = heap_open(ForeignServerRelationId, RowExclusiveLock); *************** *** 1003,1008 **** AlterForeignServer(AlterForeignServerStmt *stmt) --- 1127,1144 ---- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, stmt->servername); + /* Call BEFORE ALTER SERVER triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = srvId; + cmd.objectname = stmt->servername; + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } + memset(repl_val, 0, sizeof(repl_val)); memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); *************** *** 1059,1064 **** AlterForeignServer(AlterForeignServerStmt *stmt) --- 1195,1204 ---- heap_freetuple(tp); heap_close(rel, RowExclusiveLock); + + /* Call AFTER ALTER SERVER triggers */ + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } *************** *** 1130,1135 **** CreateUserMapping(CreateUserMappingStmt *stmt) --- 1270,1276 ---- ObjectAddress referenced; ForeignServer *srv; ForeignDataWrapper *fdw; + CommandContextData cmd; rel = heap_open(UserMappingRelationId, RowExclusiveLock); *************** *** 1155,1160 **** CreateUserMapping(CreateUserMappingStmt *stmt) --- 1296,1313 ---- fdw = GetForeignDataWrapper(srv->fdwid); + /* Call BEFORE CREATE USER MAPPING triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = NULL; /* composite object name */ + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } + /* * Insert tuple into pg_user_mapping. */ *************** *** 1207,1212 **** CreateUserMapping(CreateUserMappingStmt *stmt) --- 1360,1372 ---- UserMappingRelationId, umId, 0, NULL); heap_close(rel, RowExclusiveLock); + + /* Call AFTER CREATE USER MAPPING triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = umId; + ExecAfterCommandTriggers(&cmd); + } } *************** *** 1224,1229 **** AlterUserMapping(AlterUserMappingStmt *stmt) --- 1384,1390 ---- Oid useId; Oid umId; ForeignServer *srv; + CommandContextData cmd; rel = heap_open(UserMappingRelationId, RowExclusiveLock); *************** *** 1246,1251 **** AlterUserMapping(AlterUserMappingStmt *stmt) --- 1407,1424 ---- if (!HeapTupleIsValid(tp)) elog(ERROR, "cache lookup failed for user mapping %u", umId); + /* Call BEFORE ALTER USER MAPPING triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = umId; + cmd.objectname = NULL; /* composite object name */ + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } + memset(repl_val, 0, sizeof(repl_val)); memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); *************** *** 1293,1298 **** AlterUserMapping(AlterUserMappingStmt *stmt) --- 1466,1475 ---- heap_freetuple(tp); heap_close(rel, RowExclusiveLock); + + /* Call AFTER ALTER SERVER triggers */ + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } *************** *** 1306,1311 **** RemoveUserMapping(DropUserMappingStmt *stmt) --- 1483,1489 ---- Oid useId; Oid umId; ForeignServer *srv; + CommandContextData cmd; useId = GetUserOidFromMapping(stmt->username, stmt->missing_ok); srv = GetForeignServerByName(stmt->servername, true); *************** *** 1353,1358 **** RemoveUserMapping(DropUserMappingStmt *stmt) --- 1531,1548 ---- user_mapping_ddl_aclcheck(useId, srv->serverid, srv->servername); + /* Call BEFORE DROP USER MAPPING triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = umId; + cmd.objectname = NULL; /* composite object name */ + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } + /* * Do the deletion */ *************** *** 1361,1366 **** RemoveUserMapping(DropUserMappingStmt *stmt) --- 1551,1562 ---- object.objectSubId = 0; performDeletion(&object, DROP_CASCADE, 0); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + ExecAfterCommandTriggers(&cmd); + } } *** a/src/backend/commands/functioncmds.c --- b/src/backend/commands/functioncmds.c *************** *** 55,60 **** --- 55,61 ---- #include "parser/parse_expr.h" #include "parser/parse_func.h" #include "parser/parse_type.h" + #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" *************** *** 66,72 **** static void AlterFunctionOwner_internal(Relation rel, HeapTuple tup, ! Oid newOwnerId); /* --- 67,73 ---- static void AlterFunctionOwner_internal(Relation rel, HeapTuple tup, ! Oid newOwnerId, CommandContext cmd); /* *************** *** 802,807 **** CreateFunction(CreateFunctionStmt *stmt, const char *queryString) --- 803,809 ---- { char *probin_str; char *prosrc_str; + Oid procOid; Oid prorettype; bool returnsSet; char *language; *************** *** 827,832 **** CreateFunction(CreateFunctionStmt *stmt, const char *queryString) --- 829,835 ---- HeapTuple languageTuple; Form_pg_language languageStruct; List *as_clause; + CommandContextData cmd; /* Convert list of names to a name and namespace */ namespaceId = QualifiedNameGetCreationNamespace(stmt->funcname, *************** *** 970,1001 **** CreateFunction(CreateFunctionStmt *stmt, const char *queryString) errmsg("ROWS is not applicable when function does not return a set"))); /* * And now that we have all the parameters, and know we're permitted to do * so, go ahead and create the function. */ ! ProcedureCreate(funcname, ! namespaceId, ! stmt->replace, ! returnsSet, ! prorettype, ! languageOid, ! languageValidator, ! prosrc_str, /* converted to text later */ ! probin_str, /* converted to text later */ ! false, /* not an aggregate */ ! isWindowFunc, ! security, ! isLeakProof, ! isStrict, ! volatility, ! parameterTypes, ! PointerGetDatum(allParameterTypes), ! PointerGetDatum(parameterModes), ! PointerGetDatum(parameterNames), ! parameterDefaults, ! PointerGetDatum(proconfig), ! procost, ! prorows); } --- 973,1025 ---- errmsg("ROWS is not applicable when function does not return a set"))); /* + * Call BEFORE CREATE FUNCTION triggers + */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = (char *)funcname; + cmd.schemaname = get_namespace_name(namespaceId); + + ExecBeforeCommandTriggers(&cmd); + } + /* * And now that we have all the parameters, and know we're permitted to do * so, go ahead and create the function. */ ! procOid = ! ProcedureCreate(funcname, ! namespaceId, ! stmt->replace, ! returnsSet, ! prorettype, ! languageOid, ! languageValidator, ! prosrc_str, /* converted to text later */ ! probin_str, /* converted to text later */ ! false, /* not an aggregate */ ! isWindowFunc, ! security, ! isLeakProof, ! isStrict, ! volatility, ! parameterTypes, ! PointerGetDatum(allParameterTypes), ! PointerGetDatum(parameterModes), ! PointerGetDatum(parameterNames), ! parameterDefaults, ! PointerGetDatum(proconfig), ! procost, ! prorows); ! ! /* Call AFTER CREATE FUNCTION triggers */ ! if (CommandFiresAfterTriggers(&cmd)) ! { ! cmd.objectId = procOid; ! ExecAfterCommandTriggers(&cmd); ! } } *************** *** 1053,1059 **** RemoveFunctionById(Oid funcOid) * Rename function */ void ! RenameFunction(List *name, List *argtypes, const char *newname) { Oid procOid; Oid namespaceOid; --- 1077,1083 ---- * Rename function */ void ! RenameFunction(List *name, List *argtypes, const char *newname, CommandContext cmd) { Oid procOid; Oid namespaceOid; *************** *** 1107,1112 **** RenameFunction(List *name, List *argtypes, const char *newname) --- 1131,1146 ---- aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* Call BEFORE ALTER FUNCTION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = procOid; + cmd->objectname = pstrdup(NameStr(procForm->proname)); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + /* rename */ namestrcpy(&(procForm->proname), newname); simple_heap_update(rel, &tup->t_self, tup); *************** *** 1114,1126 **** RenameFunction(List *name, List *argtypes, const char *newname) heap_close(rel, NoLock); heap_freetuple(tup); } /* * Change function owner by name and args */ void ! AlterFunctionOwner(List *name, List *argtypes, Oid newOwnerId) { Relation rel; Oid procOid; --- 1148,1168 ---- heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER FUNCTION triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } /* * Change function owner by name and args */ void ! AlterFunctionOwner(List *name, List *argtypes, Oid newOwnerId, ! CommandContext cmd) { Relation rel; Oid procOid; *************** *** 1141,1147 **** AlterFunctionOwner(List *name, List *argtypes, Oid newOwnerId) NameListToString(name)), errhint("Use ALTER AGGREGATE to change owner of aggregate functions."))); ! AlterFunctionOwner_internal(rel, tup, newOwnerId); heap_close(rel, NoLock); } --- 1183,1189 ---- NameListToString(name)), errhint("Use ALTER AGGREGATE to change owner of aggregate functions."))); ! AlterFunctionOwner_internal(rel, tup, newOwnerId, cmd); heap_close(rel, NoLock); } *************** *** 1150,1156 **** AlterFunctionOwner(List *name, List *argtypes, Oid newOwnerId) * Change function owner by Oid */ void ! AlterFunctionOwner_oid(Oid procOid, Oid newOwnerId) { Relation rel; HeapTuple tup; --- 1192,1198 ---- * Change function owner by Oid */ void ! AlterFunctionOwner_oid(Oid procOid, Oid newOwnerId, CommandContext cmd) { Relation rel; HeapTuple tup; *************** *** 1160,1172 **** AlterFunctionOwner_oid(Oid procOid, Oid newOwnerId) tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procOid)); if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for function %u", procOid); ! AlterFunctionOwner_internal(rel, tup, newOwnerId); heap_close(rel, NoLock); } static void ! AlterFunctionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) { Form_pg_proc procForm; AclResult aclresult; --- 1202,1215 ---- tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procOid)); if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for function %u", procOid); ! AlterFunctionOwner_internal(rel, tup, newOwnerId, cmd); heap_close(rel, NoLock); } static void ! AlterFunctionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId, ! CommandContext cmd) { Form_pg_proc procForm; AclResult aclresult; *************** *** 1212,1217 **** AlterFunctionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) --- 1255,1269 ---- get_namespace_name(procForm->pronamespace)); } + /* Call BEFORE ALTER FUNCTION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = procOid; + cmd->objectname = pstrdup(NameStr(procForm->proname)); + cmd->schemaname = get_namespace_name(procForm->pronamespace); + + ExecBeforeCommandTriggers(cmd); + } memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); *************** *** 1243,1248 **** AlterFunctionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) --- 1295,1304 ---- /* Update owner dependency reference */ changeDependencyOnOwner(ProcedureRelationId, procOid, newOwnerId); + + /* Call AFTER ALTER FUNCTION triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } ReleaseSysCache(tup); *************** *** 1268,1273 **** AlterFunction(AlterFunctionStmt *stmt) --- 1324,1330 ---- List *set_items = NIL; DefElem *cost_item = NULL; DefElem *rows_item = NULL; + CommandContextData cmd; rel = heap_open(ProcedureRelationId, RowExclusiveLock); *************** *** 1342,1347 **** AlterFunction(AlterFunctionStmt *stmt) --- 1399,1417 ---- (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("ROWS is not applicable when function does not return a set"))); } + + /* Call BEFORE ALTER FUNCTION command triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = HeapTupleGetOid(tup); + cmd.objectname = pstrdup(NameStr(procForm->proname)); + cmd.schemaname = get_namespace_name(procForm->pronamespace); + + ExecBeforeCommandTriggers(&cmd); + } + if (set_items) { Datum datum; *************** *** 1383,1388 **** AlterFunction(AlterFunctionStmt *stmt) --- 1453,1462 ---- heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER FUNCTION command triggers */ + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } /* *************** *** 1479,1484 **** CreateCast(CreateCastStmt *stmt) --- 1553,1559 ---- ObjectAddress myself, referenced; AclResult aclresult; + CommandContextData cmd; sourcetypeid = typenameTypeId(NULL, stmt->sourcetype); targettypeid = typenameTypeId(NULL, stmt->targettype); *************** *** 1697,1702 **** CreateCast(CreateCastStmt *stmt) --- 1772,1789 ---- break; } + /* Call BEFORE CREATE CAST command triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = NULL; /* composite name, not supported */ + cmd.schemaname = NULL; /* casts don't live in a namespace */ + + ExecBeforeCommandTriggers(&cmd); + } + relation = heap_open(CastRelationId, RowExclusiveLock); /* *************** *** 1765,1770 **** CreateCast(CreateCastStmt *stmt) --- 1852,1863 ---- heap_freetuple(tuple); heap_close(relation, RowExclusiveLock); + + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = castid; + ExecAfterCommandTriggers(&cmd); + } } /* *************** *** 1823,1829 **** DropCastById(Oid castOid) */ void AlterFunctionNamespace(List *name, List *argtypes, bool isagg, ! const char *newschema) { Oid procOid; Oid nspOid; --- 1916,1922 ---- */ void AlterFunctionNamespace(List *name, List *argtypes, bool isagg, ! const char *newschema, CommandContext cmd) { Oid procOid; Oid nspOid; *************** *** 1837,1847 **** AlterFunctionNamespace(List *name, List *argtypes, bool isagg, /* get schema OID and check its permissions */ nspOid = LookupCreationNamespace(newschema); ! AlterFunctionNamespace_oid(procOid, nspOid); } Oid ! AlterFunctionNamespace_oid(Oid procOid, Oid nspOid) { Oid oldNspOid; HeapTuple tup; --- 1930,1940 ---- /* get schema OID and check its permissions */ nspOid = LookupCreationNamespace(newschema); ! AlterFunctionNamespace_oid(procOid, nspOid, cmd); } Oid ! AlterFunctionNamespace_oid(Oid procOid, Oid nspOid, CommandContext cmd) { Oid oldNspOid; HeapTuple tup; *************** *** 1876,1881 **** AlterFunctionNamespace_oid(Oid procOid, Oid nspOid) --- 1969,1984 ---- NameStr(proc->proname), get_namespace_name(nspOid)))); + /* Call BEFORE ALTER FUNCTION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = procOid; + cmd->objectname = pstrdup(NameStr(proc->proname)); + cmd->schemaname = get_namespace_name(oldNspOid); + + ExecBeforeCommandTriggers(cmd); + } + /* OK, modify the pg_proc row */ /* tup is a copy, so we can scribble directly on it */ *************** *** 1894,1899 **** AlterFunctionNamespace_oid(Oid procOid, Oid nspOid) --- 1997,2007 ---- heap_close(procRel, RowExclusiveLock); + if (CommandFiresAfterTriggers(cmd)) + { + cmd->schemaname = get_namespace_name(nspOid); + ExecAfterCommandTriggers(cmd); + } return oldNspOid; } *** a/src/backend/commands/indexcmds.c --- b/src/backend/commands/indexcmds.c *************** *** 318,324 **** DefineIndex(RangeVar *heapRelation, bool check_rights, bool skip_build, bool quiet, ! bool concurrent) { Oid *typeObjectId; Oid *collationObjectId; --- 318,325 ---- bool check_rights, bool skip_build, bool quiet, ! bool concurrent, ! CommandContext cmd) { Oid *typeObjectId; Oid *collationObjectId; *************** *** 574,579 **** DefineIndex(RangeVar *heapRelation, --- 575,597 ---- index_check_primary_key(rel, indexInfo, is_alter_table); /* + * Call BEFORE CREATE INDEX triggers + * + * cmd.tag and cmd.parseetree must have been prepared for us by the caller. + * + * Bootstrap code must be able to skip command triggers, it's passing NULL + * as the CommandContext pointer. + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = indexRelationName; + cmd->schemaname = get_namespace_name(namespaceId); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Report index creation if appropriate (delay this till after most of the * error checks) */ *************** *** 625,630 **** DefineIndex(RangeVar *heapRelation, --- 643,655 ---- { /* Close the heap and we're done, in the non-concurrent case */ heap_close(rel, NoLock); + + /* Call AFTER CREATE INDEX triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = indexRelationId; + ExecAfterCommandTriggers(cmd); + } return indexRelationId; } *************** *** 914,919 **** DefineIndex(RangeVar *heapRelation, --- 939,949 ---- */ UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock); + /* + * Don't Call AFTER CREATE INDEX triggers here, because the transaction + * that did the work is already commited, RAISE EXCEPTION in the trigger + * can no longer undo what we did. + */ return indexRelationId; } *************** *** 1735,1741 **** ChooseIndexColumnNames(List *indexElems) * Recreate a specific index. */ void ! ReindexIndex(RangeVar *indexRelation) { Oid indOid; Oid heapOid = InvalidOid; --- 1765,1771 ---- * Recreate a specific index. */ void ! ReindexIndex(RangeVar *indexRelation, CommandContext cmd) { Oid indOid; Oid heapOid = InvalidOid; *************** *** 1746,1752 **** ReindexIndex(RangeVar *indexRelation) RangeVarCallbackForReindexIndex, (void *) &heapOid); ! reindex_index(indOid, false); } /* --- 1776,1782 ---- RangeVarCallbackForReindexIndex, (void *) &heapOid); ! reindex_index(indOid, false, cmd); } /* *************** *** 1813,1819 **** RangeVarCallbackForReindexIndex(const RangeVar *relation, * Recreate all indexes of a table (and of its toast table, if any) */ void ! ReindexTable(RangeVar *relation) { Oid heapOid; --- 1843,1849 ---- * Recreate all indexes of a table (and of its toast table, if any) */ void ! ReindexTable(RangeVar *relation, CommandContext cmd) { Oid heapOid; *************** *** 1821,1827 **** ReindexTable(RangeVar *relation) heapOid = RangeVarGetRelidExtended(relation, ShareLock, false, false, RangeVarCallbackOwnsTable, NULL); ! if (!reindex_relation(heapOid, REINDEX_REL_PROCESS_TOAST)) ereport(NOTICE, (errmsg("table \"%s\" has no indexes", relation->relname))); --- 1851,1857 ---- heapOid = RangeVarGetRelidExtended(relation, ShareLock, false, false, RangeVarCallbackOwnsTable, NULL); ! if (!reindex_relation(heapOid, REINDEX_REL_PROCESS_TOAST, cmd)) ereport(NOTICE, (errmsg("table \"%s\" has no indexes", relation->relname))); *************** *** 1934,1940 **** ReindexDatabase(const char *databaseName, bool do_system, bool do_user) StartTransactionCommand(); /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); ! if (reindex_relation(relid, REINDEX_REL_PROCESS_TOAST)) ereport(NOTICE, (errmsg("table \"%s.%s\" was reindexed", get_namespace_name(get_rel_namespace(relid)), --- 1964,1970 ---- StartTransactionCommand(); /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); ! if (reindex_relation(relid, REINDEX_REL_PROCESS_TOAST, NULL)) ereport(NOTICE, (errmsg("table \"%s.%s\" was reindexed", get_namespace_name(get_rel_namespace(relid)), *** a/src/backend/commands/opclasscmds.c --- b/src/backend/commands/opclasscmds.c *************** *** 81,89 **** static void dropOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid, static void dropProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid, List *procedures); static void AlterOpClassOwner_internal(Relation rel, HeapTuple tuple, ! Oid newOwnerId); static void AlterOpFamilyOwner_internal(Relation rel, HeapTuple tuple, ! Oid newOwnerId); /* --- 81,89 ---- static void dropProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid, List *procedures); static void AlterOpClassOwner_internal(Relation rel, HeapTuple tuple, ! Oid newOwnerId, CommandContext cmd); static void AlterOpFamilyOwner_internal(Relation rel, HeapTuple tuple, ! Oid newOwnerId, CommandContext cmd); /* *************** *** 350,355 **** DefineOpClass(CreateOpClassStmt *stmt) --- 350,356 ---- NameData opcName; ObjectAddress myself, referenced; + CommandContextData cmd; /* Convert list of names to a name and namespace */ namespaceoid = QualifiedNameGetCreationNamespace(stmt->opclassname, *************** *** 590,595 **** DefineOpClass(CreateOpClassStmt *stmt) --- 591,608 ---- stmt->amname))); } + /* Call BEFORE CREATE OPERATOR CLASS command triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = pstrdup(opcname); + cmd.schemaname = get_namespace_name(namespaceoid); + + ExecBeforeCommandTriggers(&cmd); + } + rel = heap_open(OperatorClassRelationId, RowExclusiveLock); /* *************** *** 720,725 **** DefineOpClass(CreateOpClassStmt *stmt) --- 733,745 ---- OperatorClassRelationId, opclassoid, 0, NULL); heap_close(rel, RowExclusiveLock); + + /* Call AFTER CREATE OPERATOR CLASS command triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = opclassoid; + ExecAfterCommandTriggers(&cmd); + } } *************** *** 731,739 **** void DefineOpFamily(CreateOpFamilyStmt *stmt) { char *opfname; /* name of opfamily we're creating */ ! Oid amoid, /* our AM's oid */ namespaceoid; /* namespace to create opfamily in */ AclResult aclresult; /* Convert list of names to a name and namespace */ namespaceoid = QualifiedNameGetCreationNamespace(stmt->opfamilyname, --- 751,761 ---- DefineOpFamily(CreateOpFamilyStmt *stmt) { char *opfname; /* name of opfamily we're creating */ ! Oid opfOid, ! amoid, /* our AM's oid */ namespaceoid; /* namespace to create opfamily in */ AclResult aclresult; + CommandContextData cmd; /* Convert list of names to a name and namespace */ namespaceoid = QualifiedNameGetCreationNamespace(stmt->opfamilyname, *************** *** 759,766 **** DefineOpFamily(CreateOpFamilyStmt *stmt) (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to create an operator family"))); /* Insert pg_opfamily catalog entry */ ! (void) CreateOpFamily(stmt->amname, opfname, namespaceoid, amoid); } --- 781,807 ---- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to create an operator family"))); + /* Call BEFORE CREATE OPERATOR FAMILY command triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = opfname; + cmd.schemaname = get_namespace_name(namespaceoid); + + ExecBeforeCommandTriggers(&cmd); + } + /* Insert pg_opfamily catalog entry */ ! opfOid = CreateOpFamily(stmt->amname, opfname, namespaceoid, amoid); ! ! /* Call AFTER CREATE OPERATOR FAMILY command triggers */ ! if (CommandFiresAfterTriggers(&cmd)) ! { ! cmd.objectId = opfOid; ! ExecAfterCommandTriggers(&cmd); ! } } *************** *** 781,786 **** AlterOpFamily(AlterOpFamilyStmt *stmt) --- 822,828 ---- maxProcNumber; /* amsupport value */ HeapTuple tup; Form_pg_am pg_am; + CommandContextData cmd; /* Get necessary info about access method */ tup = SearchSysCache1(AMNAME, CStringGetDatum(stmt->amname)); *************** *** 815,820 **** AlterOpFamily(AlterOpFamilyStmt *stmt) --- 857,882 ---- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to alter an operator family"))); + /* Call BEFORE ALTER OPERATOR FAMILY command triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + HeapTuple htup; + Form_pg_opfamily opfForm; + + htup = OpFamilyCacheLookup(amoid, stmt->opfamilyname, false); + opfForm = (Form_pg_opfamily) GETSTRUCT(htup); + + cmd.objectId = opfamilyoid; + cmd.objectname = pstrdup(NameStr(opfForm->opfname)); + cmd.schemaname = get_namespace_name(opfForm->opfnamespace); + + ReleaseSysCache(htup); + + ExecBeforeCommandTriggers(&cmd); + } + /* * ADD and DROP cases need separate code from here on down. */ *************** *** 826,831 **** AlterOpFamily(AlterOpFamilyStmt *stmt) --- 888,897 ---- AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid, maxOpNumber, maxProcNumber, stmt->items); + + /* Call AFTER ALTER OPERATOR FAMILY command triggers */ + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } /* *************** *** 1667,1673 **** RemoveAmProcEntryById(Oid entryOid) * Rename opclass */ void ! RenameOpClass(List *name, const char *access_method, const char *newname) { Oid opcOid; Oid amOid; --- 1733,1740 ---- * Rename opclass */ void ! RenameOpClass(List *name, const char *access_method, const char *newname, ! CommandContext cmd) { Oid opcOid; Oid amOid; *************** *** 1712,1717 **** RenameOpClass(List *name, const char *access_method, const char *newname) --- 1779,1794 ---- aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* Call BEFORE ALTER OPERATOR CLASS triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = opcOid; + cmd->objectname = NameListToString(name); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + /* rename */ namestrcpy(&(((Form_pg_opclass) GETSTRUCT(tup))->opcname), newname); simple_heap_update(rel, &tup->t_self, tup); *************** *** 1719,1731 **** RenameOpClass(List *name, const char *access_method, const char *newname) heap_close(rel, NoLock); heap_freetuple(tup); } /* * Rename opfamily */ void ! RenameOpFamily(List *name, const char *access_method, const char *newname) { Oid opfOid; Oid amOid; --- 1796,1816 ---- heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER OPERATOR CLASS triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } /* * Rename opfamily */ void ! RenameOpFamily(List *name, const char *access_method, const char *newname, ! CommandContext cmd) { Oid opfOid; Oid amOid; *************** *** 1801,1806 **** RenameOpFamily(List *name, const char *access_method, const char *newname) --- 1886,1901 ---- aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* Call BEFORE ALTER OPERATOR FAMILY triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = opfOid; + cmd->objectname = pstrdup(opfname); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + /* rename */ namestrcpy(&(((Form_pg_opfamily) GETSTRUCT(tup))->opfname), newname); simple_heap_update(rel, &tup->t_self, tup); *************** *** 1808,1820 **** RenameOpFamily(List *name, const char *access_method, const char *newname) heap_close(rel, NoLock); heap_freetuple(tup); } /* * Change opclass owner by name */ void ! AlterOpClassOwner(List *name, const char *access_method, Oid newOwnerId) { Oid amOid; Relation rel; --- 1903,1923 ---- heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER OPERATOR FAMILY triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } /* * Change opclass owner by name */ void ! AlterOpClassOwner(List *name, const char *access_method, Oid newOwnerId, ! CommandContext cmd) { Oid amOid; Relation rel; *************** *** 1830,1836 **** AlterOpClassOwner(List *name, const char *access_method, Oid newOwnerId) tup = heap_copytuple(origtup); ReleaseSysCache(origtup); ! AlterOpClassOwner_internal(rel, tup, newOwnerId); heap_freetuple(tup); heap_close(rel, NoLock); --- 1933,1939 ---- tup = heap_copytuple(origtup); ReleaseSysCache(origtup); ! AlterOpClassOwner_internal(rel, tup, newOwnerId, cmd); heap_freetuple(tup); heap_close(rel, NoLock); *************** *** 1851,1857 **** AlterOpClassOwner_oid(Oid opclassOid, Oid newOwnerId) if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for opclass %u", opclassOid); ! AlterOpClassOwner_internal(rel, tup, newOwnerId); heap_freetuple(tup); heap_close(rel, NoLock); --- 1954,1960 ---- if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for opclass %u", opclassOid); ! AlterOpClassOwner_internal(rel, tup, newOwnerId, NULL); heap_freetuple(tup); heap_close(rel, NoLock); *************** *** 1862,1868 **** AlterOpClassOwner_oid(Oid opclassOid, Oid newOwnerId) * parameter is a copy of the tuple from pg_opclass we want to modify. */ static void ! AlterOpClassOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) { Oid namespaceOid; AclResult aclresult; --- 1965,1972 ---- * parameter is a copy of the tuple from pg_opclass we want to modify. */ static void ! AlterOpClassOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId, ! CommandContext cmd) { Oid namespaceOid; AclResult aclresult; *************** *** 1900,1905 **** AlterOpClassOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) --- 2004,2019 ---- get_namespace_name(namespaceOid)); } + /* Call BEFORE ALTER OPERATOR CLASS triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(opcForm->opcname)); + cmd->schemaname = get_namespace_name(opcForm->opcnamespace); + + ExecBeforeCommandTriggers(cmd); + } + /* * Modify the owner --- okay to scribble on tup because it's a copy */ *************** *** 1912,1917 **** AlterOpClassOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) --- 2026,2035 ---- /* Update owner dependency reference */ changeDependencyOnOwner(OperatorClassRelationId, HeapTupleGetOid(tup), newOwnerId); + + /* Call AFTER ALTER OPERATOR CLASS triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } } *************** *** 1919,1925 **** AlterOpClassOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) * ALTER OPERATOR CLASS any_name USING access_method SET SCHEMA name */ void ! AlterOpClassNamespace(List *name, char *access_method, const char *newschema) { Oid amOid; Relation rel; --- 2037,2044 ---- * ALTER OPERATOR CLASS any_name USING access_method SET SCHEMA name */ void ! AlterOpClassNamespace(List *name, char *access_method, const char *newschema, ! CommandContext cmd) { Oid amOid; Relation rel; *************** *** 1941,1947 **** AlterOpClassNamespace(List *name, char *access_method, const char *newschema) Anum_pg_opclass_opcname, Anum_pg_opclass_opcnamespace, Anum_pg_opclass_opcowner, ! ACL_KIND_OPCLASS); heap_close(rel, RowExclusiveLock); } --- 2060,2066 ---- Anum_pg_opclass_opcname, Anum_pg_opclass_opcnamespace, Anum_pg_opclass_opcowner, ! ACL_KIND_OPCLASS, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 1960,1966 **** AlterOpClassNamespace_oid(Oid opclassOid, Oid newNspOid) Anum_pg_opclass_opcname, Anum_pg_opclass_opcnamespace, Anum_pg_opclass_opcowner, ! ACL_KIND_OPCLASS); heap_close(rel, RowExclusiveLock); --- 2079,2085 ---- Anum_pg_opclass_opcname, Anum_pg_opclass_opcnamespace, Anum_pg_opclass_opcowner, ! ACL_KIND_OPCLASS, NULL); heap_close(rel, RowExclusiveLock); *************** *** 1971,1977 **** AlterOpClassNamespace_oid(Oid opclassOid, Oid newNspOid) * Change opfamily owner by name */ void ! AlterOpFamilyOwner(List *name, const char *access_method, Oid newOwnerId) { Oid amOid; Relation rel; --- 2090,2097 ---- * Change opfamily owner by name */ void ! AlterOpFamilyOwner(List *name, const char *access_method, Oid newOwnerId, ! CommandContext cmd) { Oid amOid; Relation rel; *************** *** 2020,2026 **** AlterOpFamilyOwner(List *name, const char *access_method, Oid newOwnerId) elog(ERROR, "cache lookup failed for opfamily %u", opfOid); } ! AlterOpFamilyOwner_internal(rel, tup, newOwnerId); heap_freetuple(tup); heap_close(rel, NoLock); --- 2140,2146 ---- elog(ERROR, "cache lookup failed for opfamily %u", opfOid); } ! AlterOpFamilyOwner_internal(rel, tup, newOwnerId, cmd); heap_freetuple(tup); heap_close(rel, NoLock); *************** *** 2041,2047 **** AlterOpFamilyOwner_oid(Oid opfamilyOid, Oid newOwnerId) if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for opfamily %u", opfamilyOid); ! AlterOpFamilyOwner_internal(rel, tup, newOwnerId); heap_freetuple(tup); heap_close(rel, NoLock); --- 2161,2167 ---- if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for opfamily %u", opfamilyOid); ! AlterOpFamilyOwner_internal(rel, tup, newOwnerId, NULL); heap_freetuple(tup); heap_close(rel, NoLock); *************** *** 2052,2058 **** AlterOpFamilyOwner_oid(Oid opfamilyOid, Oid newOwnerId) * parameter is a copy of the tuple from pg_opfamily we want to modify. */ static void ! AlterOpFamilyOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) { Oid namespaceOid; AclResult aclresult; --- 2172,2179 ---- * parameter is a copy of the tuple from pg_opfamily we want to modify. */ static void ! AlterOpFamilyOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId, ! CommandContext cmd) { Oid namespaceOid; AclResult aclresult; *************** *** 2090,2095 **** AlterOpFamilyOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) --- 2211,2226 ---- get_namespace_name(namespaceOid)); } + /* Call BEFORE ALTER OPERATOR FAMILY triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(opfForm->opfname)); + cmd->schemaname = get_namespace_name(opfForm->opfnamespace); + + ExecBeforeCommandTriggers(cmd); + } + /* * Modify the owner --- okay to scribble on tup because it's a copy */ *************** *** 2102,2107 **** AlterOpFamilyOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) --- 2233,2242 ---- /* Update owner dependency reference */ changeDependencyOnOwner(OperatorFamilyRelationId, HeapTupleGetOid(tup), newOwnerId); + + /* Call AFTER ALTER OPERATOR FAMILY triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } } *************** *** 2128,2134 **** get_am_oid(const char *amname, bool missing_ok) * ALTER OPERATOR FAMILY any_name USING access_method SET SCHEMA name */ void ! AlterOpFamilyNamespace(List *name, char *access_method, const char *newschema) { Oid amOid; Relation rel; --- 2263,2270 ---- * ALTER OPERATOR FAMILY any_name USING access_method SET SCHEMA name */ void ! AlterOpFamilyNamespace(List *name, char *access_method, const char *newschema, ! CommandContext cmd) { Oid amOid; Relation rel; *************** *** 2150,2156 **** AlterOpFamilyNamespace(List *name, char *access_method, const char *newschema) Anum_pg_opfamily_opfname, Anum_pg_opfamily_opfnamespace, Anum_pg_opfamily_opfowner, ! ACL_KIND_OPFAMILY); heap_close(rel, RowExclusiveLock); } --- 2286,2292 ---- Anum_pg_opfamily_opfname, Anum_pg_opfamily_opfnamespace, Anum_pg_opfamily_opfowner, ! ACL_KIND_OPFAMILY, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 2169,2175 **** AlterOpFamilyNamespace_oid(Oid opfamilyOid, Oid newNspOid) Anum_pg_opfamily_opfname, Anum_pg_opfamily_opfnamespace, Anum_pg_opfamily_opfowner, ! ACL_KIND_OPFAMILY); heap_close(rel, RowExclusiveLock); --- 2305,2311 ---- Anum_pg_opfamily_opfname, Anum_pg_opfamily_opfnamespace, Anum_pg_opfamily_opfowner, ! ACL_KIND_OPFAMILY, NULL); heap_close(rel, RowExclusiveLock); *** a/src/backend/commands/operatorcmds.c --- b/src/backend/commands/operatorcmds.c *************** *** 51,57 **** #include "utils/syscache.h" ! static void AlterOperatorOwner_internal(Relation rel, Oid operOid, Oid newOwnerId); /* * DefineOperator --- 51,58 ---- #include "utils/syscache.h" ! static void AlterOperatorOwner_internal(Relation rel, Oid operOid, Oid newOwnerId, ! CommandContext cmd); /* * DefineOperator *************** *** 62,68 **** static void AlterOperatorOwner_internal(Relation rel, Oid operOid, Oid newOwnerI * 'parameters' is a list of DefElem */ void ! DefineOperator(List *names, List *parameters) { char *oprName; Oid oprNamespace; --- 63,69 ---- * 'parameters' is a list of DefElem */ void ! DefineOperator(List *names, List *parameters, CommandContext cmd) { char *oprName; Oid oprNamespace; *************** *** 310,316 **** DefineOperator(List *names, List *parameters) restrictionOid, /* optional restrict. sel. procedure */ joinOid, /* optional join sel. procedure name */ canMerge, /* operator merges */ ! canHash); /* operator hashes */ } --- 311,318 ---- restrictionOid, /* optional restrict. sel. procedure */ joinOid, /* optional join sel. procedure name */ canMerge, /* operator merges */ ! canHash, /* operator hashes */ ! cmd); } *************** *** 343,349 **** AlterOperatorOwner_oid(Oid operOid, Oid newOwnerId) rel = heap_open(OperatorRelationId, RowExclusiveLock); ! AlterOperatorOwner_internal(rel, operOid, newOwnerId); heap_close(rel, NoLock); } --- 345,351 ---- rel = heap_open(OperatorRelationId, RowExclusiveLock); ! AlterOperatorOwner_internal(rel, operOid, newOwnerId, NULL); heap_close(rel, NoLock); } *************** *** 353,359 **** AlterOperatorOwner_oid(Oid operOid, Oid newOwnerId) */ void AlterOperatorOwner(List *name, TypeName *typeName1, TypeName *typeName2, ! Oid newOwnerId) { Oid operOid; Relation rel; --- 355,361 ---- */ void AlterOperatorOwner(List *name, TypeName *typeName1, TypeName *typeName2, ! Oid newOwnerId, CommandContext cmd) { Oid operOid; Relation rel; *************** *** 364,376 **** AlterOperatorOwner(List *name, TypeName *typeName1, TypeName *typeName2, typeName1, typeName2, false, -1); ! AlterOperatorOwner_internal(rel, operOid, newOwnerId); heap_close(rel, NoLock); } static void ! AlterOperatorOwner_internal(Relation rel, Oid operOid, Oid newOwnerId) { HeapTuple tup; AclResult aclresult; --- 366,379 ---- typeName1, typeName2, false, -1); ! AlterOperatorOwner_internal(rel, operOid, newOwnerId, cmd); heap_close(rel, NoLock); } static void ! AlterOperatorOwner_internal(Relation rel, Oid operOid, Oid newOwnerId, ! CommandContext cmd) { HeapTuple tup; AclResult aclresult; *************** *** 410,415 **** AlterOperatorOwner_internal(Relation rel, Oid operOid, Oid newOwnerId) --- 413,428 ---- get_namespace_name(oprForm->oprnamespace)); } + /* Call BEFORE ALTER OPERATOR triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = operOid; + cmd->objectname = pstrdup(NameStr(oprForm->oprname)); + cmd->schemaname = get_namespace_name(oprForm->oprnamespace); + + ExecBeforeCommandTriggers(cmd); + } + /* * Modify the owner --- okay to scribble on tup because it's a copy */ *************** *** 424,436 **** AlterOperatorOwner_internal(Relation rel, Oid operOid, Oid newOwnerId) } heap_freetuple(tup); } /* * Execute ALTER OPERATOR SET SCHEMA */ void ! AlterOperatorNamespace(List *names, List *argtypes, const char *newschema) { List *operatorName = names; TypeName *typeName1 = (TypeName *) linitial(argtypes); --- 437,454 ---- } heap_freetuple(tup); + + /* Call AFTER ALTER OPERATOR triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } /* * Execute ALTER OPERATOR SET SCHEMA */ void ! AlterOperatorNamespace(List *names, List *argtypes, const char *newschema, ! CommandContext cmd) { List *operatorName = names; TypeName *typeName1 = (TypeName *) linitial(argtypes); *************** *** 454,460 **** AlterOperatorNamespace(List *names, List *argtypes, const char *newschema) Anum_pg_operator_oprname, Anum_pg_operator_oprnamespace, Anum_pg_operator_oprowner, ! ACL_KIND_OPER); heap_close(rel, RowExclusiveLock); } --- 472,478 ---- Anum_pg_operator_oprname, Anum_pg_operator_oprnamespace, Anum_pg_operator_oprowner, ! ACL_KIND_OPER, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 472,478 **** AlterOperatorNamespace_oid(Oid operOid, Oid newNspOid) Anum_pg_operator_oprname, Anum_pg_operator_oprnamespace, Anum_pg_operator_oprowner, ! ACL_KIND_OPER); heap_close(rel, RowExclusiveLock); --- 490,496 ---- Anum_pg_operator_oprname, Anum_pg_operator_oprnamespace, Anum_pg_operator_oprowner, ! ACL_KIND_OPER, NULL); heap_close(rel, RowExclusiveLock); *** a/src/backend/commands/proclang.c --- b/src/backend/commands/proclang.c *************** *** 49,60 **** typedef struct char *tmpllibrary; /* path of shared library */ } PLTemplate; ! static void create_proc_lang(const char *languageName, bool replace, Oid languageOwner, Oid handlerOid, Oid inlineOid, Oid valOid, bool trusted); static PLTemplate *find_language_template(const char *languageName); static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel, ! Oid newOwnerId); /* --------------------------------------------------------------------- --- 49,60 ---- char *tmpllibrary; /* path of shared library */ } PLTemplate; ! static Oid create_proc_lang(const char *languageName, bool replace, Oid languageOwner, Oid handlerOid, Oid inlineOid, Oid valOid, bool trusted); static PLTemplate *find_language_template(const char *languageName); static void AlterLanguageOwner_internal(HeapTuple tup, Relation rel, ! Oid newOwnerId, CommandContext cmd); /* --------------------------------------------------------------------- *************** *** 67,75 **** CreateProceduralLanguage(CreatePLangStmt *stmt) PLTemplate *pltemplate; Oid handlerOid, inlineOid, ! valOid; Oid funcrettype; Oid funcargtypes[1]; /* * If we have template information for the language, ignore the supplied --- 67,79 ---- PLTemplate *pltemplate; Oid handlerOid, inlineOid, ! valOid, ! loid; Oid funcrettype; Oid funcargtypes[1]; + CommandContextData cmd; + + InitCommandContext(&cmd, (Node *)stmt); /* * If we have template information for the language, ignore the supplied *************** *** 222,231 **** CreateProceduralLanguage(CreatePLangStmt *stmt) else valOid = InvalidOid; /* ok, create it */ ! create_proc_lang(stmt->plname, stmt->replace, GetUserId(), ! handlerOid, inlineOid, ! valOid, pltemplate->tmpltrusted); } else { --- 226,252 ---- else valOid = InvalidOid; + /* Call BEFORE CREATE LANGUAGE command triggers */ + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = stmt->plname; + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } + /* ok, create it */ ! loid = create_proc_lang(stmt->plname, stmt->replace, GetUserId(), ! handlerOid, inlineOid, ! valOid, pltemplate->tmpltrusted); ! ! /* Call AFTER CREATE LANGUAGE command triggers */ ! if (CommandFiresAfterTriggers(&cmd)) ! { ! cmd.objectId = loid; ! ExecAfterCommandTriggers(&cmd); ! } } else { *************** *** 297,313 **** CreateProceduralLanguage(CreatePLangStmt *stmt) else valOid = InvalidOid; /* ok, create it */ ! create_proc_lang(stmt->plname, stmt->replace, GetUserId(), ! handlerOid, inlineOid, ! valOid, stmt->pltrusted); } } /* * Guts of language creation. */ ! static void create_proc_lang(const char *languageName, bool replace, Oid languageOwner, Oid handlerOid, Oid inlineOid, Oid valOid, bool trusted) --- 318,351 ---- else valOid = InvalidOid; + /* Call BEFORE CREATE LANGUAGE command triggers */ + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = stmt->plname; + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } + /* ok, create it */ ! loid = create_proc_lang(stmt->plname, stmt->replace, GetUserId(), ! handlerOid, inlineOid, ! valOid, stmt->pltrusted); ! ! /* Call AFTER CREATE LANGUAGE command triggers */ ! if (CommandFiresAfterTriggers(&cmd)) ! { ! cmd.objectId = loid; ! ExecAfterCommandTriggers(&cmd); ! } } } /* * Guts of language creation. */ ! static Oid create_proc_lang(const char *languageName, bool replace, Oid languageOwner, Oid handlerOid, Oid inlineOid, Oid valOid, bool trusted) *************** *** 431,436 **** create_proc_lang(const char *languageName, bool replace, --- 469,476 ---- LanguageRelationId, myself.objectId, 0, NULL); heap_close(rel, RowExclusiveLock); + + return myself.objectId; } /* *************** *** 536,542 **** DropProceduralLanguageById(Oid langOid) * Rename language */ void ! RenameLanguage(const char *oldname, const char *newname) { HeapTuple tup; Relation rel; --- 576,582 ---- * Rename language */ void ! RenameLanguage(const char *oldname, const char *newname, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 560,565 **** RenameLanguage(const char *oldname, const char *newname) --- 600,615 ---- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_LANGUAGE, oldname); + /* Call BEFORE ALTER SERVER triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(oldname); + cmd->schemaname = NULL; + + ExecBeforeCommandTriggers(cmd); + } + /* rename */ namestrcpy(&(((Form_pg_language) GETSTRUCT(tup))->lanname), newname); simple_heap_update(rel, &tup->t_self, tup); *************** *** 567,579 **** RenameLanguage(const char *oldname, const char *newname) heap_close(rel, NoLock); heap_freetuple(tup); } /* * Change language owner */ void ! AlterLanguageOwner(const char *name, Oid newOwnerId) { HeapTuple tup; Relation rel; --- 617,636 ---- heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER LANGUAGE triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } /* * Change language owner */ void ! AlterLanguageOwner(const char *name, Oid newOwnerId, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 586,592 **** AlterLanguageOwner(const char *name, Oid newOwnerId) (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("language \"%s\" does not exist", name))); ! AlterLanguageOwner_internal(tup, rel, newOwnerId); ReleaseSysCache(tup); --- 643,649 ---- (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("language \"%s\" does not exist", name))); ! AlterLanguageOwner_internal(tup, rel, newOwnerId, cmd); ReleaseSysCache(tup); *************** *** 609,615 **** AlterLanguageOwner_oid(Oid oid, Oid newOwnerId) if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for language %u", oid); ! AlterLanguageOwner_internal(tup, rel, newOwnerId); ReleaseSysCache(tup); --- 666,672 ---- if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for language %u", oid); ! AlterLanguageOwner_internal(tup, rel, newOwnerId, NULL); ReleaseSysCache(tup); *************** *** 620,626 **** AlterLanguageOwner_oid(Oid oid, Oid newOwnerId) * Workhorse for AlterLanguageOwner variants */ static void ! AlterLanguageOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId) { Form_pg_language lanForm; --- 677,684 ---- * Workhorse for AlterLanguageOwner variants */ static void ! AlterLanguageOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId, ! CommandContext cmd) { Form_pg_language lanForm; *************** *** 648,653 **** AlterLanguageOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId) --- 706,721 ---- /* Must be able to become new owner */ check_is_member_of_role(GetUserId(), newOwnerId); + /* Call BEFORE ALTER LANGUAGE triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(lanForm->lanname)); + cmd->schemaname = NULL; + + ExecBeforeCommandTriggers(cmd); + } + memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); *************** *** 680,685 **** AlterLanguageOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId) --- 748,757 ---- /* Update owner dependency reference */ changeDependencyOnOwner(LanguageRelationId, HeapTupleGetOid(tup), newOwnerId); + + /* Call AFTER ALTER LANGUAGE triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } } *** a/src/backend/commands/schemacmds.c --- b/src/backend/commands/schemacmds.c *************** *** 20,25 **** --- 20,26 ---- #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_namespace.h" + #include "commands/cmdtrigger.h" #include "commands/dbcommands.h" #include "commands/schemacmds.h" #include "miscadmin.h" *************** *** 31,37 **** #include "utils/syscache.h" ! static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId); /* * CREATE SCHEMA --- 32,39 ---- #include "utils/syscache.h" ! static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, ! Oid newOwnerId, CommandContext cmd); /* * CREATE SCHEMA *************** *** 49,54 **** CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) --- 51,57 ---- Oid saved_uid; int save_sec_context; AclResult aclresult; + CommandContextData cmd; GetUserIdAndSecContext(&saved_uid, &save_sec_context); *************** *** 82,87 **** CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) --- 85,103 ---- errdetail("The prefix \"pg_\" is reserved for system schemas."))); /* + * Call BEFORE CREATE SCHEMA triggers (before changing authorization) + */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = (char *)schemaName; + cmd.schemaname = NULL; /* a schema does not live in another schema */ + + ExecBeforeCommandTriggers(&cmd); + } + /* * If the requested authorization is different from the current user, * temporarily set the current user so that the object(s) will be created * with the correct ownership. *************** *** 144,149 **** CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) --- 160,172 ---- /* Reset current user and security context */ SetUserIdAndSecContext(saved_uid, save_sec_context); + + /* Call AFTER CREATE SCHEMA triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = namespaceId; + ExecAfterCommandTriggers(&cmd); + } } /* *************** *** 174,180 **** RemoveSchemaById(Oid schemaOid) * Rename schema */ void ! RenameSchema(const char *oldname, const char *newname) { HeapTuple tup; Relation rel; --- 197,203 ---- * Rename schema */ void ! RenameSchema(const char *oldname, const char *newname, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 211,216 **** RenameSchema(const char *oldname, const char *newname) --- 234,249 ---- errmsg("unacceptable schema name \"%s\"", newname), errdetail("The prefix \"pg_\" is reserved for system schemas."))); + /* Call BEFORE ALTER SCHEMA triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(oldname); + cmd->schemaname = NULL; + + ExecBeforeCommandTriggers(cmd); + } + /* rename */ namestrcpy(&(((Form_pg_namespace) GETSTRUCT(tup))->nspname), newname); simple_heap_update(rel, &tup->t_self, tup); *************** *** 218,223 **** RenameSchema(const char *oldname, const char *newname) --- 251,263 ---- heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER SCHEMA triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } void *************** *** 232,238 **** AlterSchemaOwner_oid(Oid oid, Oid newOwnerId) if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for schema %u", oid); ! AlterSchemaOwner_internal(tup, rel, newOwnerId); ReleaseSysCache(tup); --- 272,278 ---- if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for schema %u", oid); ! AlterSchemaOwner_internal(tup, rel, newOwnerId, NULL); ReleaseSysCache(tup); *************** *** 244,250 **** AlterSchemaOwner_oid(Oid oid, Oid newOwnerId) * Change schema owner */ void ! AlterSchemaOwner(const char *name, Oid newOwnerId) { HeapTuple tup; Relation rel; --- 284,290 ---- * Change schema owner */ void ! AlterSchemaOwner(const char *name, Oid newOwnerId, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 257,263 **** AlterSchemaOwner(const char *name, Oid newOwnerId) (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("schema \"%s\" does not exist", name))); ! AlterSchemaOwner_internal(tup, rel, newOwnerId); ReleaseSysCache(tup); --- 297,303 ---- (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("schema \"%s\" does not exist", name))); ! AlterSchemaOwner_internal(tup, rel, newOwnerId, cmd); ReleaseSysCache(tup); *************** *** 265,271 **** AlterSchemaOwner(const char *name, Oid newOwnerId) } static void ! AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId) { Form_pg_namespace nspForm; --- 305,312 ---- } static void ! AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId, ! CommandContext cmd) { Form_pg_namespace nspForm; *************** *** 312,317 **** AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId) --- 353,368 ---- aclcheck_error(aclresult, ACL_KIND_DATABASE, get_database_name(MyDatabaseId)); + /* Call BEFORE ALTER SCHEMA triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(nspForm->nspname)); + cmd->schemaname = NULL; + + ExecBeforeCommandTriggers(cmd); + } + memset(repl_null, false, sizeof(repl_null)); memset(repl_repl, false, sizeof(repl_repl)); *************** *** 343,348 **** AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId) /* Update owner dependency reference */ changeDependencyOnOwner(NamespaceRelationId, HeapTupleGetOid(tup), newOwnerId); - } } --- 394,402 ---- /* Update owner dependency reference */ changeDependencyOnOwner(NamespaceRelationId, HeapTupleGetOid(tup), newOwnerId); + /* Call AFTER ALTER SCHEMA triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); + } } *** a/src/backend/commands/sequence.c --- b/src/backend/commands/sequence.c *************** *** 20,25 **** --- 20,26 ---- #include "catalog/namespace.h" #include "catalog/pg_type.h" #include "commands/defrem.h" + #include "commands/cmdtrigger.h" #include "commands/sequence.h" #include "commands/tablecmds.h" #include "funcapi.h" *************** *** 28,33 **** --- 29,35 ---- #include "storage/lmgr.h" #include "storage/proc.h" #include "storage/smgr.h" + #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" *************** *** 115,127 **** DefineSequence(CreateSeqStmt *seq) bool null[SEQ_COL_LASTCOL]; int i; NameData name; /* Unlogged sequences are not implemented -- not clear if useful. */ if (seq->sequence->relpersistence == RELPERSISTENCE_UNLOGGED) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("unlogged sequences are not supported"))); - /* Check and set all option values */ init_params(seq->options, true, &new, &owned_by); --- 117,129 ---- bool null[SEQ_COL_LASTCOL]; int i; NameData name; + CommandContextData cmd; /* Unlogged sequences are not implemented -- not clear if useful. */ if (seq->sequence->relpersistence == RELPERSISTENCE_UNLOGGED) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("unlogged sequences are not supported"))); /* Check and set all option values */ init_params(seq->options, true, &new, &owned_by); *************** *** 211,217 **** DefineSequence(CreateSeqStmt *seq) stmt->tablespacename = NULL; stmt->if_not_exists = false; ! seqoid = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId); Assert(seqoid != InvalidOid); rel = heap_open(seqoid, AccessExclusiveLock); --- 213,222 ---- stmt->tablespacename = NULL; stmt->if_not_exists = false; ! /* Prepare BEFORE CREATE SEQUENCE triggers */ ! InitCommandContext(&cmd, (Node *)seq); ! ! seqoid = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, &cmd); Assert(seqoid != InvalidOid); rel = heap_open(seqoid, AccessExclusiveLock); *************** *** 226,231 **** DefineSequence(CreateSeqStmt *seq) --- 231,243 ---- process_owned_by(rel, owned_by); heap_close(rel, NoLock); + + /* Call AFTER CREATE SEQUENCE triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = seqoid; + ExecAfterCommandTriggers(&cmd); + } } /* *************** *** 423,428 **** AlterSequence(AlterSeqStmt *stmt) --- 435,441 ---- Form_pg_sequence seq; FormData_pg_sequence new; List *owned_by; + CommandContextData cmd; /* Open and lock sequence. */ relid = RangeVarGetRelid(stmt->sequence, AccessShareLock, stmt->missing_ok); *************** *** 458,463 **** AlterSequence(AlterSeqStmt *stmt) --- 471,490 ---- /* Now okay to update the on-disk tuple */ memcpy(seq, &new, sizeof(FormData_pg_sequence)); + /* + * Call BEFORE ALTER SEQUENCE triggers + */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = relid; + cmd.objectname = pstrdup(stmt->sequence->relname); + cmd.schemaname = pstrdup(stmt->sequence->schemaname); + + ExecBeforeCommandTriggers(&cmd); + } + START_CRIT_SECTION(); MarkBufferDirty(buf); *************** *** 496,501 **** AlterSequence(AlterSeqStmt *stmt) --- 523,532 ---- process_owned_by(seqrel, owned_by); relation_close(seqrel, NoLock); + + /* Call AFTER ALTER SEQUENCE triggers */ + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** *** 41,46 **** --- 41,47 ---- #include "catalog/pg_type_fn.h" #include "catalog/storage.h" #include "catalog/toasting.h" + #include "commands/cmdtrigger.h" #include "commands/cluster.h" #include "commands/comment.h" #include "commands/defrem.h" *************** *** 73,78 **** --- 74,80 ---- #include "storage/lock.h" #include "storage/predicate.h" #include "storage/smgr.h" + #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" *************** *** 413,419 **** static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, * ---------------------------------------------------------------- */ Oid ! DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId) { char relname[NAMEDATALEN]; Oid namespaceId; --- 415,421 ---- * ---------------------------------------------------------------- */ Oid ! DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, CommandContext cmd) { char relname[NAMEDATALEN]; Oid namespaceId; *************** *** 606,611 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId) --- 608,623 ---- } } + /* Exec BEFORE CREATE view|sequence|table|type command triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = pstrdup(relname); + cmd->schemaname = get_namespace_name(namespaceId); + + ExecBeforeCommandTriggers(cmd); + } + /* * Create the relation. Inherited defaults and constraints are passed in * for immediate handling --- since they don't need parsing, they can be *************** *** 668,673 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId) --- 680,686 ---- */ relation_close(rel, NoLock); + /* AFTER command triggers are fired from well outside this function */ return relationId; } *************** *** 738,743 **** RemoveRelations(DropStmt *drop) --- 751,758 ---- ObjectAddresses *objects; char relkind; ListCell *cell; + int i = 0, n = list_length(drop->objects); + CommandContext *cmds = (CommandContext *) palloc(n * sizeof(CommandContext)); /* * First we identify all the relations, then we delete them in a single *************** *** 784,789 **** RemoveRelations(DropStmt *drop) --- 799,805 ---- Oid relOid; ObjectAddress obj; struct DropRelationCallbackState state; + CommandContextData cmd; /* * These next few steps are a great deal like relation_openrv, but we *************** *** 809,817 **** RemoveRelations(DropStmt *drop) --- 825,849 ---- if (!OidIsValid(relOid)) { DropErrorMsgNonExistent(rel->relname, relkind, drop->missing_ok); + cmds[i++] = NULL; continue; } + /* + * Call BEFORE DROP command triggers + */ + InitCommandContext(&cmd, (Node *)drop); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = relOid; + cmd.objectname = get_rel_name(relOid); + cmd.schemaname = get_namespace_name(get_rel_namespace(relOid)); + + ExecBeforeCommandTriggers(&cmd); + } + cmds[i++] = &cmd; + /* OK, we're ready to delete this one */ obj.classId = RelationRelationId; obj.objectId = relOid; *************** *** 822,827 **** RemoveRelations(DropStmt *drop) --- 854,869 ---- performMultipleDeletions(objects, drop->behavior, 0); + /* Call AFTER DROP command triggers */ + for(i = 0; iobjectId = InvalidOid; + ExecAfterCommandTriggers(cmds[i]); + } + } + free_object_addresses(objects); } *************** *** 1139,1145 **** ExecuteTruncate(TruncateStmt *stmt) /* * Reconstruct the indexes to match, and we're done. */ ! reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST); } } --- 1181,1187 ---- /* * Reconstruct the indexes to match, and we're done. */ ! reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST, NULL); } } *************** *** 2300,2306 **** RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid, * renameatt - changes the name of a attribute in a relation */ void ! renameatt(RenameStmt *stmt) { Oid relid; --- 2342,2348 ---- * renameatt - changes the name of a attribute in a relation */ void ! renameatt(RenameStmt *stmt, CommandContext cmd) { Oid relid; *************** *** 2318,2323 **** renameatt(RenameStmt *stmt) --- 2360,2375 ---- return; } + /* BEFORE command triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = relid; + cmd->objectname = stmt->relation->relname; + cmd->schemaname = stmt->relation->schemaname; + + ExecBeforeCommandTriggers(cmd); + } + renameatt_internal(relid, stmt->subname, /* old att name */ stmt->newname, /* new att name */ *************** *** 2325,2330 **** renameatt(RenameStmt *stmt) --- 2377,2386 ---- false, /* recursing? */ 0, /* expected inhcount */ stmt->behavior); + + /* AFTER command triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } *************** *** 2337,2343 **** rename_constraint_internal(Oid myrelid, const char *newconname, bool recurse, bool recursing, ! int expected_parents) { Relation targetrelation; Oid constraintOid; --- 2393,2400 ---- const char *newconname, bool recurse, bool recursing, ! int expected_parents, ! CommandContext cmd) { Relation targetrelation; Oid constraintOid; *************** *** 2356,2361 **** rename_constraint_internal(Oid myrelid, --- 2413,2427 ---- constraintOid); con = (Form_pg_constraint) GETSTRUCT(tuple); + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = myrelid; + cmd->objectname = RelationGetRelationName(targetrelation); + cmd->schemaname = get_namespace_name(RelationGetNamespace(targetrelation)); + + ExecBeforeCommandTriggers(cmd); + } + if (con->contype == CONSTRAINT_CHECK && !con->conisonly) { if (recurse) *************** *** 2376,2382 **** rename_constraint_internal(Oid myrelid, if (childrelid == myrelid) continue; ! rename_constraint_internal(childrelid, oldconname, newconname, false, true, numparents); } } else --- 2442,2448 ---- if (childrelid == myrelid) continue; ! rename_constraint_internal(childrelid, oldconname, newconname, false, true, numparents, NULL); } } else *************** *** 2401,2417 **** rename_constraint_internal(Oid myrelid, || con->contype == CONSTRAINT_UNIQUE || con->contype == CONSTRAINT_EXCLUSION)) /* rename the index; this renames the constraint as well */ ! RenameRelationInternal(con->conindid, newconname); else RenameConstraintById(constraintOid, newconname); ReleaseSysCache(tuple); relation_close(targetrelation, NoLock); /* close rel but keep lock */ } void ! RenameConstraint(RenameStmt *stmt) { Oid relid; --- 2467,2486 ---- || con->contype == CONSTRAINT_UNIQUE || con->contype == CONSTRAINT_EXCLUSION)) /* rename the index; this renames the constraint as well */ ! RenameRelationInternal(con->conindid, newconname, NULL); else RenameConstraintById(constraintOid, newconname); ReleaseSysCache(tuple); relation_close(targetrelation, NoLock); /* close rel but keep lock */ + + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } void ! RenameConstraint(RenameStmt *stmt, CommandContext cmd) { Oid relid; *************** *** 2426,2439 **** RenameConstraint(RenameStmt *stmt) stmt->newname, interpretInhOption(stmt->relation->inhOpt), /* recursive? */ false, /* recursing? */ ! 0 /* expected inhcount */); } /* * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/FOREIGN TABLE RENAME */ void ! RenameRelation(RenameStmt *stmt) { Oid relid; --- 2495,2509 ---- stmt->newname, interpretInhOption(stmt->relation->inhOpt), /* recursive? */ false, /* recursing? */ ! 0 /* expected inhcount */, ! cmd); } /* * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/FOREIGN TABLE RENAME */ void ! RenameRelation(RenameStmt *stmt, CommandContext cmd) { Oid relid; *************** *** 2458,2464 **** RenameRelation(RenameStmt *stmt) } /* Do the work */ ! RenameRelationInternal(relid, stmt->newname); } /* --- 2528,2534 ---- } /* Do the work */ ! RenameRelationInternal(relid, stmt->newname, cmd); } /* *************** *** 2471,2477 **** RenameRelation(RenameStmt *stmt) * sequence, AFAIK there's no need for it to be there. */ void ! RenameRelationInternal(Oid myrelid, const char *newrelname) { Relation targetrelation; Relation relrelation; /* for RELATION relation */ --- 2541,2547 ---- * sequence, AFAIK there's no need for it to be there. */ void ! RenameRelationInternal(Oid myrelid, const char *newrelname, CommandContext cmd) { Relation targetrelation; Relation relrelation; /* for RELATION relation */ *************** *** 2502,2507 **** RenameRelationInternal(Oid myrelid, const char *newrelname) --- 2572,2587 ---- errmsg("relation \"%s\" already exists", newrelname))); + /* Call BEFORE ALTER relation triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(reltup); + cmd->objectname = pstrdup(NameStr(relform->relname)); + cmd->schemaname = get_namespace_name(namespaceId); + + ExecBeforeCommandTriggers(cmd); + } + /* * Update pg_class tuple with new relname. (Scribbling on reltup is OK * because it's a copy...) *************** *** 2521,2527 **** RenameRelationInternal(Oid myrelid, const char *newrelname) */ if (OidIsValid(targetrelation->rd_rel->reltype)) RenameTypeInternal(targetrelation->rd_rel->reltype, ! newrelname, namespaceId); /* * Also rename the associated constraint, if any. --- 2601,2607 ---- */ if (OidIsValid(targetrelation->rd_rel->reltype)) RenameTypeInternal(targetrelation->rd_rel->reltype, ! newrelname, namespaceId, NULL); /* * Also rename the associated constraint, if any. *************** *** 2538,2543 **** RenameRelationInternal(Oid myrelid, const char *newrelname) --- 2618,2630 ---- * Close rel, but keep exclusive lock! */ relation_close(targetrelation, NoLock); + + /* Call AFTER ALTER relation triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newrelname); + ExecAfterCommandTriggers(cmd); + } } /* *************** *** 2636,2642 **** AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode) * Thanks to the magic of MVCC, an error anywhere along the way rolls back * the whole operation; we don't have to do anything special to clean up. * ! * The caller must lock the relation, with an appropriate lock level * for the subcommands requested. Any subcommand that needs to rewrite * tuples in the table forces the whole command to be executed with * AccessExclusiveLock (actually, that is currently required always, but --- 2723,2729 ---- * Thanks to the magic of MVCC, an error anywhere along the way rolls back * the whole operation; we don't have to do anything special to clean up. * ! * The caller must lock the relation, with an appropriate lock level * for the subcommands requested. Any subcommand that needs to rewrite * tuples in the table forces the whole command to be executed with * AccessExclusiveLock (actually, that is currently required always, but *************** *** 2650,2663 **** void --- 2737,2768 ---- AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt) { Relation rel; + CommandContextData cmd; /* Caller is required to provide an adequate lock. */ rel = relation_open(relid, NoLock); CheckTableNotInUse(rel, "ALTER TABLE"); + /* Call BEFORE ALTER TABLE triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = relid; + cmd.objectname = stmt->relation->relname; + cmd.schemaname = get_namespace_name( + RangeVarGetCreationNamespace(stmt->relation)); + + ExecBeforeCommandTriggers(&cmd); + } + ATController(rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt), lockmode); + + /* Call AFTER ALTER TABLE triggers */ + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } /* *************** *** 5436,5442 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel, check_rights, skip_build, quiet, ! false); /* * If TryReuseIndex() stashed a relfilenode for us, we used it for the new --- 5541,5547 ---- check_rights, skip_build, quiet, ! false, NULL); /* * If TryReuseIndex() stashed a relfilenode for us, we used it for the new *************** *** 5495,5501 **** ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel, ereport(NOTICE, (errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"", indexName, constraintName))); ! RenameRelationInternal(index_oid, constraintName); } /* Extra checks needed if making primary key */ --- 5600,5606 ---- ereport(NOTICE, (errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"", indexName, constraintName))); ! RenameRelationInternal(index_oid, constraintName, NULL); } /* Extra checks needed if making primary key */ *************** *** 6973,6979 **** ATExecDropConstraint(Relation rel, const char *constrName, /* Right now only CHECK constraints can be inherited */ if (con->contype == CONSTRAINT_CHECK) is_check_constraint = true; ! if (con->conisonly) { Assert(is_check_constraint); --- 7078,7084 ---- /* Right now only CHECK constraints can be inherited */ if (con->contype == CONSTRAINT_CHECK) is_check_constraint = true; ! if (con->conisonly) { Assert(is_check_constraint); *************** *** 9757,9762 **** AlterTableNamespace(AlterObjectSchemaStmt *stmt) --- 9862,9868 ---- Oid nspOid; Relation classRel; RangeVar *newrv; + CommandContextData cmd; relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock, stmt->missing_ok, false, *************** *** 9797,9809 **** AlterTableNamespace(AlterObjectSchemaStmt *stmt) /* common checks on switching namespaces */ CheckSetNamespace(oldNspOid, nspOid, RelationRelationId, relid); /* OK, modify the pg_class row and pg_depend entry */ classRel = heap_open(RelationRelationId, RowExclusiveLock); AlterRelationNamespaceInternal(classRel, relid, oldNspOid, nspOid, true); /* Fix the table's row type too */ ! AlterTypeNamespaceInternal(rel->rd_rel->reltype, nspOid, false, false); /* Fix other dependent stuff */ if (rel->rd_rel->relkind == RELKIND_RELATION) --- 9903,9927 ---- /* common checks on switching namespaces */ CheckSetNamespace(oldNspOid, nspOid, RelationRelationId, relid); + /* Call BEFORE ALTER TABLE triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = relid; + cmd.objectname = stmt->relation->relname; + cmd.schemaname = get_namespace_name(oldNspOid); + + ExecBeforeCommandTriggers(&cmd); + } + /* OK, modify the pg_class row and pg_depend entry */ classRel = heap_open(RelationRelationId, RowExclusiveLock); AlterRelationNamespaceInternal(classRel, relid, oldNspOid, nspOid, true); /* Fix the table's row type too */ ! AlterTypeNamespaceInternal(rel->rd_rel->reltype, nspOid, false, false, NULL); /* Fix other dependent stuff */ if (rel->rd_rel->relkind == RELKIND_RELATION) *************** *** 9818,9823 **** AlterTableNamespace(AlterObjectSchemaStmt *stmt) --- 9936,9948 ---- /* close rel, but keep lock until commit */ relation_close(rel, NoLock); + + /* Call AFTER ALTER TABLE triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.schemaname = get_namespace_name(nspOid); + ExecAfterCommandTriggers(&cmd); + } } /* *************** *** 9965,9971 **** AlterSeqNamespaces(Relation classRel, Relation rel, * them to the new namespace, too. */ AlterTypeNamespaceInternal(RelationGetForm(seqRel)->reltype, ! newNspOid, false, false); /* Now we can close it. Keep the lock till end of transaction. */ relation_close(seqRel, NoLock); --- 10090,10096 ---- * them to the new namespace, too. */ AlterTypeNamespaceInternal(RelationGetForm(seqRel)->reltype, ! newNspOid, false, false, NULL); /* Now we can close it. Keep the lock till end of transaction. */ relation_close(seqRel, NoLock); *** a/src/backend/commands/tablespace.c --- b/src/backend/commands/tablespace.c *************** *** 67,72 **** --- 67,73 ---- #include "postmaster/bgwriter.h" #include "storage/fd.h" #include "storage/standby.h" + #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" *************** *** 365,370 **** CreateTableSpace(CreateTableSpaceStmt *stmt) --- 366,372 ---- /* We keep the lock on pg_tablespace until commit */ heap_close(rel, NoLock); + #else /* !HAVE_SYMLINK */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), *************** *** 527,532 **** DropTableSpace(DropTableSpaceStmt *stmt) --- 529,535 ---- /* We keep the lock on pg_tablespace until commit */ heap_close(rel, NoLock); + #else /* !HAVE_SYMLINK */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), *** a/src/backend/commands/trigger.c --- b/src/backend/commands/trigger.c *************** *** 144,149 **** CreateTrigger(CreateTrigStmt *stmt, const char *queryString, --- 144,150 ---- Oid constrrelid = InvalidOid; ObjectAddress myself, referenced; + CommandContextData cmd; rel = heap_openrv(stmt->relation, AccessExclusiveLock); *************** *** 426,431 **** CreateTrigger(CreateTrigStmt *stmt, const char *queryString, --- 427,449 ---- } /* + * Call BEFORE CREATE TRIGGER triggers + */ + if (!isInternal) + { + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = stmt->trigname; + cmd.schemaname = NULL; /* triggers are not schema qualified */ + + ExecBeforeCommandTriggers(&cmd); + } + } + + /* * If it's a user-entered CREATE CONSTRAINT TRIGGER command, make a * corresponding pg_constraint entry. */ *************** *** 761,766 **** CreateTrigger(CreateTrigStmt *stmt, const char *queryString, --- 779,790 ---- /* Keep lock on target rel until end of xact */ heap_close(rel, NoLock); + /* Call AFTER CREATE TRIGGER triggers */ + if (!isInternal && CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = trigoid; + ExecAfterCommandTriggers(&cmd); + } return trigoid; } *************** *** 1208,1214 **** RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid, * update row in catalog */ void ! renametrig(RenameStmt *stmt) { Relation targetrel; Relation tgrel; --- 1232,1238 ---- * update row in catalog */ void ! renametrig(RenameStmt *stmt, CommandContext cmd) { Relation targetrel; Relation tgrel; *************** *** 1275,1280 **** renametrig(RenameStmt *stmt) --- 1299,1314 ---- SnapshotNow, 2, key); if (HeapTupleIsValid(tuple = systable_getnext(tgscan))) { + /* Call BEFORE ALTER TRIGGER triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tuple); + cmd->objectname = stmt->subname; + cmd->schemaname = get_namespace_name(RelationGetNamespace(targetrel)); + + ExecBeforeCommandTriggers(cmd); + } + /* * Update pg_trigger tuple with new tgname. */ *************** *** 1311,1316 **** renametrig(RenameStmt *stmt) --- 1345,1357 ---- * Close rel, but keep exclusive lock! */ relation_close(targetrel, NoLock); + + /* Call AFTER ALTER TRIGGER triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = stmt->newname; + ExecAfterCommandTriggers(cmd); + } } *** a/src/backend/commands/tsearchcmds.c --- b/src/backend/commands/tsearchcmds.c *************** *** 32,37 **** --- 32,38 ---- #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" #include "commands/alter.h" + #include "commands/cmdtrigger.h" #include "commands/defrem.h" #include "miscadmin.h" #include "nodes/makefuncs.h" *************** *** 167,173 **** makeParserDependencies(HeapTuple tuple) * CREATE TEXT SEARCH PARSER */ void ! DefineTSParser(List *names, List *parameters) { char *prsname; ListCell *pl; --- 168,174 ---- * CREATE TEXT SEARCH PARSER */ void ! DefineTSParser(List *names, List *parameters, CommandContext cmd) { char *prsname; ListCell *pl; *************** *** 258,263 **** DefineTSParser(List *names, List *parameters) --- 259,276 ---- errmsg("text search parser lextypes method is required"))); /* + * Call BEFORE CREATE TEXT SEARCH PARSER triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = pstrdup(NameStr(pname)); + cmd->schemaname = get_namespace_name(namespaceoid); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Looks good, insert */ prsRel = heap_open(TSParserRelationId, RowExclusiveLock); *************** *** 277,282 **** DefineTSParser(List *names, List *parameters) --- 290,302 ---- heap_freetuple(tup); heap_close(prsRel, RowExclusiveLock); + + /* Call AFTER CREATE TEXT SEARCH PARSER triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = prsOid; + ExecAfterCommandTriggers(cmd); + } } /* *************** *** 306,312 **** RemoveTSParserById(Oid prsId) * ALTER TEXT SEARCH PARSER RENAME */ void ! RenameTSParser(List *oldname, const char *newname) { HeapTuple tup; Relation rel; --- 326,332 ---- * ALTER TEXT SEARCH PARSER RENAME */ void ! RenameTSParser(List *oldname, const char *newname, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 337,355 **** RenameTSParser(List *oldname, const char *newname) errmsg("text search parser \"%s\" already exists", newname))); namestrcpy(&(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); heap_close(rel, NoLock); heap_freetuple(tup); } /* * ALTER TEXT SEARCH PARSER any_name SET SCHEMA name */ void ! AlterTSParserNamespace(List *name, const char *newschema) { Oid prsId, nspOid; --- 357,392 ---- errmsg("text search parser \"%s\" already exists", newname))); + /* Call BEFORE ALTER TEXT SEARCH PARSER triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = prsId; + cmd->objectname = pstrdup(NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname)); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + namestrcpy(&(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER TEXT SEARCH PARSER triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } /* * ALTER TEXT SEARCH PARSER any_name SET SCHEMA name */ void ! AlterTSParserNamespace(List *name, const char *newschema, CommandContext cmd) { Oid prsId, nspOid; *************** *** 366,372 **** AlterTSParserNamespace(List *name, const char *newschema) prsId, nspOid, Anum_pg_ts_parser_prsname, Anum_pg_ts_parser_prsnamespace, ! -1, -1); heap_close(rel, RowExclusiveLock); } --- 403,409 ---- prsId, nspOid, Anum_pg_ts_parser_prsname, Anum_pg_ts_parser_prsnamespace, ! -1, -1, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 384,390 **** AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid) prsId, newNspOid, Anum_pg_ts_parser_prsname, Anum_pg_ts_parser_prsnamespace, ! -1, -1); heap_close(rel, RowExclusiveLock); --- 421,427 ---- prsId, newNspOid, Anum_pg_ts_parser_prsname, Anum_pg_ts_parser_prsnamespace, ! -1, -1, NULL); heap_close(rel, RowExclusiveLock); *************** *** 485,491 **** verify_dictoptions(Oid tmplId, List *dictoptions) * CREATE TEXT SEARCH DICTIONARY */ void ! DefineTSDictionary(List *names, List *parameters) { ListCell *pl; Relation dictRel; --- 522,528 ---- * CREATE TEXT SEARCH DICTIONARY */ void ! DefineTSDictionary(List *names, List *parameters, CommandContext cmd) { ListCell *pl; Relation dictRel; *************** *** 538,543 **** DefineTSDictionary(List *names, List *parameters) --- 575,592 ---- verify_dictoptions(templId, dictoptions); /* + * Call BEFORE CREATE TEXT SEARCH DICTIONARY triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = pstrdup(dictname); + cmd->schemaname = get_namespace_name(namespaceoid); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Looks good, insert */ memset(values, 0, sizeof(values)); *************** *** 571,583 **** DefineTSDictionary(List *names, List *parameters) heap_freetuple(tup); heap_close(dictRel, RowExclusiveLock); } /* * ALTER TEXT SEARCH DICTIONARY RENAME */ void ! RenameTSDictionary(List *oldname, const char *newname) { HeapTuple tup; Relation rel; --- 620,639 ---- heap_freetuple(tup); heap_close(dictRel, RowExclusiveLock); + + /* Call AFTER CREATE TEXT SEARCH DICTIONARY triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = dictOid; + ExecAfterCommandTriggers(cmd); + } } /* * ALTER TEXT SEARCH DICTIONARY RENAME */ void ! RenameTSDictionary(List *oldname, const char *newname, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 616,634 **** RenameTSDictionary(List *oldname, const char *newname) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); namestrcpy(&(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); heap_close(rel, NoLock); heap_freetuple(tup); } /* * ALTER TEXT SEARCH DICTIONARY any_name SET SCHEMA name */ void ! AlterTSDictionaryNamespace(List *name, const char *newschema) { Oid dictId, nspOid; --- 672,707 ---- aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* Call BEFORE ALTER TEXT SEARCH DICTIONARY triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = dictId; + cmd->objectname = pstrdup(NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname)); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + namestrcpy(&(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER TEXT SEARCH DICTIONARY triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } /* * ALTER TEXT SEARCH DICTIONARY any_name SET SCHEMA name */ void ! AlterTSDictionaryNamespace(List *name, const char *newschema, CommandContext cmd) { Oid dictId, nspOid; *************** *** 646,652 **** AlterTSDictionaryNamespace(List *name, const char *newschema) Anum_pg_ts_dict_dictname, Anum_pg_ts_dict_dictnamespace, Anum_pg_ts_dict_dictowner, ! ACL_KIND_TSDICTIONARY); heap_close(rel, RowExclusiveLock); } --- 719,725 ---- Anum_pg_ts_dict_dictname, Anum_pg_ts_dict_dictnamespace, Anum_pg_ts_dict_dictowner, ! ACL_KIND_TSDICTIONARY, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 665,671 **** AlterTSDictionaryNamespace_oid(Oid dictId, Oid newNspOid) Anum_pg_ts_dict_dictname, Anum_pg_ts_dict_dictnamespace, Anum_pg_ts_dict_dictowner, ! ACL_KIND_TSDICTIONARY); heap_close(rel, RowExclusiveLock); --- 738,744 ---- Anum_pg_ts_dict_dictname, Anum_pg_ts_dict_dictnamespace, Anum_pg_ts_dict_dictowner, ! ACL_KIND_TSDICTIONARY, NULL); heap_close(rel, RowExclusiveLock); *************** *** 713,718 **** AlterTSDictionary(AlterTSDictionaryStmt *stmt) --- 786,792 ---- Datum repl_val[Natts_pg_ts_dict]; bool repl_null[Natts_pg_ts_dict]; bool repl_repl[Natts_pg_ts_dict]; + CommandContextData cmd; dictId = get_ts_dict_oid(stmt->dictname, false); *************** *** 776,781 **** AlterTSDictionary(AlterTSDictionaryStmt *stmt) --- 850,868 ---- verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(tup))->dicttemplate, dictoptions); + /* Call BEFORE ALTER TEXT SEARCH DICTIONARY command triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + Form_pg_ts_dict form = (Form_pg_ts_dict) GETSTRUCT(tup); + cmd.objectId = dictId; + cmd.objectname = pstrdup(NameStr(form->dictname)); + cmd.schemaname = get_namespace_name(form->dictnamespace); + + ExecBeforeCommandTriggers(&cmd); + } + /* * Looks good, update */ *************** *** 807,819 **** AlterTSDictionary(AlterTSDictionaryStmt *stmt) ReleaseSysCache(tup); heap_close(rel, RowExclusiveLock); } /* * ALTER TEXT SEARCH DICTIONARY OWNER */ void ! AlterTSDictionaryOwner(List *name, Oid newOwnerId) { HeapTuple tup; Relation rel; --- 894,910 ---- ReleaseSysCache(tup); heap_close(rel, RowExclusiveLock); + + /* Call AFTER ALTER TEXT SEARCH DICTIONARY command triggers */ + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } /* * ALTER TEXT SEARCH DICTIONARY OWNER */ void ! AlterTSDictionaryOwner(List *name, Oid newOwnerId, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 855,860 **** AlterTSDictionaryOwner(List *name, Oid newOwnerId) --- 946,961 ---- get_namespace_name(namespaceOid)); } + /* Call BEFORE ALTER TEXT SEARCH DICTIONARY triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = dictId; + cmd->objectname = pstrdup(NameStr(form->dictname)); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + form->dictowner = newOwnerId; simple_heap_update(rel, &tup->t_self, tup); *************** *** 863,868 **** AlterTSDictionaryOwner(List *name, Oid newOwnerId) --- 964,973 ---- /* Update owner dependency reference */ changeDependencyOnOwner(TSDictionaryRelationId, HeapTupleGetOid(tup), newOwnerId); + + /* Call AFTER ALTER TEXT SEARCH DICTIONARY triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } heap_close(rel, NoLock); *************** *** 957,963 **** makeTSTemplateDependencies(HeapTuple tuple) * CREATE TEXT SEARCH TEMPLATE */ void ! DefineTSTemplate(List *names, List *parameters) { ListCell *pl; Relation tmplRel; --- 1062,1068 ---- * CREATE TEXT SEARCH TEMPLATE */ void ! DefineTSTemplate(List *names, List *parameters, CommandContext cmd) { ListCell *pl; Relation tmplRel; *************** *** 1023,1028 **** DefineTSTemplate(List *names, List *parameters) --- 1128,1145 ---- errmsg("text search template lexize method is required"))); /* + * Call BEFORE CREATE TEXT SEARCH TEMPLATE triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = pstrdup(NameStr(dname)); + cmd->schemaname = get_namespace_name(namespaceoid); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Looks good, insert */ *************** *** 1043,1055 **** DefineTSTemplate(List *names, List *parameters) heap_freetuple(tup); heap_close(tmplRel, RowExclusiveLock); } /* * ALTER TEXT SEARCH TEMPLATE RENAME */ void ! RenameTSTemplate(List *oldname, const char *newname) { HeapTuple tup; Relation rel; --- 1160,1179 ---- heap_freetuple(tup); heap_close(tmplRel, RowExclusiveLock); + + /* Call AFTER CREATE TEXT SEARCH TEMPLATE triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = dictOid; + ExecAfterCommandTriggers(cmd); + } } /* * ALTER TEXT SEARCH TEMPLATE RENAME */ void ! RenameTSTemplate(List *oldname, const char *newname, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 1081,1099 **** RenameTSTemplate(List *oldname, const char *newname) errmsg("text search template \"%s\" already exists", newname))); namestrcpy(&(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); heap_close(rel, NoLock); heap_freetuple(tup); } /* * ALTER TEXT SEARCH TEMPLATE any_name SET SCHEMA name */ void ! AlterTSTemplateNamespace(List *name, const char *newschema) { Oid tmplId, nspOid; --- 1205,1240 ---- errmsg("text search template \"%s\" already exists", newname))); + /* Call BEFORE ALTER TEXT SEARCH TEMPLATE triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = tmplId; + cmd->objectname = pstrdup(NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname)); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + namestrcpy(&(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER TEXT SEARCH TEMPLATE triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } /* * ALTER TEXT SEARCH TEMPLATE any_name SET SCHEMA name */ void ! AlterTSTemplateNamespace(List *name, const char *newschema, CommandContext cmd) { Oid tmplId, nspOid; *************** *** 1110,1116 **** AlterTSTemplateNamespace(List *name, const char *newschema) tmplId, nspOid, Anum_pg_ts_template_tmplname, Anum_pg_ts_template_tmplnamespace, ! -1, -1); heap_close(rel, RowExclusiveLock); } --- 1251,1257 ---- tmplId, nspOid, Anum_pg_ts_template_tmplname, Anum_pg_ts_template_tmplnamespace, ! -1, -1, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 1128,1134 **** AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid) tmplId, newNspOid, Anum_pg_ts_template_tmplname, Anum_pg_ts_template_tmplnamespace, ! -1, -1); heap_close(rel, RowExclusiveLock); --- 1269,1275 ---- tmplId, newNspOid, Anum_pg_ts_template_tmplname, Anum_pg_ts_template_tmplnamespace, ! -1, -1, NULL); heap_close(rel, RowExclusiveLock); *************** *** 1276,1282 **** makeConfigurationDependencies(HeapTuple tuple, bool removeOld, * CREATE TEXT SEARCH CONFIGURATION */ void ! DefineTSConfiguration(List *names, List *parameters) { Relation cfgRel; Relation mapRel = NULL; --- 1417,1423 ---- * CREATE TEXT SEARCH CONFIGURATION */ void ! DefineTSConfiguration(List *names, List *parameters, CommandContext cmd) { Relation cfgRel; Relation mapRel = NULL; *************** *** 1353,1358 **** DefineTSConfiguration(List *names, List *parameters) --- 1494,1511 ---- errmsg("text search parser is required"))); /* + * Call BEFORE CREATE TEXT SEARCH CONFIGURATION triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = pstrdup(cfgname); + cmd->schemaname = get_namespace_name(namespaceoid); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Looks good, build tuple and insert */ memset(values, 0, sizeof(values)); *************** *** 1429,1441 **** DefineTSConfiguration(List *names, List *parameters) if (mapRel) heap_close(mapRel, RowExclusiveLock); heap_close(cfgRel, RowExclusiveLock); } /* * ALTER TEXT SEARCH CONFIGURATION RENAME */ void ! RenameTSConfiguration(List *oldname, const char *newname) { HeapTuple tup; Relation rel; --- 1582,1601 ---- if (mapRel) heap_close(mapRel, RowExclusiveLock); heap_close(cfgRel, RowExclusiveLock); + + /* Call AFTER CREATE TEXT SEARCH PARSER triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = cfgOid; + ExecAfterCommandTriggers(cmd); + } } /* * ALTER TEXT SEARCH CONFIGURATION RENAME */ void ! RenameTSConfiguration(List *oldname, const char *newname, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 1473,1491 **** RenameTSConfiguration(List *oldname, const char *newname) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); namestrcpy(&(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); heap_close(rel, NoLock); heap_freetuple(tup); } /* * ALTER TEXT SEARCH CONFIGURATION any_name SET SCHEMA name */ void ! AlterTSConfigurationNamespace(List *name, const char *newschema) { Oid cfgId, nspOid; --- 1633,1668 ---- aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* Call BEFORE ALTER TEXT SEARCH CONFIGURATION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = cfgId; + cmd->objectname = pstrdup(NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname)); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + namestrcpy(&(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER TEXT SEARCH CONFIGURATION triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = pstrdup(newname); + ExecAfterCommandTriggers(cmd); + } } /* * ALTER TEXT SEARCH CONFIGURATION any_name SET SCHEMA name */ void ! AlterTSConfigurationNamespace(List *name, const char *newschema, CommandContext cmd) { Oid cfgId, nspOid; *************** *** 1503,1509 **** AlterTSConfigurationNamespace(List *name, const char *newschema) Anum_pg_ts_config_cfgname, Anum_pg_ts_config_cfgnamespace, Anum_pg_ts_config_cfgowner, ! ACL_KIND_TSCONFIGURATION); heap_close(rel, RowExclusiveLock); } --- 1680,1686 ---- Anum_pg_ts_config_cfgname, Anum_pg_ts_config_cfgnamespace, Anum_pg_ts_config_cfgowner, ! ACL_KIND_TSCONFIGURATION, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 1522,1528 **** AlterTSConfigurationNamespace_oid(Oid cfgId, Oid newNspOid) Anum_pg_ts_config_cfgname, Anum_pg_ts_config_cfgnamespace, Anum_pg_ts_config_cfgowner, ! ACL_KIND_TSCONFIGURATION); heap_close(rel, RowExclusiveLock); --- 1699,1705 ---- Anum_pg_ts_config_cfgname, Anum_pg_ts_config_cfgnamespace, Anum_pg_ts_config_cfgowner, ! ACL_KIND_TSCONFIGURATION, NULL); heap_close(rel, RowExclusiveLock); *************** *** 1581,1587 **** RemoveTSConfigurationById(Oid cfgId) * ALTER TEXT SEARCH CONFIGURATION OWNER */ void ! AlterTSConfigurationOwner(List *name, Oid newOwnerId) { HeapTuple tup; Relation rel; --- 1758,1764 ---- * ALTER TEXT SEARCH CONFIGURATION OWNER */ void ! AlterTSConfigurationOwner(List *name, Oid newOwnerId, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 1623,1628 **** AlterTSConfigurationOwner(List *name, Oid newOwnerId) --- 1800,1815 ---- get_namespace_name(namespaceOid)); } + /* Call BEFORE ALTER TEXT SEARCH CONFIGURATION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = cfgId; + cmd->objectname = pstrdup(NameStr(form->cfgname)); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + form->cfgowner = newOwnerId; simple_heap_update(rel, &tup->t_self, tup); *************** *** 1631,1638 **** AlterTSConfigurationOwner(List *name, Oid newOwnerId) /* Update owner dependency reference */ changeDependencyOnOwner(TSConfigRelationId, HeapTupleGetOid(tup), newOwnerId); - } heap_close(rel, NoLock); heap_freetuple(tup); } --- 1818,1828 ---- /* Update owner dependency reference */ changeDependencyOnOwner(TSConfigRelationId, HeapTupleGetOid(tup), newOwnerId); + /* Call AFTER ALTER TEXT SEARCH CONFIGURATION triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); + } heap_close(rel, NoLock); heap_freetuple(tup); } *************** *** 1645,1650 **** AlterTSConfiguration(AlterTSConfigurationStmt *stmt) --- 1835,1841 ---- { HeapTuple tup; Relation relMap; + CommandContextData cmd; /* Find the configuration */ tup = GetTSConfigTuple(stmt->cfgname); *************** *** 1659,1664 **** AlterTSConfiguration(AlterTSConfigurationStmt *stmt) --- 1850,1869 ---- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION, NameListToString(stmt->cfgname)); + /* Call BEFORE ALTER TEXT SEARCH CONFIGURATION command triggers */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + Form_pg_ts_config form = (Form_pg_ts_config) GETSTRUCT(tup); + + cmd.objectId = HeapTupleGetOid(tup); + cmd.objectname = pstrdup(NameStr(form->cfgname)); + cmd.schemaname = get_namespace_name(form->cfgnamespace); + + ExecBeforeCommandTriggers(&cmd); + } + relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock); /* Add or drop mappings */ *************** *** 1673,1678 **** AlterTSConfiguration(AlterTSConfigurationStmt *stmt) --- 1878,1887 ---- heap_close(relMap, RowExclusiveLock); ReleaseSysCache(tup); + + /* Call AFTER ALTER TEXT SEARCH CONFIGURATION command triggers */ + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } /* *** a/src/backend/commands/typecmds.c --- b/src/backend/commands/typecmds.c *************** *** 49,54 **** --- 49,55 ---- #include "catalog/pg_range.h" #include "catalog/pg_type.h" #include "catalog/pg_type_fn.h" + #include "commands/cmdtrigger.h" #include "commands/defrem.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" *************** *** 62,67 **** --- 63,69 ---- #include "parser/parse_expr.h" #include "parser/parse_func.h" #include "parser/parse_type.h" + #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" *************** *** 111,117 **** static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, * Registers a new base type. */ void ! DefineType(List *names, List *parameters) { char *typeName; Oid typeNamespace; --- 113,119 ---- * Registers a new base type. */ void ! DefineType(List *names, List *parameters, CommandContext cmd) { char *typeName; Oid typeNamespace; *************** *** 542,547 **** DefineType(List *names, List *parameters) --- 544,560 ---- NameListToString(analyzeName)); #endif + /* + * Call BEFORE CREATE TYPE triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = pstrdup(typeName); + cmd->schemaname = get_namespace_name(typeNamespace); + + ExecBeforeCommandTriggers(cmd); + } array_oid = AssignTypeArrayOid(); /* *************** *** 625,630 **** DefineType(List *names, List *parameters) --- 638,650 ---- collation); /* type's collation */ pfree(array_type); + + /* Call AFTER CREATE AGGREGATE triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = typoid; + ExecAfterCommandTriggers(cmd); + } } /* *************** *** 706,711 **** DefineDomain(CreateDomainStmt *stmt) --- 726,732 ---- Form_pg_type baseType; int32 basetypeMod; Oid baseColl; + CommandContextData cmd; /* Convert list of names to a name and namespace */ domainNamespace = QualifiedNameGetCreationNamespace(stmt->domainname, *************** *** 969,974 **** DefineDomain(CreateDomainStmt *stmt) --- 990,1010 ---- } } + + /* + * Call BEFORE CREATE DOMAIN triggers + */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = (char *)domainName; + cmd.schemaname = get_namespace_name(domainNamespace); + + ExecBeforeCommandTriggers(&cmd); + } + /* * Have TypeCreate do all the real work. */ *************** *** 1036,1041 **** DefineDomain(CreateDomainStmt *stmt) --- 1072,1084 ---- * Now we can clean up. */ ReleaseSysCache(typeTup); + + /* Call AFTER CREATE DOMAIN triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = domainoid; + ExecAfterCommandTriggers(&cmd); + } } *************** *** 1053,1058 **** DefineEnum(CreateEnumStmt *stmt) --- 1096,1102 ---- AclResult aclresult; Oid old_type_oid; Oid enumArrayOid; + CommandContextData cmd; /* Convert list of names to a name and namespace */ enumNamespace = QualifiedNameGetCreationNamespace(stmt->typeName, *************** *** 1079,1084 **** DefineEnum(CreateEnumStmt *stmt) --- 1123,1142 ---- errmsg("type \"%s\" already exists", enumName))); } + /* + * Call BEFORE CREATE (enum) TYPE triggers + */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = enumName; + cmd.schemaname = get_namespace_name(enumNamespace); + + ExecBeforeCommandTriggers(&cmd); + } + enumArrayOid = AssignTypeArrayOid(); /* Create the pg_type entry */ *************** *** 1156,1161 **** DefineEnum(CreateEnumStmt *stmt) --- 1214,1226 ---- InvalidOid); /* type's collation */ pfree(enumArrayName); + + /* Call AFTER CREATE (enum) TYPE triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = enumTypeOid; + ExecAfterCommandTriggers(&cmd); + } } /* *************** *** 1240,1245 **** DefineRange(CreateRangeStmt *stmt) --- 1305,1311 ---- char alignment; AclResult aclresult; ListCell *lc; + CommandContextData cmd; /* Convert list of names to a name and namespace */ typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName, *************** *** 1387,1392 **** DefineRange(CreateRangeStmt *stmt) --- 1453,1472 ---- /* alignment must be 'i' or 'd' for ranges */ alignment = (subtypalign == 'd') ? 'd' : 'i'; + /* + * Call BEFORE CREATE EXTENSION triggers + */ + InitCommandContext(&cmd, (Node *)stmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = typeName; + cmd.schemaname = get_namespace_name(typeNamespace); + + ExecBeforeCommandTriggers(&cmd); + } + /* Allocate OID for array type */ rangeArrayOid = AssignTypeArrayOid(); *************** *** 1469,1474 **** DefineRange(CreateRangeStmt *stmt) --- 1549,1561 ---- /* And create the constructor functions for this range type */ makeRangeConstructors(typeName, typeNamespace, typoid, rangeSubtype); + + /* Call AFTER CREATE (range) TYPE triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = typoid; + ExecAfterCommandTriggers(&cmd); + } } /* *************** *** 1980,1986 **** AssignTypeArrayOid(void) *------------------------------------------------------------------- */ Oid ! DefineCompositeType(RangeVar *typevar, List *coldeflist) { CreateStmt *createStmt = makeNode(CreateStmt); Oid old_type_oid; --- 2067,2074 ---- *------------------------------------------------------------------- */ Oid ! DefineCompositeType(RangeVar *typevar, List *coldeflist, ! CommandContext cmd) { CreateStmt *createStmt = makeNode(CreateStmt); Oid old_type_oid; *************** *** 2024,2031 **** DefineCompositeType(RangeVar *typevar, List *coldeflist) /* * Finally create the relation. This also creates the type. */ ! relid = DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid); Assert(relid != InvalidOid); return relid; } --- 2112,2126 ---- /* * Finally create the relation. This also creates the type. */ ! relid = DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid, cmd); Assert(relid != InvalidOid); + + /* Call AFTER CREATE (composite) TYPE triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = relid; + ExecAfterCommandTriggers(cmd); + } return relid; } *************** *** 2035,2041 **** DefineCompositeType(RangeVar *typevar, List *coldeflist) * Routine implementing ALTER DOMAIN SET/DROP DEFAULT statements. */ void ! AlterDomainDefault(List *names, Node *defaultRaw) { TypeName *typename; Oid domainoid; --- 2130,2136 ---- * Routine implementing ALTER DOMAIN SET/DROP DEFAULT statements. */ void ! AlterDomainDefault(List *names, Node *defaultRaw, CommandContext cmd) { TypeName *typename; Oid domainoid; *************** *** 2065,2070 **** AlterDomainDefault(List *names, Node *defaultRaw) --- 2160,2175 ---- /* Check it's a domain and check user has permission for ALTER DOMAIN */ checkDomainOwner(tup); + /* Call BEFORE ALTER DOMAIN triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(typTup->typname)); + cmd->schemaname = get_namespace_name(typTup->typnamespace); + + ExecBeforeCommandTriggers(cmd); + } + /* Setup new tuple */ MemSet(new_record, (Datum) 0, sizeof(new_record)); MemSet(new_record_nulls, false, sizeof(new_record_nulls)); *************** *** 2161,2166 **** AlterDomainDefault(List *names, Node *defaultRaw) --- 2266,2275 ---- /* Clean up */ heap_close(rel, NoLock); heap_freetuple(newtuple); + + /* Call AFTER ALTER DOMAIN triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } /* *************** *** 2169,2175 **** AlterDomainDefault(List *names, Node *defaultRaw) * Routine implementing ALTER DOMAIN SET/DROP NOT NULL statements. */ void ! AlterDomainNotNull(List *names, bool notNull) { TypeName *typename; Oid domainoid; --- 2278,2284 ---- * Routine implementing ALTER DOMAIN SET/DROP NOT NULL statements. */ void ! AlterDomainNotNull(List *names, bool notNull, CommandContext cmd) { TypeName *typename; Oid domainoid; *************** *** 2192,2197 **** AlterDomainNotNull(List *names, bool notNull) --- 2301,2316 ---- /* Check it's a domain and check user has permission for ALTER DOMAIN */ checkDomainOwner(tup); + /* Call BEFORE ALTER DOMAIN triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(typTup->typname)); + cmd->schemaname = get_namespace_name(typTup->typnamespace); + + ExecBeforeCommandTriggers(cmd); + } + /* Is the domain already set to the desired constraint? */ if (typTup->typnotnull == notNull) { *************** *** 2257,2262 **** AlterDomainNotNull(List *names, bool notNull) --- 2376,2385 ---- /* Clean up */ heap_freetuple(tup); heap_close(typrel, RowExclusiveLock); + + /* Call AFTER ALTER DOMAIN triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } /* *************** *** 2266,2272 **** AlterDomainNotNull(List *names, bool notNull) */ void AlterDomainDropConstraint(List *names, const char *constrName, ! DropBehavior behavior, bool missing_ok) { TypeName *typename; Oid domainoid; --- 2389,2395 ---- */ void AlterDomainDropConstraint(List *names, const char *constrName, ! DropBehavior behavior, bool missing_ok, CommandContext cmd) { TypeName *typename; Oid domainoid; *************** *** 2292,2297 **** AlterDomainDropConstraint(List *names, const char *constrName, --- 2415,2432 ---- /* Check it's a domain and check user has permission for ALTER DOMAIN */ checkDomainOwner(tup); + /* Call BEFORE ALTER DOMAIN triggers */ + if (CommandFiresTriggers(cmd)) + { + Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup); + + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(typTup->typname)); + cmd->schemaname = get_namespace_name(typTup->typnamespace); + + ExecBeforeCommandTriggers(cmd); + } + /* Grab an appropriate lock on the pg_constraint relation */ conrel = heap_open(ConstraintRelationId, RowExclusiveLock); *************** *** 2341,2346 **** AlterDomainDropConstraint(List *names, const char *constrName, --- 2476,2485 ---- (errmsg("constraint \"%s\" of domain \"%s\" does not exist, skipping", constrName, TypeNameToString(typename)))); } + + /* Call AFTER ALTER DOMAIN triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } /* *************** *** 2349,2355 **** AlterDomainDropConstraint(List *names, const char *constrName, * Implements the ALTER DOMAIN .. ADD CONSTRAINT statement. */ void ! AlterDomainAddConstraint(List *names, Node *newConstraint) { TypeName *typename; Oid domainoid; --- 2488,2494 ---- * Implements the ALTER DOMAIN .. ADD CONSTRAINT statement. */ void ! AlterDomainAddConstraint(List *names, Node *newConstraint, CommandContext cmd) { TypeName *typename; Oid domainoid; *************** *** 2378,2383 **** AlterDomainAddConstraint(List *names, Node *newConstraint) --- 2517,2532 ---- elog(ERROR, "unrecognized node type: %d", (int) nodeTag(newConstraint)); + /* Call BEFORE ALTER DOMAIN triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(typTup->typname)); + cmd->schemaname = get_namespace_name(typTup->typnamespace); + + ExecBeforeCommandTriggers(cmd); + } + constr = (Constraint *) newConstraint; switch (constr->contype) *************** *** 2444,2449 **** AlterDomainAddConstraint(List *names, Node *newConstraint) --- 2593,2602 ---- /* Clean up */ heap_close(typrel, RowExclusiveLock); + + /* Call AFTER ALTER DOMAIN triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } /* *************** *** 2452,2458 **** AlterDomainAddConstraint(List *names, Node *newConstraint) * Implements the ALTER DOMAIN .. VALIDATE CONSTRAINT statement. */ void ! AlterDomainValidateConstraint(List *names, char *constrName) { TypeName *typename; Oid domainoid; --- 2605,2611 ---- * Implements the ALTER DOMAIN .. VALIDATE CONSTRAINT statement. */ void ! AlterDomainValidateConstraint(List *names, char *constrName, CommandContext cmd) { TypeName *typename; Oid domainoid; *************** *** 2525,2530 **** AlterDomainValidateConstraint(List *names, char *constrName) --- 2678,2695 ---- HeapTupleGetOid(tuple)); conbin = TextDatumGetCString(val); + /* Call BEFORE ALTER DOMAIN triggers */ + if (CommandFiresTriggers(cmd)) + { + Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup); + + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = pstrdup(NameStr(typTup->typname)); + cmd->schemaname = get_namespace_name(typTup->typnamespace); + + ExecBeforeCommandTriggers(cmd); + } + validateDomainConstraint(domainoid, conbin); /* *************** *** 2543,2548 **** AlterDomainValidateConstraint(List *names, char *constrName) --- 2708,2717 ---- heap_close(conrel, RowExclusiveLock); ReleaseSysCache(tup); + + /* Call AFTER ALTER DOMAIN triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } static void *************** *** 3091,3097 **** GetDomainConstraints(Oid typeOid) * Execute ALTER TYPE RENAME */ void ! RenameType(RenameStmt *stmt) { List *names = stmt->object; const char *newTypeName = stmt->newname; --- 3260,3266 ---- * Execute ALTER TYPE RENAME */ void ! RenameType(RenameStmt *stmt, CommandContext cmd) { List *names = stmt->object; const char *newTypeName = stmt->newname; *************** *** 3153,3162 **** RenameType(RenameStmt *stmt) * RenameRelationInternal will call RenameTypeInternal automatically. */ if (typTup->typtype == TYPTYPE_COMPOSITE) ! RenameRelationInternal(typTup->typrelid, newTypeName); else RenameTypeInternal(typeOid, newTypeName, ! typTup->typnamespace); /* Clean up */ heap_close(rel, RowExclusiveLock); --- 3322,3331 ---- * RenameRelationInternal will call RenameTypeInternal automatically. */ if (typTup->typtype == TYPTYPE_COMPOSITE) ! RenameRelationInternal(typTup->typrelid, newTypeName, cmd); else RenameTypeInternal(typeOid, newTypeName, ! typTup->typnamespace, cmd); /* Clean up */ heap_close(rel, RowExclusiveLock); *************** *** 3166,3172 **** RenameType(RenameStmt *stmt) * Change the owner of a type. */ void ! AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype) { TypeName *typename; Oid typeOid; --- 3335,3342 ---- * Change the owner of a type. */ void ! AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype, ! CommandContext cmd) { TypeName *typename; Oid typeOid; *************** *** 3252,3257 **** AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype) --- 3422,3437 ---- get_namespace_name(typTup->typnamespace)); } + /* Call BEFORE ALTER TYPE triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = typeOid; + cmd->objectname = pstrdup(NameStr(typTup->typname)); + cmd->schemaname = get_namespace_name(typTup->typnamespace); + + ExecBeforeCommandTriggers(cmd); + } + /* * If it's a composite type, invoke ATExecChangeOwner so that we fix * up the pg_class entry properly. That will call back to *************** *** 3279,3284 **** AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype) --- 3459,3468 ---- if (OidIsValid(typTup->typarray)) AlterTypeOwnerInternal(typTup->typarray, newOwnerId, false); } + + /* Call AFTER ALTER TYPE triggers */ + if (CommandFiresAfterTriggers(cmd)) + ExecAfterCommandTriggers(cmd); } /* Clean up */ *************** *** 3336,3342 **** AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId, * Execute ALTER TYPE SET SCHEMA */ void ! AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype) { TypeName *typename; Oid typeOid; --- 3520,3527 ---- * Execute ALTER TYPE SET SCHEMA */ void ! AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype, ! CommandContext cmd) { TypeName *typename; Oid typeOid; *************** *** 3356,3366 **** AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype) /* get schema OID and check its permissions */ nspOid = LookupCreationNamespace(newschema); ! AlterTypeNamespace_oid(typeOid, nspOid); } Oid ! AlterTypeNamespace_oid(Oid typeOid, Oid nspOid) { Oid elemOid; --- 3541,3551 ---- /* get schema OID and check its permissions */ nspOid = LookupCreationNamespace(newschema); ! AlterTypeNamespace_oid(typeOid, nspOid, cmd); } Oid ! AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, CommandContext cmd) { Oid elemOid; *************** *** 3380,3386 **** AlterTypeNamespace_oid(Oid typeOid, Oid nspOid) format_type_be(elemOid)))); /* and do the work */ ! return AlterTypeNamespaceInternal(typeOid, nspOid, false, true); } /* --- 3565,3571 ---- format_type_be(elemOid)))); /* and do the work */ ! return AlterTypeNamespaceInternal(typeOid, nspOid, false, true, cmd); } /* *************** *** 3401,3407 **** AlterTypeNamespace_oid(Oid typeOid, Oid nspOid) Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, ! bool errorOnTableType) { Relation rel; HeapTuple tup; --- 3586,3593 ---- Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, ! bool errorOnTableType, ! CommandContext cmd) { Relation rel; HeapTuple tup; *************** *** 3447,3452 **** AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, --- 3633,3648 ---- format_type_be(typeOid)), errhint("Use ALTER TABLE instead."))); + /* Call BEFORE ALTER TYPE triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = typeOid; + cmd->objectname = pstrdup(NameStr(typform->typname)); + cmd->schemaname = get_namespace_name(oldNspOid); + + ExecBeforeCommandTriggers(cmd); + } + /* OK, modify the pg_type row */ /* tup is a copy, so we can scribble directly on it */ *************** *** 3504,3510 **** AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, /* Recursively alter the associated array type, if any */ if (OidIsValid(arrayOid)) ! AlterTypeNamespaceInternal(arrayOid, nspOid, true, true); return oldNspOid; } --- 3700,3711 ---- /* Recursively alter the associated array type, if any */ if (OidIsValid(arrayOid)) ! AlterTypeNamespaceInternal(arrayOid, nspOid, true, true, NULL); + if (CommandFiresAfterTriggers(cmd)) + { + cmd->schemaname = get_namespace_name(nspOid); + ExecAfterCommandTriggers(cmd); + } return oldNspOid; } *** a/src/backend/commands/vacuum.c --- b/src/backend/commands/vacuum.c *************** *** 30,35 **** --- 30,36 ---- #include "catalog/namespace.h" #include "catalog/pg_database.h" #include "catalog/pg_namespace.h" + #include "commands/cmdtrigger.h" #include "commands/cluster.h" #include "commands/vacuum.h" #include "miscadmin.h" *************** *** 122,127 **** vacuum(VacuumStmt *vacstmt, Oid relid, bool do_toast, --- 123,147 ---- else in_outer_xact = IsInTransactionChain(isTopLevel); + /* Call BEFORE VACUUM command triggers */ + if (!IsAutoVacuumWorkerProcess()) + { + CommandContextData cmd; + InitCommandContext(&cmd, (Node *)vacstmt); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = relid; + + if (vacstmt->relation != NULL) + { + cmd.objectname = vacstmt->relation->relname; + cmd.schemaname = vacstmt->relation->schemaname; + } + ExecBeforeCommandTriggers(&cmd); + } + } + /* * Send info about dead objects to the statistics collector, unless we are * in autovacuum --- autovacuum.c does this for itself. *************** *** 1054,1060 **** vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound) /* VACUUM FULL is now a variant of CLUSTER; see cluster.c */ cluster_rel(relid, InvalidOid, false, (vacstmt->options & VACOPT_VERBOSE) != 0, ! vacstmt->freeze_min_age, vacstmt->freeze_table_age); } else lazy_vacuum_rel(onerel, vacstmt, vac_strategy); --- 1074,1080 ---- /* VACUUM FULL is now a variant of CLUSTER; see cluster.c */ cluster_rel(relid, InvalidOid, false, (vacstmt->options & VACOPT_VERBOSE) != 0, ! vacstmt->freeze_min_age, vacstmt->freeze_table_age, NULL); } else lazy_vacuum_rel(onerel, vacstmt, vac_strategy); *** a/src/backend/commands/view.c --- b/src/backend/commands/view.c *************** *** 28,33 **** --- 28,34 ---- #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteManip.h" #include "rewrite/rewriteSupport.h" + #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" *************** *** 99,105 **** isViewOnTempTable_walker(Node *node, void *context) */ static Oid DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace, ! List *options) { Oid viewOid; LOCKMODE lockmode; --- 100,106 ---- */ static Oid DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace, ! List *options, CommandContext cmd) { Oid viewOid; LOCKMODE lockmode; *************** *** 195,200 **** DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace, --- 196,211 ---- */ Assert(relation->relpersistence == rel->rd_rel->relpersistence); + /* Call BEFORE CREATE VIEW triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = viewOid; + cmd->objectname = RelationGetRelationName(rel); + cmd->schemaname = get_namespace_name(RelationGetNamespace(rel)); + + ExecBeforeCommandTriggers(cmd); + } + /* * Create a tuple descriptor to compare against the existing view, and * verify that the old column list is an initial prefix of the new *************** *** 269,275 **** DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace, * existing view, so we don't need more code to complain if "replace" * is false). */ ! relid = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid); Assert(relid != InvalidOid); return relid; } --- 280,286 ---- * existing view, so we don't need more code to complain if "replace" * is false). */ ! relid = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, cmd); Assert(relid != InvalidOid); return relid; } *************** *** 343,349 **** DefineViewRules(Oid viewOid, Query *viewParse, bool replace) CMD_SELECT, true, replace, ! list_make1(viewParse)); /* * Someday: automatic ON INSERT, etc --- 354,361 ---- CMD_SELECT, true, replace, ! list_make1(viewParse), ! NULL); /* * Someday: automatic ON INSERT, etc *************** *** 426,431 **** DefineView(ViewStmt *stmt, const char *queryString) --- 438,444 ---- Query *viewParse; Oid viewOid; RangeVar *view; + CommandContextData cmd; /* * Run parse analysis to convert the raw parse tree to a Query. Note this *************** *** 515,527 **** DefineView(ViewStmt *stmt, const char *queryString) } /* * Create the view relation * * NOTE: if it already exists and replace is false, the xact will be * aborted. */ viewOid = DefineVirtualRelation(view, viewParse->targetList, ! stmt->replace, stmt->options); /* * The relation we have just created is not visible to any other commands --- 528,548 ---- } /* + * Prepare BEFORE CREATE VIEW triggers + */ + InitCommandContext(&cmd, (Node *)stmt); + + /* * Create the view relation * * NOTE: if it already exists and replace is false, the xact will be * aborted. */ viewOid = DefineVirtualRelation(view, viewParse->targetList, ! stmt->replace, stmt->options, &cmd); ! ! if (!OidIsValid(viewOid)) ! return; /* * The relation we have just created is not visible to any other commands *************** *** 540,543 **** DefineView(ViewStmt *stmt, const char *queryString) --- 561,571 ---- * Now create the rules associated with the view. */ DefineViewRules(viewOid, viewParse, stmt->replace); + + /* Call AFTER CREATE VIEW triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = viewOid; + ExecAfterCommandTriggers(&cmd); + } } *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 3455,3460 **** _copyCreateTrigStmt(const CreateTrigStmt *from) --- 3455,3483 ---- return newnode; } + static CreateCmdTrigStmt * + _copyCreateCmdTrigStmt(const CreateCmdTrigStmt *from) + { + CreateCmdTrigStmt *newnode = makeNode(CreateCmdTrigStmt); + + COPY_STRING_FIELD(trigname); + COPY_SCALAR_FIELD(timing); + COPY_NODE_FIELD(funcname); + + return newnode; + } + + static AlterCmdTrigStmt * + _copyAlterCmdTrigStmt(const AlterCmdTrigStmt *from) + { + AlterCmdTrigStmt *newnode = makeNode(AlterCmdTrigStmt); + + COPY_STRING_FIELD(trigname); + COPY_STRING_FIELD(tgenabled); + + return newnode; + } + static CreatePLangStmt * _copyCreatePLangStmt(const CreatePLangStmt *from) { *************** *** 3683,3689 **** _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from) /* * Perform a deep copy of the specified list, using copyObject(). The * list MUST be of type T_List; T_IntList and T_OidList nodes don't ! * need deep copies, so they should be copied via list_copy() */ #define COPY_NODE_CELL(new, old) \ (new) = (ListCell *) palloc(sizeof(ListCell)); \ --- 3706,3712 ---- /* * Perform a deep copy of the specified list, using copyObject(). The * list MUST be of type T_List; T_IntList and T_OidList nodes don't ! * need deep copies, so they should be copied via list_copy(const ) */ #define COPY_NODE_CELL(new, old) \ (new) = (ListCell *) palloc(sizeof(ListCell)); \ *************** *** 4306,4311 **** copyObject(const void *from) --- 4329,4340 ---- case T_CreateTrigStmt: retval = _copyCreateTrigStmt(from); break; + case T_CreateCmdTrigStmt: + retval = _copyCreateCmdTrigStmt(from); + break; + case T_AlterCmdTrigStmt: + retval = _copyAlterCmdTrigStmt(from); + break; case T_CreatePLangStmt: retval = _copyCreatePLangStmt(from); break; *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** *** 1787,1792 **** _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b) --- 1787,1811 ---- } static bool + _equalCreateCmdTrigStmt(const CreateCmdTrigStmt *a, const CreateCmdTrigStmt *b) + { + COMPARE_STRING_FIELD(trigname); + COMPARE_SCALAR_FIELD(timing); + COMPARE_NODE_FIELD(funcname); + + return true; + } + + static bool + _equalAlterCmdTrigStmt(const AlterCmdTrigStmt *a, const AlterCmdTrigStmt *b) + { + COMPARE_STRING_FIELD(trigname); + COMPARE_STRING_FIELD(tgenabled); + + return true; + } + + static bool _equalCreatePLangStmt(const CreatePLangStmt *a, const CreatePLangStmt *b) { COMPARE_SCALAR_FIELD(replace); *************** *** 2864,2869 **** equal(const void *a, const void *b) --- 2883,2894 ---- case T_CreateTrigStmt: retval = _equalCreateTrigStmt(a, b); break; + case T_CreateCmdTrigStmt: + retval = _equalCreateCmdTrigStmt(a, b); + break; + case T_AlterCmdTrigStmt: + retval = _equalAlterCmdTrigStmt(a, b); + break; case T_CreatePLangStmt: retval = _equalCreatePLangStmt(a, b); break; *** a/src/backend/nodes/readfuncs.c --- b/src/backend/nodes/readfuncs.c *************** *** 254,259 **** _readDeclareCursorStmt(void) --- 254,275 ---- } /* + * _readCreateCmdTrigStmt + */ + static CreateCmdTrigStmt * + _readCreateCmdTrigStmt(void) + { + READ_LOCALS(CreateCmdTrigStmt); + + READ_NODE_FIELD(command); + READ_STRING_FIELD(trigname); + READ_CHAR_FIELD(timing); + READ_NODE_FIELD(funcname); + + READ_DONE(); + } + + /* * _readSortGroupClause */ static SortGroupClause * *************** *** 1254,1259 **** parseNodeString(void) --- 1270,1277 ---- if (MATCH("QUERY", 5)) return_value = _readQuery(); + else if (MATCH("CREATECMDTRIGSTMT", 17)) + return_value = _readCreateCmdTrigStmt(); else if (MATCH("SORTGROUPCLAUSE", 15)) return_value = _readSortGroupClause(); else if (MATCH("WINDOWCLAUSE", 12)) *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 53,58 **** --- 53,59 ---- #include "catalog/index.h" #include "catalog/namespace.h" + #include "catalog/pg_cmdtrigger.h" #include "catalog/pg_trigger.h" #include "commands/defrem.h" #include "nodes/makefuncs.h" *************** *** 194,199 **** static void processCASbits(int cas_bits, int location, const char *constrType, --- 195,201 ---- } %type stmt schema_stmt + AlterCmdTrigStmt AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt *************** *** 207,218 **** static void processCASbits(int cas_bits, int location, const char *constrType, CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt ! CreateAssertStmt CreateTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt ! DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt ! DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt --- 209,220 ---- CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt ! CreateAssertStmt CreateTrigStmt CreateCmdTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt ! DropAssertStmt DropTrigStmt DropCmdTrigStmt DropRuleStmt DropCastStmt ! DropRoleStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt *************** *** 263,272 **** static void processCASbits(int cas_bits, int location, const char *constrType, %type OptSchemaEltList %type TriggerForSpec TriggerForType ! %type TriggerActionTime %type TriggerEvents TriggerOneEvent %type TriggerFuncArg %type TriggerWhen %type copy_file_name database_name access_method_clause access_method attr_name --- 265,275 ---- %type OptSchemaEltList %type TriggerForSpec TriggerForType ! %type TriggerActionTime CmdTriggerActionTime %type TriggerEvents TriggerOneEvent %type TriggerFuncArg %type TriggerWhen + %type trigger_command enable_trigger %type copy_file_name database_name access_method_clause access_method attr_name *************** *** 494,500 **** static void processCASbits(int cas_bits, int location, const char *constrType, CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE ! CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CROSS CSV CURRENT_P --- 497,503 ---- CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE ! CLUSTER COALESCE COLLATE COLLATION COLUMN COMMAND COMMENT COMMENTS COMMIT COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CROSS CSV CURRENT_P *************** *** 674,680 **** stmtmulti: stmtmulti ';' stmt ; stmt : ! AlterDatabaseStmt | AlterDatabaseSetStmt | AlterDefaultPrivilegesStmt | AlterDomainStmt --- 677,684 ---- ; stmt : ! AlterCmdTrigStmt ! | AlterDatabaseStmt | AlterDatabaseSetStmt | AlterDefaultPrivilegesStmt | AlterDomainStmt *************** *** 725,730 **** stmt : --- 729,735 ---- | CreateStmt | CreateTableSpaceStmt | CreateTrigStmt + | CreateCmdTrigStmt | CreateRoleStmt | CreateUserStmt | CreateUserMappingStmt *************** *** 748,753 **** stmt : --- 753,759 ---- | DropStmt | DropTableSpaceStmt | DropTrigStmt + | DropCmdTrigStmt | DropRoleStmt | DropUserStmt | DropUserMappingStmt *************** *** 4255,4260 **** DropTrigStmt: --- 4261,4433 ---- /***************************************************************************** * * QUERIES : + * CREATE COMMAND TRIGGER ... BEFORE|AFTER COMMAND ... + * DROP COMMAND TRIGGER ... BEFORE|AFTER ... + * + *****************************************************************************/ + + CreateCmdTrigStmt: + CREATE COMMAND TRIGGER name CmdTriggerActionTime trigger_command + EXECUTE PROCEDURE func_name '(' ')' + { + CreateCmdTrigStmt *n = makeNode(CreateCmdTrigStmt); + n->trigname = $4; + n->timing = $5; + n->command = $6; + n->funcname = $9; + $$ = (Node *)n; + } + | CREATE COMMAND TRIGGER name CmdTriggerActionTime ANY COMMAND + EXECUTE PROCEDURE func_name '(' ')' + { + CreateCmdTrigStmt *n = makeNode(CreateCmdTrigStmt); + n->trigname = $4; + n->timing = $5; + n->command = "ANY"; + n->funcname = $10; + $$ = (Node *)n; + } + ; + + CmdTriggerActionTime: + BEFORE { $$ = CMD_TRIGGER_FIRED_BEFORE; } + | AFTER { $$ = CMD_TRIGGER_FIRED_AFTER; } + ; + + /* + * that will get matched against what CreateCommandTag returns + * + * we don't support Command Triggers on every possible command that PostgreSQL + * supports, this list should match with the implementation. + */ + trigger_command: + CREATE SCHEMA { $$ = "CREATE SCHEMA"; } + | CREATE EXTENSION { $$ = "CREATE EXTENSION"; } + | CREATE LANGUAGE { $$ = "CREATE LANGUAGE"; } + | CREATE FUNCTION { $$ = "CREATE FUNCTION"; } + | CREATE TABLE { $$ = "CREATE TABLE"; } + | CREATE TABLE AS { $$ = "CREATE TABLE AS"; } + | SELECT INTO { $$ = "SELECT INTO"; } + | CREATE SERVER { $$ = "CREATE SERVER"; } + | CREATE FOREIGN TABLE { $$ = "CREATE FOREIGN TABLE"; } + | CREATE FOREIGN DATA_P WRAPPER { $$ = "CREATE FOREIGN DATA WRAPPER"; } + | CREATE USER MAPPING { $$ = "CREATE USER MAPPING"; } + | CREATE INDEX { $$ = "CREATE INDEX"; } + | CREATE SEQUENCE { $$ = "CREATE SEQUENCE"; } + | CREATE VIEW { $$ = "CREATE VIEW"; } + | CREATE RULE { $$ = "CREATE RULE"; } + | CREATE AGGREGATE { $$ = "CREATE AGGREGATE"; } + | CREATE OPERATOR { $$ = "CREATE OPERATOR"; } + | CREATE COLLATION { $$ = "CREATE COLLATION"; } + | CREATE TEXT_P SEARCH PARSER { $$ = "CREATE TEXT SEARCH PARSER"; } + | CREATE TEXT_P SEARCH DICTIONARY { $$ = "CREATE TEXT SEARCH DICTIONARY"; } + | CREATE TEXT_P SEARCH TEMPLATE { $$ = "CREATE TEXT SEARCH TEMPLATE"; } + | CREATE TEXT_P SEARCH CONFIGURATION { $$ = "CREATE TEXT SEARCH CONFIGURATION"; } + | CREATE TYPE_P { $$ = "CREATE TYPE"; } + | CREATE DOMAIN_P { $$ = "CREATE DOMAIN"; } + | CREATE TRIGGER { $$ = "CREATE TRIGGER"; } + | CREATE CONVERSION_P { $$ = "CREATE CONVERSION"; } + | CREATE CAST { $$ = "CREATE CAST"; } + | CREATE OPERATOR CLASS { $$ = "CREATE OPERATOR CLASS"; } + | CREATE OPERATOR FAMILY { $$ = "CREATE OPERATOR FAMILY"; } + | ALTER SCHEMA { $$ = "ALTER SCHEMA"; } + | ALTER EXTENSION { $$ = "ALTER EXTENSION"; } + | ALTER LANGUAGE { $$ = "ALTER LANGUAGE"; } + | ALTER FUNCTION { $$ = "ALTER FUNCTION"; } + | ALTER TABLE { $$ = "ALTER TABLE"; } + | ALTER SERVER { $$ = "ALTER SERVER"; } + | ALTER FOREIGN TABLE { $$ = "ALTER FOREIGN TABLE"; } + | ALTER FOREIGN DATA_P WRAPPER { $$ = "ALTER FOREIGN DATA WRAPPER"; } + | ALTER USER MAPPING { $$ = "ALTER USER MAPPING"; } + | ALTER SEQUENCE { $$ = "ALTER SEQUENCE"; } + | ALTER VIEW { $$ = "ALTER VIEW"; } + | ALTER AGGREGATE { $$ = "ALTER AGGREGATE"; } + | ALTER OPERATOR { $$ = "ALTER OPERATOR"; } + | ALTER OPERATOR CLASS { $$ = "ALTER OPERATOR CLASS"; } + | ALTER OPERATOR FAMILY { $$ = "ALTER OPERATOR FAMILY"; } + | ALTER COLLATION { $$ = "ALTER COLLATION"; } + | ALTER TEXT_P SEARCH PARSER { $$ = "ALTER TEXT SEARCH PARSER"; } + | ALTER TEXT_P SEARCH DICTIONARY { $$ = "ALTER TEXT SEARCH DICTIONARY"; } + | ALTER TEXT_P SEARCH TEMPLATE { $$ = "ALTER TEXT SEARCH TEMPLATE"; } + | ALTER TEXT_P SEARCH CONFIGURATION { $$ = "ALTER TEXT SEARCH CONFIGURATION"; } + | ALTER TYPE_P { $$ = "ALTER TYPE"; } + | ALTER DOMAIN_P { $$ = "ALTER DOMAIN"; } + | ALTER TRIGGER { $$ = "ALTER TRIGGER"; } + | ALTER CONVERSION_P { $$ = "ALTER CONVERSION"; } + | DROP TABLE { $$ = "DROP TABLE"; } + | DROP SEQUENCE { $$ = "DROP SEQUENCE"; } + | DROP VIEW { $$ = "DROP VIEW"; } + | DROP INDEX { $$ = "DROP INDEX"; } + | DROP TYPE_P { $$ = "DROP TYPE"; } + | DROP DOMAIN_P { $$ = "DROP DOMAIN"; } + | DROP COLLATION { $$ = "DROP COLLATION"; } + | DROP CONVERSION_P { $$ = "DROP CONVERSION"; } + | DROP SCHEMA { $$ = "DROP SCHEMA"; } + | DROP EXTENSION { $$ = "DROP EXTENSION"; } + | DROP TEXT_P SEARCH PARSER { $$ = "DROP TEXT SEARCH PARSER"; } + | DROP TEXT_P SEARCH DICTIONARY { $$ = "DROP TEXT SEARCH DICTIONARY"; } + | DROP TEXT_P SEARCH TEMPLATE { $$ = "DROP TEXT SEARCH TEMPLATE"; } + | DROP TEXT_P SEARCH CONFIGURATION { $$ = "DROP TEXT SEARCH CONFIGURATION"; } + | DROP LANGUAGE { $$ = "DROP LANGUAGE"; } + | DROP SERVER { $$ = "DROP SERVER"; } + | DROP FOREIGN TABLE { $$ = "DROP FOREIGN TABLE"; } + | DROP FOREIGN DATA_P WRAPPER { $$ = "DROP FOREIGN DATA WRAPPER"; } + | DROP USER MAPPING { $$ = "DROP USER MAPPING"; } + | DROP TRIGGER { $$ = "DROP TRIGGER"; } + | DROP OPERATOR CLASS { $$ = "DROP OPERATOR CLASS"; } + | DROP OPERATOR FAMILY { $$ = "DROP OPERATOR FAMILY"; } + | DROP FUNCTION { $$ = "DROP FUNCTION"; } + | DROP AGGREGATE { $$ = "DROP AGGREGATE"; } + | DROP OPERATOR { $$ = "DROP OPERATOR"; } + | DROP CAST { $$ = "DROP CAST"; } + | DROP RULE { $$ = "DROP RULE"; } + | REINDEX { $$ = "REINDEX"; } + | VACUUM { $$ = "VACUUM"; } + | CLUSTER { $$ = "CLUSTER"; } + | LOAD { $$ = "LOAD"; } + ; + + DropCmdTrigStmt: + DROP COMMAND TRIGGER name opt_drop_behavior + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_CMDTRIGGER; + n->objects = list_make1(list_make1(makeString($4))); + n->behavior = $5; + n->missing_ok = false; + $$ = (Node *) n; + } + | DROP COMMAND TRIGGER IF_P EXISTS name opt_drop_behavior + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_CMDTRIGGER; + n->objects = list_make1(list_make1(makeString($6))); + n->behavior = $7; + n->missing_ok = true; + $$ = (Node *) n; + } + ; + + AlterCmdTrigStmt: + ALTER COMMAND TRIGGER name SET enable_trigger + { + AlterCmdTrigStmt *n = makeNode(AlterCmdTrigStmt); + n->trigname = $4; + n->tgenabled = $6; + $$ = (Node *) n; + } + ; + + enable_trigger: + ENABLE_P { $$ = "O"; } + | ENABLE_P REPLICA { $$ = "R"; } + | ENABLE_P ALWAYS { $$ = "A"; } + | DISABLE_P { $$ = "D"; } + ; + + /***************************************************************************** + * + * QUERIES : * CREATE ASSERTION ... * DROP ASSERTION ... * *************** *** 6768,6773 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name --- 6941,6954 ---- n->missing_ok = false; $$ = (Node *)n; } + | ALTER COMMAND TRIGGER name RENAME TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_CMDTRIGGER; + n->object = list_make1(makeString($4)); + n->newname = $7; + $$ = (Node *)n; + } | ALTER ROLE RoleId RENAME TO RoleId { RenameStmt *n = makeNode(RenameStmt); *** a/src/backend/rewrite/rewriteDefine.c --- b/src/backend/rewrite/rewriteDefine.c *************** *** 195,204 **** DefineRule(RuleStmt *stmt, const char *queryString) --- 195,208 ---- List *actions; Node *whereClause; Oid relId; + CommandContextData cmd; /* Parse analysis. */ transformRuleStmt(stmt, queryString, &actions, &whereClause); + /* Prepare command context */ + InitCommandContext(&cmd, (Node *)stmt); + /* * Find and lock the relation. Lock level should match * DefineQueryRewrite. *************** *** 212,218 **** DefineRule(RuleStmt *stmt, const char *queryString) stmt->event, stmt->instead, stmt->replace, ! actions); } --- 216,223 ---- stmt->event, stmt->instead, stmt->replace, ! actions, ! &cmd); } *************** *** 230,237 **** DefineQueryRewrite(char *rulename, CmdType event_type, bool is_instead, bool replace, ! List *action) { Relation event_relation; int event_attno; ListCell *l; --- 235,244 ---- CmdType event_type, bool is_instead, bool replace, ! List *action, ! CommandContext cmd) { + Oid ruleOid; Relation event_relation; int event_attno; ListCell *l; *************** *** 479,484 **** DefineQueryRewrite(char *rulename, --- 486,501 ---- } } + /* Call BEFORE CREATE RULE triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = rulename; + cmd->schemaname = NULL; /* rules are not schema qualified */ + + ExecBeforeCommandTriggers(cmd); + } + /* * This rule is allowed - prepare to install it. */ *************** *** 487,500 **** DefineQueryRewrite(char *rulename, /* discard rule if it's null action and not INSTEAD; it's a no-op */ if (action != NIL || is_instead) { ! InsertRule(rulename, ! event_type, ! event_relid, ! event_attno, ! is_instead, ! event_qual, ! action, ! replace); /* * Set pg_class 'relhasrules' field TRUE for event relation. If --- 504,517 ---- /* discard rule if it's null action and not INSTEAD; it's a no-op */ if (action != NIL || is_instead) { ! ruleOid = InsertRule(rulename, ! event_type, ! event_relid, ! event_attno, ! is_instead, ! event_qual, ! action, ! replace); /* * Set pg_class 'relhasrules' field TRUE for event relation. If *************** *** 519,524 **** DefineQueryRewrite(char *rulename, --- 536,548 ---- /* Close rel, but keep lock till commit... */ heap_close(event_relation, NoLock); + + /* Call AFTER CREATE RULE triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = ruleOid; + ExecAfterCommandTriggers(cmd); + } } /* *** a/src/backend/tcop/dest.c --- b/src/backend/tcop/dest.c *************** *** 118,124 **** CreateDestReceiver(CommandDest dest) return CreateTuplestoreDestReceiver(); case DestIntoRel: ! return CreateIntoRelDestReceiver(NULL); case DestCopyOut: return CreateCopyDestReceiver(); --- 118,124 ---- return CreateTuplestoreDestReceiver(); case DestIntoRel: ! return CreateIntoRelDestReceiver(NULL, NULL); case DestCopyOut: return CreateCopyDestReceiver(); *** a/src/backend/tcop/utility.c --- b/src/backend/tcop/utility.c *************** *** 25,30 **** --- 25,31 ---- #include "commands/alter.h" #include "commands/async.h" #include "commands/cluster.h" + #include "commands/cmdtrigger.h" #include "commands/comment.h" #include "commands/collationcmds.h" #include "commands/conversioncmds.h" *************** *** 59,67 **** #include "tcop/utility.h" #include "utils/acl.h" #include "utils/guc.h" #include "utils/syscache.h" - /* Hook for plugins to get control in ProcessUtility() */ ProcessUtility_hook_type ProcessUtility_hook = NULL; --- 60,68 ---- #include "tcop/utility.h" #include "utils/acl.h" #include "utils/guc.h" + #include "utils/lsyscache.h" #include "utils/syscache.h" /* Hook for plugins to get control in ProcessUtility() */ ProcessUtility_hook_type ProcessUtility_hook = NULL; *************** *** 183,188 **** check_xact_readonly(Node *parsetree) --- 184,191 ---- case T_CommentStmt: case T_DefineStmt: case T_CreateCastStmt: + case T_CreateCmdTrigStmt: + case T_AlterCmdTrigStmt: case T_CreateConversionStmt: case T_CreatedbStmt: case T_CreateDomainStmt: *************** *** 509,519 **** standard_ProcessUtility(Node *parsetree, { List *stmts; ListCell *l; ! Oid relOid; /* Run parse analysis ... */ ! stmts = transformCreateStmt((CreateStmt *) parsetree, ! queryString); /* ... and do it */ foreach(l, stmts) --- 512,526 ---- { List *stmts; ListCell *l; ! Oid relOid = InvalidOid; ! CreateStmt *stmt = (CreateStmt *) parsetree; ! CommandContextData cmd; ! ! /* Prepare BEFORE CREATE TABLE triggers */ ! InitCommandContext(&cmd, parsetree); /* Run parse analysis ... */ ! stmts = transformCreateStmt(stmt, queryString); /* ... and do it */ foreach(l, stmts) *************** *** 528,534 **** standard_ProcessUtility(Node *parsetree, /* Create the table itself */ relOid = DefineRelation((CreateStmt *) stmt, RELKIND_RELATION, ! InvalidOid); /* * Let AlterTableCreateToastTable decide if this one --- 535,542 ---- /* Create the table itself */ relOid = DefineRelation((CreateStmt *) stmt, RELKIND_RELATION, ! InvalidOid, ! &cmd); /* * Let AlterTableCreateToastTable decide if this one *************** *** 552,558 **** standard_ProcessUtility(Node *parsetree, /* Create the table itself */ relOid = DefineRelation((CreateStmt *) stmt, RELKIND_FOREIGN_TABLE, ! InvalidOid); CreateForeignTable((CreateForeignTableStmt *) stmt, relOid); } --- 560,567 ---- /* Create the table itself */ relOid = DefineRelation((CreateStmt *) stmt, RELKIND_FOREIGN_TABLE, ! InvalidOid, ! &cmd); CreateForeignTable((CreateForeignTableStmt *) stmt, relOid); } *************** *** 571,576 **** standard_ProcessUtility(Node *parsetree, --- 580,592 ---- if (lnext(l) != NULL) CommandCounterIncrement(); } + + /* Call AFTER CREATE TABLE triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = relOid; + ExecAfterCommandTriggers(&cmd); + } } break; *************** *** 756,761 **** standard_ProcessUtility(Node *parsetree, --- 772,781 ---- case T_AlterDomainStmt: { AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree; + CommandContextData cmd; + + /* Prepare BEFORE ALTER DOMAIN triggers */ + InitCommandContext(&cmd, parsetree); /* * Some or all of these functions are recursive to cover *************** *** 770,798 **** standard_ProcessUtility(Node *parsetree, * requested, for descendants */ AlterDomainDefault(stmt->typeName, ! stmt->def); break; case 'N': /* ALTER DOMAIN DROP NOT NULL */ AlterDomainNotNull(stmt->typeName, ! false); break; case 'O': /* ALTER DOMAIN SET NOT NULL */ AlterDomainNotNull(stmt->typeName, ! true); break; case 'C': /* ADD CONSTRAINT */ AlterDomainAddConstraint(stmt->typeName, ! stmt->def); break; case 'X': /* DROP CONSTRAINT */ AlterDomainDropConstraint(stmt->typeName, stmt->name, stmt->behavior, ! stmt->missing_ok); break; case 'V': /* VALIDATE CONSTRAINT */ AlterDomainValidateConstraint(stmt->typeName, ! stmt->name); break; default: /* oops */ elog(ERROR, "unrecognized alter domain type: %d", --- 790,819 ---- * requested, for descendants */ AlterDomainDefault(stmt->typeName, ! stmt->def, &cmd); break; case 'N': /* ALTER DOMAIN DROP NOT NULL */ AlterDomainNotNull(stmt->typeName, ! false, &cmd); break; case 'O': /* ALTER DOMAIN SET NOT NULL */ AlterDomainNotNull(stmt->typeName, ! true, &cmd); break; case 'C': /* ADD CONSTRAINT */ AlterDomainAddConstraint(stmt->typeName, ! stmt->def, &cmd); break; case 'X': /* DROP CONSTRAINT */ AlterDomainDropConstraint(stmt->typeName, stmt->name, stmt->behavior, ! stmt->missing_ok, ! &cmd); break; case 'V': /* VALIDATE CONSTRAINT */ AlterDomainValidateConstraint(stmt->typeName, ! stmt->name, &cmd); break; default: /* oops */ elog(ERROR, "unrecognized alter domain type: %d", *************** *** 820,859 **** standard_ProcessUtility(Node *parsetree, case T_DefineStmt: { DefineStmt *stmt = (DefineStmt *) parsetree; switch (stmt->kind) { case OBJECT_AGGREGATE: DefineAggregate(stmt->defnames, stmt->args, ! stmt->oldstyle, stmt->definition); break; case OBJECT_OPERATOR: Assert(stmt->args == NIL); ! DefineOperator(stmt->defnames, stmt->definition); break; case OBJECT_TYPE: Assert(stmt->args == NIL); ! DefineType(stmt->defnames, stmt->definition); break; case OBJECT_TSPARSER: Assert(stmt->args == NIL); ! DefineTSParser(stmt->defnames, stmt->definition); break; case OBJECT_TSDICTIONARY: Assert(stmt->args == NIL); ! DefineTSDictionary(stmt->defnames, stmt->definition); break; case OBJECT_TSTEMPLATE: Assert(stmt->args == NIL); ! DefineTSTemplate(stmt->defnames, stmt->definition); break; case OBJECT_TSCONFIGURATION: Assert(stmt->args == NIL); ! DefineTSConfiguration(stmt->defnames, stmt->definition); break; case OBJECT_COLLATION: Assert(stmt->args == NIL); ! DefineCollation(stmt->defnames, stmt->definition); break; default: elog(ERROR, "unrecognized define stmt type: %d", --- 841,883 ---- case T_DefineStmt: { DefineStmt *stmt = (DefineStmt *) parsetree; + CommandContextData cmd; + + InitCommandContext(&cmd, parsetree); switch (stmt->kind) { case OBJECT_AGGREGATE: DefineAggregate(stmt->defnames, stmt->args, ! stmt->oldstyle, stmt->definition, &cmd); break; case OBJECT_OPERATOR: Assert(stmt->args == NIL); ! DefineOperator(stmt->defnames, stmt->definition, &cmd); break; case OBJECT_TYPE: Assert(stmt->args == NIL); ! DefineType(stmt->defnames, stmt->definition, &cmd); break; case OBJECT_TSPARSER: Assert(stmt->args == NIL); ! DefineTSParser(stmt->defnames, stmt->definition, &cmd); break; case OBJECT_TSDICTIONARY: Assert(stmt->args == NIL); ! DefineTSDictionary(stmt->defnames, stmt->definition, &cmd); break; case OBJECT_TSTEMPLATE: Assert(stmt->args == NIL); ! DefineTSTemplate(stmt->defnames, stmt->definition, &cmd); break; case OBJECT_TSCONFIGURATION: Assert(stmt->args == NIL); ! DefineTSConfiguration(stmt->defnames, stmt->definition, &cmd); break; case OBJECT_COLLATION: Assert(stmt->args == NIL); ! DefineCollation(stmt->defnames, stmt->definition, &cmd); break; default: elog(ERROR, "unrecognized define stmt type: %d", *************** *** 866,873 **** standard_ProcessUtility(Node *parsetree, case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ { CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; ! DefineCompositeType(stmt->typevar, stmt->coldeflist); } break; --- 890,899 ---- case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ { CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; + CommandContextData cmd; ! InitCommandContext(&cmd, parsetree); ! DefineCompositeType(stmt->typevar, stmt->coldeflist, &cmd); } break; *************** *** 905,910 **** standard_ProcessUtility(Node *parsetree, --- 931,939 ---- case T_IndexStmt: /* CREATE INDEX */ { IndexStmt *stmt = (IndexStmt *) parsetree; + CommandContextData cmd; + + InitCommandContext(&cmd, parsetree); if (stmt->concurrent) PreventTransactionChain(isTopLevel, *************** *** 935,941 **** standard_ProcessUtility(Node *parsetree, true, /* check_rights */ false, /* skip_build */ false, /* quiet */ ! stmt->concurrent); /* concurrent */ } break; --- 964,971 ---- true, /* check_rights */ false, /* skip_build */ false, /* quiet */ ! stmt->concurrent, /* concurrent */ ! &cmd); } break; *************** *** 973,979 **** standard_ProcessUtility(Node *parsetree, DropdbStmt *stmt = (DropdbStmt *) parsetree; PreventTransactionChain(isTopLevel, "DROP DATABASE"); ! dropdb(stmt->dbname, stmt->missing_ok); } break; --- 1003,1009 ---- DropdbStmt *stmt = (DropdbStmt *) parsetree; PreventTransactionChain(isTopLevel, "DROP DATABASE"); ! dropdb(stmt); } break; *************** *** 1013,1022 **** standard_ProcessUtility(Node *parsetree, --- 1043,1066 ---- case T_LoadStmt: { LoadStmt *stmt = (LoadStmt *) parsetree; + CommandContextData cmd; + + InitCommandContext(&cmd, parsetree); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectname = stmt->filename; + cmd.schemaname = NULL; + + ExecBeforeCommandTriggers(&cmd); + } closeAllVfds(); /* probably not necessary... */ /* Allowed names are restricted if you're not superuser */ load_file(stmt->filename, !superuser()); + + if (CommandFiresAfterTriggers(&cmd)) + ExecAfterCommandTriggers(&cmd); } break; *************** *** 1065,1070 **** standard_ProcessUtility(Node *parsetree, --- 1109,1122 ---- InvalidOid, InvalidOid, false); break; + case T_CreateCmdTrigStmt: + CreateCmdTrigger((CreateCmdTrigStmt *) parsetree, queryString); + break; + + case T_AlterCmdTrigStmt: + (void) AlterCmdTrigger((AlterCmdTrigStmt *) parsetree); + break; + case T_CreatePLangStmt: CreateProceduralLanguage((CreatePLangStmt *) parsetree); break; *************** *** 1137,1152 **** standard_ProcessUtility(Node *parsetree, case T_ReindexStmt: { ReindexStmt *stmt = (ReindexStmt *) parsetree; /* we choose to allow this during "read only" transactions */ PreventCommandDuringRecovery("REINDEX"); switch (stmt->kind) { case OBJECT_INDEX: ! ReindexIndex(stmt->relation); break; case OBJECT_TABLE: ! ReindexTable(stmt->relation); break; case OBJECT_DATABASE: --- 1189,1207 ---- case T_ReindexStmt: { ReindexStmt *stmt = (ReindexStmt *) parsetree; + CommandContextData cmd; + + InitCommandContext(&cmd, parsetree); /* we choose to allow this during "read only" transactions */ PreventCommandDuringRecovery("REINDEX"); switch (stmt->kind) { case OBJECT_INDEX: ! ReindexIndex(stmt->relation, &cmd); break; case OBJECT_TABLE: ! ReindexTable(stmt->relation, &cmd); break; case OBJECT_DATABASE: *************** *** 1467,1472 **** AlterObjectTypeCommandTag(ObjectType objtype) --- 1522,1530 ---- case OBJECT_TRIGGER: tag = "ALTER TRIGGER"; break; + case OBJECT_CMDTRIGGER: + tag = "ALTER COMMAND TRIGGER"; + break; case OBJECT_TSCONFIGURATION: tag = "ALTER TEXT SEARCH CONFIGURATION"; break; *************** *** 1736,1741 **** CreateCommandTag(Node *parsetree) --- 1794,1802 ---- case OBJECT_TRIGGER: tag = "DROP TRIGGER"; break; + case OBJECT_CMDTRIGGER: + tag = "DROP COMMAND TRIGGER"; + break; case OBJECT_RULE: tag = "DROP RULE"; break; *************** *** 1989,1994 **** CreateCommandTag(Node *parsetree) --- 2050,2063 ---- tag = "CREATE TRIGGER"; break; + case T_CreateCmdTrigStmt: + tag = "CREATE COMMAND TRIGGER"; + break; + + case T_AlterCmdTrigStmt: + tag = "ALTER COMMAND TRIGGER"; + break; + case T_CreatePLangStmt: tag = "CREATE LANGUAGE"; break; *************** *** 2186,2191 **** CreateCommandTag(Node *parsetree) --- 2255,2267 ---- break; } + /* + * Useful to raise WARNINGs for any DDL command not yet supported. + * + elog(WARNING, "Command Tag: %s", tag); + elog(WARNING, "Note to String: %s", nodeToString(parsetree)); + */ + return tag; } *************** *** 2484,2489 **** GetCommandLogLevel(Node *parsetree) --- 2560,2573 ---- lev = LOGSTMT_DDL; break; + case T_CreateCmdTrigStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterCmdTrigStmt: + lev = LOGSTMT_DDL; + break; + case T_CreatePLangStmt: lev = LOGSTMT_DDL; break; *** a/src/backend/utils/adt/format_type.c --- b/src/backend/utils/adt/format_type.c *************** *** 28,34 **** #define MAX_INT32_LEN 11 static char *format_type_internal(Oid type_oid, int32 typemod, ! bool typemod_given, bool allow_invalid); static char *printTypmod(const char *typname, int32 typmod, Oid typmodout); static char * psnprintf(size_t len, const char *fmt,...) --- 28,35 ---- #define MAX_INT32_LEN 11 static char *format_type_internal(Oid type_oid, int32 typemod, ! bool typemod_given, bool allow_invalid, ! bool qualify); static char *printTypmod(const char *typname, int32 typmod, Oid typmodout); static char * psnprintf(size_t len, const char *fmt,...) *************** *** 76,86 **** format_type(PG_FUNCTION_ARGS) type_oid = PG_GETARG_OID(0); if (PG_ARGISNULL(1)) ! result = format_type_internal(type_oid, -1, false, true); else { typemod = PG_GETARG_INT32(1); ! result = format_type_internal(type_oid, typemod, true, true); } PG_RETURN_TEXT_P(cstring_to_text(result)); --- 77,87 ---- type_oid = PG_GETARG_OID(0); if (PG_ARGISNULL(1)) ! result = format_type_internal(type_oid, -1, false, true, true); else { typemod = PG_GETARG_INT32(1); ! result = format_type_internal(type_oid, typemod, true, true, true); } PG_RETURN_TEXT_P(cstring_to_text(result)); *************** *** 95,101 **** format_type(PG_FUNCTION_ARGS) char * format_type_be(Oid type_oid) { ! return format_type_internal(type_oid, -1, false, false); } /* --- 96,113 ---- char * format_type_be(Oid type_oid) { ! return format_type_internal(type_oid, -1, false, false, true); ! } ! ! /* ! * Allow formating a type name without namespace, useful for command context ! * where we probide object name and namespace separately and still want nice ! * formating of type names. ! */ ! char * ! format_type_be_without_namespace(Oid type_oid) ! { ! return format_type_internal(type_oid, -1, false, false, false); } /* *************** *** 104,117 **** format_type_be(Oid type_oid) char * format_type_with_typemod(Oid type_oid, int32 typemod) { ! return format_type_internal(type_oid, typemod, true, false); } static char * format_type_internal(Oid type_oid, int32 typemod, ! bool typemod_given, bool allow_invalid) { bool with_typemod = typemod_given && (typemod >= 0); HeapTuple tuple; --- 116,129 ---- char * format_type_with_typemod(Oid type_oid, int32 typemod) { ! return format_type_internal(type_oid, typemod, true, false, true); } static char * format_type_internal(Oid type_oid, int32 typemod, ! bool typemod_given, bool allow_invalid, bool qualify) { bool with_typemod = typemod_given && (typemod >= 0); HeapTuple tuple; *************** *** 299,305 **** format_type_internal(Oid type_oid, int32 typemod, char *nspname; char *typname; ! if (TypeIsVisible(type_oid)) nspname = NULL; else nspname = get_namespace_name(typeform->typnamespace); --- 311,319 ---- char *nspname; char *typname; ! if (!qualify) ! nspname = NULL; ! else if (TypeIsVisible(type_oid)) nspname = NULL; else nspname = get_namespace_name(typeform->typnamespace); *************** *** 420,426 **** oidvectortypes(PG_FUNCTION_ARGS) for (num = 0; num < numargs; num++) { char *typename = format_type_internal(oidArray->values[num], -1, ! false, true); size_t slen = strlen(typename); if (left < (slen + 2)) --- 434,440 ---- for (num = 0; num < numargs; num++) { char *typename = format_type_internal(oidArray->values[num], -1, ! false, true, true); size_t slen = strlen(typename); if (left < (slen + 2)) *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** *** 39,47 **** --- 39,49 ---- #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/tlist.h" + #include "parser/analyze.h" #include "parser/keywords.h" #include "parser/parse_func.h" #include "parser/parse_oper.h" + #include "parser/parse_type.h" #include "parser/parser.h" #include "parser/parsetree.h" #include "rewrite/rewriteHandler.h" *************** *** 259,265 **** static char *flatten_reloptions(Oid relid); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") - /* ---------- * get_ruledef - Do it all and return a text * that could be used as a statement --- 261,266 ---- *** a/src/bin/pg_dump/common.c --- b/src/bin/pg_dump/common.c *************** *** 98,103 **** getSchemaData(Archive *fout, int *numTablesPtr) --- 98,104 ---- int numForeignDataWrappers; int numForeignServers; int numDefaultACLs; + int numCmdTriggers; if (g_verbose) write_msg(NULL, "reading schemas\n"); *************** *** 234,239 **** getSchemaData(Archive *fout, int *numTablesPtr) --- 235,244 ---- write_msg(NULL, "reading triggers\n"); getTriggers(fout, tblinfo, numTables); + if (g_verbose) + write_msg(NULL, "reading command triggers\n"); + getCmdTriggers(fout, &numCmdTriggers); + *numTablesPtr = numTables; return tblinfo; } *** a/src/bin/pg_dump/pg_dump.c --- b/src/bin/pg_dump/pg_dump.c *************** *** 48,53 **** --- 48,54 ---- #include "access/transam.h" #include "catalog/pg_cast.h" #include "catalog/pg_class.h" + #include "catalog/pg_cmdtrigger.h" #include "catalog/pg_default_acl.h" #include "catalog/pg_largeobject.h" #include "catalog/pg_largeobject_metadata.h" *************** *** 191,196 **** static void dumpConversion(Archive *fout, ConvInfo *convinfo); --- 192,198 ---- static void dumpRule(Archive *fout, RuleInfo *rinfo); static void dumpAgg(Archive *fout, AggInfo *agginfo); static void dumpTrigger(Archive *fout, TriggerInfo *tginfo); + static void dumpCmdTrigger(Archive *fout, CmdTriggerInfo *ctginfo); static void dumpTable(Archive *fout, TableInfo *tbinfo); static void dumpTableSchema(Archive *fout, TableInfo *tbinfo); static void dumpAttrDef(Archive *fout, AttrDefInfo *adinfo); *************** *** 5283,5288 **** getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) --- 5285,5361 ---- } /* + * getCmdTriggers + * get information about every command trigger on a dumpable table + */ + CmdTriggerInfo * + getCmdTriggers(Archive *fout, int *numCmdTriggers) + { + int i; + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + CmdTriggerInfo *ctginfo; + int i_tableoid, + i_oid, + i_ctgcommand, + i_ctgname, + i_ctgfname, + i_ctgtype, + i_ctgenabled; + int ntups; + + /* Make sure we are in proper schema */ + selectSourceSchema(fout, "pg_catalog"); + + if (fout->remoteVersion >= 90200) + { + appendPQExpBuffer(query, + "SELECT c.tableoid, c.oid, " + "ctgname, ctgtype, ctgcommand, " + "n.nspname || '.' || p.proname as ctgfname, ctgenabled " + "FROM pg_cmdtrigger c JOIN pg_proc p on c.ctgfoid = p.oid " + "JOIN pg_namespace n ON p.pronamespace = n.oid " + "ORDER BY c.oid"); + } + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + *numCmdTriggers = ntups; + + ctginfo = (CmdTriggerInfo *) pg_malloc(ntups * sizeof(CmdTriggerInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_ctgname = PQfnumber(res, "ctgname"); + i_ctgtype = PQfnumber(res, "ctgtype"); + i_ctgcommand = PQfnumber(res, "ctgcommand"); + i_ctgfname = PQfnumber(res, "ctgfname"); + i_ctgenabled = PQfnumber(res, "ctgenabled"); + + for (i = 0; i < ntups; i++) + { + ctginfo[i].dobj.objType = DO_CMDTRIGGER; + ctginfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + ctginfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&ctginfo[i].dobj); + ctginfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_ctgname)); + ctginfo[i].ctgname = pg_strdup(PQgetvalue(res, i, i_ctgname)); + ctginfo[i].ctgtype = *(PQgetvalue(res, i, i_ctgtype)); + ctginfo[i].ctgcommand = pg_strdup(PQgetvalue(res, i, i_ctgcommand)); + ctginfo[i].ctgfname = pg_strdup(PQgetvalue(res, i, i_ctgfname)); + ctginfo[i].ctgenabled = *(PQgetvalue(res, i, i_ctgenabled)); + } + + PQclear(res); + + destroyPQExpBuffer(query); + + return ctginfo; + } + + /* * getProcLangs * get basic information about every procedural language in the system * *************** *** 7181,7186 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj) --- 7254,7262 ---- case DO_TRIGGER: dumpTrigger(fout, (TriggerInfo *) dobj); break; + case DO_CMDTRIGGER: + dumpCmdTrigger(fout, (CmdTriggerInfo *) dobj); + break; case DO_CONSTRAINT: dumpConstraint(fout, (ConstraintInfo *) dobj); break; *************** *** 13658,13663 **** dumpTrigger(Archive *fout, TriggerInfo *tginfo) --- 13734,13810 ---- destroyPQExpBuffer(labelq); } + static void + dumpCmdTrigger(Archive *fout, CmdTriggerInfo *ctginfo) + { + PQExpBuffer query; + PQExpBuffer labelq; + + query = createPQExpBuffer(); + labelq = createPQExpBuffer(); + + appendPQExpBuffer(query, "CREATE COMMAND TRIGGER "); + appendPQExpBufferStr(query, fmtId(ctginfo->dobj.name)); + + /* Trigger type */ + if (ctginfo->ctgtype == CMD_TRIGGER_FIRED_BEFORE) + appendPQExpBuffer(query, " BEFORE "); + else if (ctginfo->ctgtype == CMD_TRIGGER_FIRED_AFTER) + appendPQExpBuffer(query, " AFTER "); + else + { + write_msg(NULL, "unexpected ctgtype value: %d\n", ctginfo->ctgtype); + exit_nicely(1); + } + + if (strcmp("ANY", ctginfo->ctgcommand) == 0) + appendPQExpBufferStr(query, "ANY COMMAND"); + else + { + appendPQExpBufferStr(query, ctginfo->ctgcommand); + } + + appendPQExpBuffer(query, " EXECUTE PROCEDURE "); + appendPQExpBufferStr(query, ctginfo->ctgfname); + appendPQExpBuffer(query, " ();\n"); + + if (ctginfo->ctgenabled != 'O') + { + appendPQExpBuffer(query, "\nALTER COMMAND TRIGGER %s SET ", + fmtId(ctginfo->dobj.name)); + switch (ctginfo->ctgenabled) + { + case 'D': + appendPQExpBuffer(query, "DISABLE"); + break; + case 'A': + appendPQExpBuffer(query, "ENABLE ALWAYS"); + break; + case 'R': + appendPQExpBuffer(query, "ENABLE REPLICA"); + break; + default: + appendPQExpBuffer(query, "ENABLE"); + break; + } + appendPQExpBuffer(query, ";\n"); + } + appendPQExpBuffer(labelq, "COMMAND TRIGGER %s ", + fmtId(ctginfo->dobj.name)); + + ArchiveEntry(fout, ctginfo->dobj.catId, ctginfo->dobj.dumpId, + ctginfo->dobj.name, NULL, NULL, "", false, + "COMMAND TRIGGER", SECTION_POST_DATA, + query->data, "", NULL, NULL, 0, NULL, NULL); + + dumpComment(fout, labelq->data, + NULL, NULL, + ctginfo->dobj.catId, 0, ctginfo->dobj.dumpId); + + destroyPQExpBuffer(query); + destroyPQExpBuffer(labelq); + } + /* * dumpRule * Dump a rule *** a/src/bin/pg_dump/pg_dump.h --- b/src/bin/pg_dump/pg_dump.h *************** *** 118,124 **** typedef enum DO_DEFAULT_ACL, DO_BLOB, DO_BLOB_DATA, ! DO_COLLATION } DumpableObjectType; typedef struct _dumpableObject --- 118,125 ---- DO_DEFAULT_ACL, DO_BLOB, DO_BLOB_DATA, ! DO_COLLATION, ! DO_CMDTRIGGER } DumpableObjectType; typedef struct _dumpableObject *************** *** 350,355 **** typedef struct _triggerInfo --- 351,366 ---- char *tgdef; } TriggerInfo; + typedef struct _cmdtriggerInfo + { + DumpableObject dobj; + char *ctgcommand; + char *ctgname; + char *ctgfname; + char ctgtype; + char ctgenabled; + } CmdTriggerInfo; + /* * struct ConstraintInfo is used for all constraint types. However we * use a different objType for foreign key constraints, to make it easier *************** *** 558,562 **** extern ForeignServerInfo *getForeignServers(Archive *fout, --- 569,574 ---- extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs); extern void getExtensionMembership(Archive *fout, ExtensionInfo extinfo[], int numExtensions); + extern CmdTriggerInfo *getCmdTriggers(Archive *fout, int *numCmdTriggers); #endif /* PG_DUMP_H */ *** a/src/bin/pg_dump/pg_dump_sort.c --- b/src/bin/pg_dump/pg_dump_sort.c *************** *** 59,65 **** static const int oldObjectTypePriority[] = 17, /* DO_DEFAULT_ACL */ 9, /* DO_BLOB */ 11, /* DO_BLOB_DATA */ ! 2 /* DO_COLLATION */ }; /* --- 59,66 ---- 17, /* DO_DEFAULT_ACL */ 9, /* DO_BLOB */ 11, /* DO_BLOB_DATA */ ! 2, /* DO_COLLATION */ ! 18 /* DO_CMDTRIGGER */ }; /* *************** *** 98,104 **** static const int newObjectTypePriority[] = 29, /* DO_DEFAULT_ACL */ 21, /* DO_BLOB */ 23, /* DO_BLOB_DATA */ ! 3 /* DO_COLLATION */ }; --- 99,106 ---- 29, /* DO_DEFAULT_ACL */ 21, /* DO_BLOB */ 23, /* DO_BLOB_DATA */ ! 3, /* DO_COLLATION */ ! 30 /* DO_CMDTRIGGER */ }; *************** *** 1114,1119 **** describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) --- 1116,1126 ---- "TRIGGER %s (ID %d OID %u)", obj->name, obj->dumpId, obj->catId.oid); return; + case DO_CMDTRIGGER: + snprintf(buf, bufsize, + "TRIGGER %s ON COMMAND %s (ID %d OID %u)", + obj->name, ((CmdTriggerInfo *)obj)->ctgcommand, obj->dumpId, obj->catId.oid); + return; case DO_CONSTRAINT: snprintf(buf, bufsize, "CONSTRAINT %s (ID %d OID %u)", *** a/src/bin/psql/command.c --- b/src/bin/psql/command.c *************** *** 363,369 **** exec_command(const char *cmd, success = describeTablespaces(pattern, show_verbose); break; case 'c': ! success = listConversions(pattern, show_verbose, show_system); break; case 'C': success = listCasts(pattern, show_verbose); --- 363,380 ---- success = describeTablespaces(pattern, show_verbose); break; case 'c': ! switch (cmd[2]) ! { ! case '\0': ! success = listConversions(pattern, show_verbose, show_system); ! break; ! case 't': ! success = listCmdTriggers(pattern, show_verbose); ! break; ! default: ! status = PSQL_CMD_UNKNOWN; ! break; ! } break; case 'C': success = listCasts(pattern, show_verbose); *** a/src/bin/psql/describe.c --- b/src/bin/psql/describe.c *************** *** 2947,2952 **** listConversions(const char *pattern, bool verbose, bool showSystem) --- 2947,3009 ---- } /* + * \dct + * + * Describes command triggers. + */ + bool + listCmdTriggers(const char *pattern, bool verbose) + { + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + static const bool translate_columns[] = {true, true}; + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT ctgname as \"%s\", " + "'CREATE TRIGGER ' || ctgname || ' ' || " + "case ctgtype when 'A' then 'AFTER' " + " when 'B' then 'BEFORE' " + " when 'I' then 'INSTEAD OF' " + "end || " + "case ctgcommand when 'ANY' then ' ANY COMMAND '" + " else ' COMMAND ' || ctgcommand || ' '" + "end ||" + " 'EXECUTE PROCEDURE ' || proname || '();' as \"%s\" " + "FROM pg_cmdtrigger c " + "JOIN pg_proc p on c.ctgfoid = p.oid ", + gettext_noop("Name"), + gettext_noop("Definition")); + + if (pattern) + { + processSQLNamePattern(pset.db, &buf, pattern, false, false, + NULL, "ctgcommand", NULL, NULL); + + appendPQExpBuffer(&buf, " OR ctgcommand = 'ANY' "); + } + + appendPQExpBuffer(&buf, "ORDER BY c.oid"); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + myopt.title = _("List of command triggers"); + myopt.translate_header = true; + myopt.translate_columns = translate_columns; + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; + } + + /* * \dC * * Describes casts. *** a/src/bin/psql/describe.h --- b/src/bin/psql/describe.h *************** *** 66,71 **** extern bool listDomains(const char *pattern, bool verbose, bool showSystem); --- 66,74 ---- /* \dc */ extern bool listConversions(const char *pattern, bool verbose, bool showSystem); + /* \dcT */ + extern bool listCmdTriggers(const char *pattern, bool verbose); + /* \dC */ extern bool listCasts(const char *pattern, bool verbose); *** a/src/bin/psql/help.c --- b/src/bin/psql/help.c *************** *** 199,204 **** slashUsage(unsigned short int pager) --- 199,205 ---- fprintf(output, _(" \\d[S+] NAME describe table, view, sequence, or index\n")); fprintf(output, _(" \\da[S] [PATTERN] list aggregates\n")); fprintf(output, _(" \\db[+] [PATTERN] list tablespaces\n")); + fprintf(output, _(" \\dct [PATTERN] list command triggers\n")); fprintf(output, _(" \\dc[S+] [PATTERN] list conversions\n")); fprintf(output, _(" \\dC[+] [PATTERN] list casts\n")); fprintf(output, _(" \\dd[S] [PATTERN] show object descriptions not displayed elsewhere\n")); *** a/src/include/catalog/dependency.h --- b/src/include/catalog/dependency.h *************** *** 146,151 **** typedef enum ObjectClass --- 146,152 ---- OCLASS_USER_MAPPING, /* pg_user_mapping */ OCLASS_DEFACL, /* pg_default_acl */ OCLASS_EXTENSION, /* pg_extension */ + OCLASS_CMDTRIGGER, /* pg_cmdtrigger */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; *** a/src/include/catalog/index.h --- b/src/include/catalog/index.h *************** *** 14,19 **** --- 14,20 ---- #ifndef INDEX_H #define INDEX_H + #include "commands/cmdtrigger.h" #include "nodes/execnodes.h" *************** *** 88,101 **** extern double IndexBuildHeapScan(Relation heapRelation, extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot); ! extern void reindex_index(Oid indexId, bool skip_constraint_checks); /* Flag bits for reindex_relation(): */ #define REINDEX_REL_PROCESS_TOAST 0x01 #define REINDEX_REL_SUPPRESS_INDEX_USE 0x02 #define REINDEX_REL_CHECK_CONSTRAINTS 0x04 ! extern bool reindex_relation(Oid relid, int flags); extern bool ReindexIsProcessingHeap(Oid heapOid); extern bool ReindexIsProcessingIndex(Oid indexOid); --- 89,103 ---- extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot); ! extern void reindex_index(Oid indexId, bool skip_constraint_checks, ! CommandContext cmd); /* Flag bits for reindex_relation(): */ #define REINDEX_REL_PROCESS_TOAST 0x01 #define REINDEX_REL_SUPPRESS_INDEX_USE 0x02 #define REINDEX_REL_CHECK_CONSTRAINTS 0x04 ! extern bool reindex_relation(Oid relid, int flags, CommandContext cmd); extern bool ReindexIsProcessingHeap(Oid heapOid); extern bool ReindexIsProcessingIndex(Oid indexOid); *** a/src/include/catalog/indexing.h --- b/src/include/catalog/indexing.h *************** *** 234,239 **** DECLARE_UNIQUE_INDEX(pg_trigger_tgrelid_tgname_index, 2701, on pg_trigger using --- 234,246 ---- DECLARE_UNIQUE_INDEX(pg_trigger_oid_index, 2702, on pg_trigger using btree(oid oid_ops)); #define TriggerOidIndexId 2702 + DECLARE_UNIQUE_INDEX(pg_cmdtrigger_ctgname_index, 3467, on pg_cmdtrigger using btree(ctgname name_ops)); + #define CmdTriggerNameIndexId 3467 + DECLARE_INDEX(pg_cmdtrigger_ctgcommand_index, 3468, on pg_cmdtrigger using btree(ctgcommand name_ops)); + #define CmdTriggerCommandNameIndexId 3468 + DECLARE_UNIQUE_INDEX(pg_cmdtrigger_oid_index, 3469, on pg_cmdtrigger using btree(oid oid_ops)); + #define CmdTriggerOidIndexId 3469 + DECLARE_UNIQUE_INDEX(pg_ts_config_cfgname_index, 3608, on pg_ts_config using btree(cfgname name_ops, cfgnamespace oid_ops)); #define TSConfigNameNspIndexId 3608 DECLARE_UNIQUE_INDEX(pg_ts_config_oid_index, 3712, on pg_ts_config using btree(oid oid_ops)); *** a/src/include/catalog/pg_aggregate.h --- b/src/include/catalog/pg_aggregate.h *************** *** 19,24 **** --- 19,25 ---- #ifndef PG_AGGREGATE_H #define PG_AGGREGATE_H + #include "commands/cmdtrigger.h" #include "catalog/genbki.h" #include "nodes/pg_list.h" *************** *** 242,247 **** extern void AggregateCreate(const char *aggName, List *aggfinalfnName, List *aggsortopName, Oid aggTransType, ! const char *agginitval); #endif /* PG_AGGREGATE_H */ --- 243,249 ---- List *aggfinalfnName, List *aggsortopName, Oid aggTransType, ! const char *agginitval, ! CommandContext cmd); #endif /* PG_AGGREGATE_H */ *** /dev/null --- b/src/include/catalog/pg_cmdtrigger.h *************** *** 0 **** --- 1,70 ---- + /*------------------------------------------------------------------------- + * + * pg_cmdtrigger.h + * definition of the system "command trigger" relation (pg_cmdtrigger) + * along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_trigger.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ + #ifndef PG_CMDTRIGGER_H + #define PG_CMDTRIGGER_H + + #include "catalog/genbki.h" + + /* ---------------- + * pg_cmdtrigger definition. cpp turns this into + * typedef struct FormData_pg_cmdtrigger + * ---------------- + */ + #define CmdTriggerRelationId 3466 + + CATALOG(pg_cmdtrigger,3466) + { + NameData ctgname; /* trigger's name */ + NameData ctgcommand; /* trigger's command */ + Oid ctgfoid; /* OID of function to be called */ + char ctgtype; /* BEFORE/AFTER */ + char ctgenabled; /* trigger's firing configuration WRT + * session_replication_role */ + } FormData_pg_cmdtrigger; + + /* ---------------- + * Form_pg_cmdtrigger corresponds to a pointer to a tuple with + * the format of pg_cmdtrigger relation. + * ---------------- + */ + typedef FormData_pg_cmdtrigger *Form_pg_cmdtrigger; + + /* ---------------- + * compiler constants for pg_cmdtrigger + * ---------------- + */ + #define Natts_pg_cmdtrigger 5 + #define Anum_pg_cmdtrigger_ctgname 1 + #define Anum_pg_cmdtrigger_ctgcommand 2 + #define Anum_pg_cmdtrigger_ctgfoid 3 + #define Anum_pg_cmdtrigger_ctgtype 4 + #define Anum_pg_cmdtrigger_ctgenabled 5 + + /* + * Times at which a command trigger can be fired. These are the + * possible values for pg_cmdtrigger.ctgtype. + * + * pg_trigger is using binary mask tricks to make it super fast, but we don't + * need to be that tricky here: we're talking about commands, not data editing, + * and we don't have so many conditions, only type and enabled. + */ + #define CMD_TRIGGER_FIRED_BEFORE 'B' + #define CMD_TRIGGER_FIRED_AFTER 'A' + + #endif /* PG_CMDTRIGGER_H */ *** a/src/include/catalog/pg_collation_fn.h --- b/src/include/catalog/pg_collation_fn.h *************** *** 14,23 **** #ifndef PG_COLLATION_FN_H #define PG_COLLATION_FN_H extern Oid CollationCreate(const char *collname, Oid collnamespace, Oid collowner, int32 collencoding, ! const char *collcollate, const char *collctype); extern void RemoveCollationById(Oid collationOid); #endif /* PG_COLLATION_FN_H */ --- 14,26 ---- #ifndef PG_COLLATION_FN_H #define PG_COLLATION_FN_H + #include "commands/cmdtrigger.h" + extern Oid CollationCreate(const char *collname, Oid collnamespace, Oid collowner, int32 collencoding, ! const char *collcollate, const char *collctype, ! CommandContext cmd); extern void RemoveCollationById(Oid collationOid); #endif /* PG_COLLATION_FN_H */ *** a/src/include/catalog/pg_operator.h --- b/src/include/catalog/pg_operator.h *************** *** 23,28 **** --- 23,29 ---- #define PG_OPERATOR_H #include "catalog/genbki.h" + #include "commands/cmdtrigger.h" #include "nodes/pg_list.h" /* ---------------- *************** *** 1725,1730 **** extern void OperatorCreate(const char *operatorName, Oid restrictionId, Oid joinId, bool canMerge, ! bool canHash); #endif /* PG_OPERATOR_H */ --- 1726,1732 ---- Oid restrictionId, Oid joinId, bool canMerge, ! bool canHash, ! CommandContext cmd); #endif /* PG_OPERATOR_H */ *** a/src/include/catalog/pg_type.h --- b/src/include/catalog/pg_type.h *************** *** 650,655 **** DATA(insert OID = 2278 ( void PGNSP PGUID 4 t p P f t \054 0 0 0 void_in void --- 650,657 ---- #define VOIDOID 2278 DATA(insert OID = 2279 ( trigger PGNSP PGUID 4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ )); #define TRIGGEROID 2279 + DATA(insert OID = 3838 ( command_trigger PGNSP PGUID 4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ )); + #define CMDTRIGGEROID 3838 DATA(insert OID = 2280 ( language_handler PGNSP PGUID 4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ )); #define LANGUAGE_HANDLEROID 2280 DATA(insert OID = 2281 ( internal PGNSP PGUID SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 0 _null_ _null_ _null_ )); *** a/src/include/catalog/pg_type_fn.h --- b/src/include/catalog/pg_type_fn.h *************** *** 14,19 **** --- 14,20 ---- #ifndef PG_TYPE_FN_H #define PG_TYPE_FN_H + #include "commands/cmdtrigger.h" #include "nodes/nodes.h" *************** *** 73,79 **** extern void GenerateTypeDependencies(Oid typeNamespace, bool rebuild); extern void RenameTypeInternal(Oid typeOid, const char *newTypeName, ! Oid typeNamespace); extern char *makeArrayTypeName(const char *typeName, Oid typeNamespace); --- 74,80 ---- bool rebuild); extern void RenameTypeInternal(Oid typeOid, const char *newTypeName, ! Oid typeNamespace, CommandContext cmd); extern char *makeArrayTypeName(const char *typeName, Oid typeNamespace); *** a/src/include/commands/alter.h --- b/src/include/commands/alter.h *************** *** 14,19 **** --- 14,20 ---- #ifndef ALTER_H #define ALTER_H + #include "commands/cmdtrigger.h" #include "utils/acl.h" #include "utils/relcache.h" *************** *** 23,29 **** extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid); extern Oid AlterObjectNamespace(Relation rel, int oidCacheId, int nameCacheId, Oid objid, Oid nspOid, int Anum_name, int Anum_namespace, int Anum_owner, ! AclObjectKind acl_kind); extern void ExecAlterOwnerStmt(AlterOwnerStmt *stmt); #endif /* ALTER_H */ --- 24,30 ---- extern Oid AlterObjectNamespace(Relation rel, int oidCacheId, int nameCacheId, Oid objid, Oid nspOid, int Anum_name, int Anum_namespace, int Anum_owner, ! AclObjectKind acl_kind, CommandContext cmd); extern void ExecAlterOwnerStmt(AlterOwnerStmt *stmt); #endif /* ALTER_H */ *** a/src/include/commands/cluster.h --- b/src/include/commands/cluster.h *************** *** 13,18 **** --- 13,19 ---- #ifndef CLUSTER_H #define CLUSTER_H + #include "commands/cmdtrigger.h" #include "nodes/parsenodes.h" #include "storage/lock.h" #include "utils/relcache.h" *************** *** 20,26 **** extern void cluster(ClusterStmt *stmt, bool isTopLevel); extern void cluster_rel(Oid tableOid, Oid indexOid, bool recheck, ! bool verbose, int freeze_min_age, int freeze_table_age); extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck, LOCKMODE lockmode); extern void mark_index_clustered(Relation rel, Oid indexOid); --- 21,28 ---- extern void cluster(ClusterStmt *stmt, bool isTopLevel); extern void cluster_rel(Oid tableOid, Oid indexOid, bool recheck, ! bool verbose, int freeze_min_age, int freeze_table_age, ! CommandContext cmd); extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck, LOCKMODE lockmode); extern void mark_index_clustered(Relation rel, Oid indexOid); *** /dev/null --- b/src/include/commands/cmdtrigger.h *************** *** 0 **** --- 1,73 ---- + /*------------------------------------------------------------------------- + * + * cmdtrigger.h + * Declarations for command trigger handling. + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/cmdtrigger.h + * + *------------------------------------------------------------------------- + */ + #ifndef CMDTRIGGER_H + #define CMDTRIGGER_H + + #include "nodes/parsenodes.h" + + /* + * Command Trigger Procedure are passed 4 arguments which are maintained in a + * global (bachend private) variable, command_context. That allows each command + * implementation to fill in the context then call the Exec...CommandTriggers + * API functions. + */ + typedef struct CommandContextData + { + char *tag; /* Command Tag */ + Oid objectId; /* oid of the existing object, if any */ + char *schemaname; /* schemaname or NULL if not relevant */ + char *objectname; /* objectname */ + Node *parsetree; /* command parsetree, given as an internal */ + List *before; /* procedures to call before the command */ + List *after; /* procedures to call after the command */ + List *before_any; /* procedures to call before any command */ + List *after_any; /* procedures to call after any command */ + MemoryContext oldmctx; /* Memory Context to switch back to */ + MemoryContext cmdmctx; /* Memory Context for the command triggers */ + } CommandContextData; + + typedef struct CommandContextData *CommandContext; + + /* + * CommandTriggerData is the node type that is passed as fmgr "context" info + * when a function is called by the command trigger manager. + */ + #define CALLED_AS_COMMAND_TRIGGER(fcinfo) \ + ((fcinfo)->context != NULL && IsA((fcinfo)->context, CommandTriggerData)) + + typedef struct CommandTriggerData + { + NodeTag type; + char *when; /* Either BEFORE or AFTER */ + char *tag; /* Command Tag */ + Oid objectId; /* oid of the existing object, if any */ + char *schemaname; /* schemaname or NULL if not relevant */ + char *objectname; /* objectname */ + Node *parsetree; /* command parsetree, given as an internal */ + } CommandTriggerData; + + extern Oid CreateCmdTrigger(CreateCmdTrigStmt *stmt, const char *queryString); + extern void RemoveCmdTriggerById(Oid ctrigOid); + extern Oid get_cmdtrigger_oid(const char *trigname, bool missing_ok); + extern void AlterCmdTrigger(AlterCmdTrigStmt *stmt); + extern void RenameCmdTrigger(List *name, const char *newname); + + extern void InitCommandContext(CommandContext cmd, const Node *stmt); + extern bool CommandFiresTriggers(CommandContext cmd); + extern bool CommandFiresAfterTriggers(CommandContext cmd); + extern void ExecBeforeCommandTriggers(CommandContext cmd); + extern void ExecBeforeAnyCommandTriggers(CommandContext cmd); + extern void ExecAfterCommandTriggers(CommandContext cmd); + extern void ExecAfterAnyCommandTriggers(CommandContext cmd); + + #endif /* CMD_TRIGGER_H */ *** a/src/include/commands/collationcmds.h --- b/src/include/commands/collationcmds.h *************** *** 15,27 **** #ifndef COLLATIONCMDS_H #define COLLATIONCMDS_H #include "nodes/parsenodes.h" ! extern void DefineCollation(List *names, List *parameters); ! extern void RenameCollation(List *name, const char *newname); ! extern void AlterCollationOwner(List *name, Oid newOwnerId); ! extern void AlterCollationOwner_oid(Oid collationOid, Oid newOwnerId); ! extern void AlterCollationNamespace(List *name, const char *newschema); ! extern Oid AlterCollationNamespace_oid(Oid collOid, Oid newNspOid); #endif /* COLLATIONCMDS_H */ --- 15,30 ---- #ifndef COLLATIONCMDS_H #define COLLATIONCMDS_H + #include "commands/cmdtrigger.h" #include "nodes/parsenodes.h" ! extern void DefineCollation(List *names, List *parameters, CommandContext cmd); ! extern void RenameCollation(List *name, const char *newname, CommandContext cmd); ! extern void AlterCollationOwner(List *name, Oid newOwnerId, CommandContext cmd); ! extern void AlterCollationOwner_oid(Oid collationOid, Oid newOwnerId, CommandContext cmd); ! extern void AlterCollationNamespace(List *name, const char *newschema, ! CommandContext cmd); ! extern Oid AlterCollationNamespace_oid(Oid collOid, Oid newNspOid, ! CommandContext cmd); #endif /* COLLATIONCMDS_H */ *** a/src/include/commands/conversioncmds.h --- b/src/include/commands/conversioncmds.h *************** *** 18,27 **** #include "nodes/parsenodes.h" extern void CreateConversionCommand(CreateConversionStmt *parsetree); ! extern void RenameConversion(List *name, const char *newname); ! extern void AlterConversionOwner(List *name, Oid newOwnerId); ! extern void AlterConversionOwner_oid(Oid conversionOid, Oid newOwnerId); ! extern void AlterConversionNamespace(List *name, const char *newschema); extern Oid AlterConversionNamespace_oid(Oid convOid, Oid newNspOid); #endif /* CONVERSIONCMDS_H */ --- 18,28 ---- #include "nodes/parsenodes.h" extern void CreateConversionCommand(CreateConversionStmt *parsetree); ! extern void RenameConversion(List *name, const char *newname, CommandContext cmd); ! extern void AlterConversionOwner(List *name, Oid newOwnerId, CommandContext cmd); ! extern void AlterConversionOwner_oid(Oid conversionOid, Oid newOwnerId, CommandContext cmd); ! extern void AlterConversionNamespace(List *name, const char *newschema, ! CommandContext cmd); extern Oid AlterConversionNamespace_oid(Oid convOid, Oid newNspOid); #endif /* CONVERSIONCMDS_H */ *** a/src/include/commands/createas.h --- b/src/include/commands/createas.h *************** *** 14,19 **** --- 14,20 ---- #ifndef CREATEAS_H #define CREATEAS_H + #include "commands/cmdtrigger.h" #include "nodes/params.h" #include "nodes/parsenodes.h" #include "tcop/dest.h" *************** *** 24,29 **** extern void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, extern int GetIntoRelEFlags(IntoClause *intoClause); ! extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause); #endif /* CREATEAS_H */ --- 25,31 ---- extern int GetIntoRelEFlags(IntoClause *intoClause); ! extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause, ! CommandContext cmd); #endif /* CREATEAS_H */ *** a/src/include/commands/dbcommands.h --- b/src/include/commands/dbcommands.h *************** *** 53,59 **** typedef struct xl_dbase_drop_rec } xl_dbase_drop_rec; extern void createdb(const CreatedbStmt *stmt); ! extern void dropdb(const char *dbname, bool missing_ok); extern void RenameDatabase(const char *oldname, const char *newname); extern void AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel); extern void AlterDatabaseSet(AlterDatabaseSetStmt *stmt); --- 53,59 ---- } xl_dbase_drop_rec; extern void createdb(const CreatedbStmt *stmt); ! extern void dropdb(const DropdbStmt *stmt); extern void RenameDatabase(const char *oldname, const char *newname); extern void AlterDatabase(AlterDatabaseStmt *stmt, bool isTopLevel); extern void AlterDatabaseSet(AlterDatabaseSetStmt *stmt); *** a/src/include/commands/defrem.h --- b/src/include/commands/defrem.h *************** *** 14,19 **** --- 14,20 ---- #ifndef DEFREM_H #define DEFREM_H + #include "commands/cmdtrigger.h" #include "nodes/parsenodes.h" /* commands/dropcmds.c */ *************** *** 39,47 **** extern Oid DefineIndex(RangeVar *heapRelation, bool check_rights, bool skip_build, bool quiet, ! bool concurrent); ! extern void ReindexIndex(RangeVar *indexRelation); ! extern void ReindexTable(RangeVar *relation); extern void ReindexDatabase(const char *databaseName, bool do_system, bool do_user); extern char *makeObjectName(const char *name1, const char *name2, --- 40,49 ---- bool check_rights, bool skip_build, bool quiet, ! bool concurrent, ! CommandContext cmd); ! extern void ReindexIndex(RangeVar *indexRelation, CommandContext cmd); ! extern void ReindexTable(RangeVar *relation, CommandContext cmd); extern void ReindexDatabase(const char *databaseName, bool do_system, bool do_user); extern char *makeObjectName(const char *name1, const char *name2, *************** *** 64,95 **** extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString); extern void RemoveFunctionById(Oid funcOid); extern void SetFunctionReturnType(Oid funcOid, Oid newRetType); extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType); ! extern void RenameFunction(List *name, List *argtypes, const char *newname); ! extern void AlterFunctionOwner(List *name, List *argtypes, Oid newOwnerId); ! extern void AlterFunctionOwner_oid(Oid procOid, Oid newOwnerId); extern void AlterFunction(AlterFunctionStmt *stmt); extern void CreateCast(CreateCastStmt *stmt); extern void DropCastById(Oid castOid); extern void AlterFunctionNamespace(List *name, List *argtypes, bool isagg, ! const char *newschema); ! extern Oid AlterFunctionNamespace_oid(Oid procOid, Oid nspOid); extern void ExecuteDoStmt(DoStmt *stmt); extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok); /* commands/operatorcmds.c */ ! extern void DefineOperator(List *names, List *parameters); extern void RemoveOperatorById(Oid operOid); extern void AlterOperatorOwner(List *name, TypeName *typeName1, ! TypeName *typename2, Oid newOwnerId); extern void AlterOperatorOwner_oid(Oid operOid, Oid newOwnerId); ! extern void AlterOperatorNamespace(List *names, List *argtypes, const char *newschema); extern Oid AlterOperatorNamespace_oid(Oid operOid, Oid newNspOid); /* commands/aggregatecmds.c */ extern void DefineAggregate(List *name, List *args, bool oldstyle, ! List *parameters); ! extern void RenameAggregate(List *name, List *args, const char *newname); ! extern void AlterAggregateOwner(List *name, List *args, Oid newOwnerId); /* commands/opclasscmds.c */ extern void DefineOpClass(CreateOpClassStmt *stmt); --- 66,99 ---- extern void RemoveFunctionById(Oid funcOid); extern void SetFunctionReturnType(Oid funcOid, Oid newRetType); extern void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType); ! extern void RenameFunction(List *name, List *argtypes, const char *newname, CommandContext cmd); ! extern void AlterFunctionOwner(List *name, List *argtypes, Oid newOwnerId, CommandContext cmd); ! extern void AlterFunctionOwner_oid(Oid procOid, Oid newOwnerId, CommandContext cmd); extern void AlterFunction(AlterFunctionStmt *stmt); extern void CreateCast(CreateCastStmt *stmt); extern void DropCastById(Oid castOid); extern void AlterFunctionNamespace(List *name, List *argtypes, bool isagg, ! const char *newschema, CommandContext cmd); ! extern Oid AlterFunctionNamespace_oid(Oid procOid, Oid nspOid, ! CommandContext cmd); extern void ExecuteDoStmt(DoStmt *stmt); extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok); /* commands/operatorcmds.c */ ! extern void DefineOperator(List *names, List *parameters, CommandContext cmd); extern void RemoveOperatorById(Oid operOid); extern void AlterOperatorOwner(List *name, TypeName *typeName1, ! TypeName *typename2, Oid newOwnerId, CommandContext cmd); extern void AlterOperatorOwner_oid(Oid operOid, Oid newOwnerId); ! extern void AlterOperatorNamespace(List *names, List *argtypes, ! const char *newschema, CommandContext cmd); extern Oid AlterOperatorNamespace_oid(Oid operOid, Oid newNspOid); /* commands/aggregatecmds.c */ extern void DefineAggregate(List *name, List *args, bool oldstyle, ! List *parameters, CommandContext cmd); ! extern void RenameAggregate(List *name, List *args, const char *newname, CommandContext cmd); ! extern void AlterAggregateOwner(List *name, List *args, Oid newOwnerId, CommandContext cmd); /* commands/opclasscmds.c */ extern void DefineOpClass(CreateOpClassStmt *stmt); *************** *** 99,156 **** extern void RemoveOpClassById(Oid opclassOid); extern void RemoveOpFamilyById(Oid opfamilyOid); extern void RemoveAmOpEntryById(Oid entryOid); extern void RemoveAmProcEntryById(Oid entryOid); ! extern void RenameOpClass(List *name, const char *access_method, const char *newname); ! extern void RenameOpFamily(List *name, const char *access_method, const char *newname); ! extern void AlterOpClassOwner(List *name, const char *access_method, Oid newOwnerId); extern void AlterOpClassOwner_oid(Oid opclassOid, Oid newOwnerId); ! extern void AlterOpClassNamespace(List *name, char *access_method, const char *newschema); extern Oid AlterOpClassNamespace_oid(Oid opclassOid, Oid newNspOid); ! extern void AlterOpFamilyOwner(List *name, const char *access_method, Oid newOwnerId); extern void AlterOpFamilyOwner_oid(Oid opfamilyOid, Oid newOwnerId); ! extern void AlterOpFamilyNamespace(List *name, char *access_method, const char *newschema); extern Oid AlterOpFamilyNamespace_oid(Oid opfamilyOid, Oid newNspOid); extern Oid get_am_oid(const char *amname, bool missing_ok); extern Oid get_opclass_oid(Oid amID, List *opclassname, bool missing_ok); extern Oid get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok); /* commands/tsearchcmds.c */ ! extern void DefineTSParser(List *names, List *parameters); ! extern void RenameTSParser(List *oldname, const char *newname); ! extern void AlterTSParserNamespace(List *name, const char *newschema); extern Oid AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid); extern void RemoveTSParserById(Oid prsId); ! extern void DefineTSDictionary(List *names, List *parameters); ! extern void RenameTSDictionary(List *oldname, const char *newname); extern void RemoveTSDictionaryById(Oid dictId); extern void AlterTSDictionary(AlterTSDictionaryStmt *stmt); ! extern void AlterTSDictionaryOwner(List *name, Oid newOwnerId); ! extern void AlterTSDictionaryNamespace(List *name, const char *newschema); extern Oid AlterTSDictionaryNamespace_oid(Oid dictId, Oid newNspOid); ! extern void DefineTSTemplate(List *names, List *parameters); ! extern void RenameTSTemplate(List *oldname, const char *newname); ! extern void AlterTSTemplateNamespace(List *name, const char *newschema); extern Oid AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid); extern void RemoveTSTemplateById(Oid tmplId); ! extern void DefineTSConfiguration(List *names, List *parameters); ! extern void RenameTSConfiguration(List *oldname, const char *newname); extern void RemoveTSConfigurationById(Oid cfgId); extern void AlterTSConfiguration(AlterTSConfigurationStmt *stmt); ! extern void AlterTSConfigurationOwner(List *name, Oid newOwnerId); ! extern void AlterTSConfigurationNamespace(List *name, const char *newschema); extern Oid AlterTSConfigurationNamespace_oid(Oid cfgId, Oid newNspOid); extern text *serialize_deflist(List *deflist); extern List *deserialize_deflist(Datum txt); /* commands/foreigncmds.c */ ! extern void RenameForeignServer(const char *oldname, const char *newname); ! extern void RenameForeignDataWrapper(const char *oldname, const char *newname); ! extern void AlterForeignServerOwner(const char *name, Oid newOwnerId); extern void AlterForeignServerOwner_oid(Oid , Oid newOwnerId); ! extern void AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId); extern void AlterForeignDataWrapperOwner_oid(Oid fwdId, Oid newOwnerId); extern void CreateForeignDataWrapper(CreateFdwStmt *stmt); extern void AlterForeignDataWrapper(AlterFdwStmt *stmt); --- 103,166 ---- extern void RemoveOpFamilyById(Oid opfamilyOid); extern void RemoveAmOpEntryById(Oid entryOid); extern void RemoveAmProcEntryById(Oid entryOid); ! extern void RenameOpClass(List *name, const char *access_method, ! const char *newname, CommandContext cmd); ! extern void RenameOpFamily(List *name, const char *access_method, ! const char *newname, CommandContext cmd); ! extern void AlterOpClassOwner(List *name, const char *access_method, ! Oid newOwnerId, CommandContext cmd); extern void AlterOpClassOwner_oid(Oid opclassOid, Oid newOwnerId); ! extern void AlterOpClassNamespace(List *name, char *access_method, ! const char *newschema, CommandContext cmd); extern Oid AlterOpClassNamespace_oid(Oid opclassOid, Oid newNspOid); ! extern void AlterOpFamilyOwner(List *name, const char *access_method, ! Oid newOwnerId, CommandContext cmd); extern void AlterOpFamilyOwner_oid(Oid opfamilyOid, Oid newOwnerId); ! extern void AlterOpFamilyNamespace(List *name, char *access_method, ! const char *newschema, CommandContext cmd); extern Oid AlterOpFamilyNamespace_oid(Oid opfamilyOid, Oid newNspOid); extern Oid get_am_oid(const char *amname, bool missing_ok); extern Oid get_opclass_oid(Oid amID, List *opclassname, bool missing_ok); extern Oid get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok); /* commands/tsearchcmds.c */ ! extern void DefineTSParser(List *names, List *parameters, CommandContext cmd); ! extern void RenameTSParser(List *oldname, const char *newname, CommandContext cmd); ! extern void AlterTSParserNamespace(List *name, const char *newschema, CommandContext cmd); extern Oid AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid); extern void RemoveTSParserById(Oid prsId); ! extern void DefineTSDictionary(List *names, List *parameters, CommandContext cmd); ! extern void RenameTSDictionary(List *oldname, const char *newname, CommandContext cmd); extern void RemoveTSDictionaryById(Oid dictId); extern void AlterTSDictionary(AlterTSDictionaryStmt *stmt); ! extern void AlterTSDictionaryOwner(List *name, Oid newOwnerId, CommandContext cmd); ! extern void AlterTSDictionaryNamespace(List *name, const char *newschema, CommandContext cmd); extern Oid AlterTSDictionaryNamespace_oid(Oid dictId, Oid newNspOid); ! extern void DefineTSTemplate(List *names, List *parameters, CommandContext cmd); ! extern void RenameTSTemplate(List *oldname, const char *newname, CommandContext cmd); ! extern void AlterTSTemplateNamespace(List *name, const char *newschema, CommandContext cmd); extern Oid AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid); extern void RemoveTSTemplateById(Oid tmplId); ! extern void DefineTSConfiguration(List *names, List *parameters, CommandContext cmd); ! extern void RenameTSConfiguration(List *oldname, const char *newname, CommandContext cmd); extern void RemoveTSConfigurationById(Oid cfgId); extern void AlterTSConfiguration(AlterTSConfigurationStmt *stmt); ! extern void AlterTSConfigurationOwner(List *name, Oid newOwnerId, CommandContext cmd); ! extern void AlterTSConfigurationNamespace(List *name, const char *newschema, CommandContext cmd); extern Oid AlterTSConfigurationNamespace_oid(Oid cfgId, Oid newNspOid); extern text *serialize_deflist(List *deflist); extern List *deserialize_deflist(Datum txt); /* commands/foreigncmds.c */ ! extern void RenameForeignServer(const char *oldname, const char *newname, CommandContext cmd); ! extern void RenameForeignDataWrapper(const char *oldname, const char *newname, CommandContext cmd); ! extern void AlterForeignServerOwner(const char *name, Oid newOwnerId, CommandContext cmd); extern void AlterForeignServerOwner_oid(Oid , Oid newOwnerId); ! extern void AlterForeignDataWrapperOwner(const char *name, Oid newOwnerId, CommandContext cmd); extern void AlterForeignDataWrapperOwner_oid(Oid fwdId, Oid newOwnerId); extern void CreateForeignDataWrapper(CreateFdwStmt *stmt); extern void AlterForeignDataWrapper(AlterFdwStmt *stmt); *** a/src/include/commands/extension.h --- b/src/include/commands/extension.h *************** *** 14,19 **** --- 14,20 ---- #ifndef EXTENSION_H #define EXTENSION_H + #include "commands/cmdtrigger.h" #include "nodes/parsenodes.h" *************** *** 43,48 **** extern void ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt); extern Oid get_extension_oid(const char *extname, bool missing_ok); extern char *get_extension_name(Oid ext_oid); ! extern void AlterExtensionNamespace(List *names, const char *newschema); #endif /* EXTENSION_H */ --- 44,50 ---- extern Oid get_extension_oid(const char *extname, bool missing_ok); extern char *get_extension_name(Oid ext_oid); ! extern void AlterExtensionNamespace(List *names, const char *newschema, ! CommandContext cmd); #endif /* EXTENSION_H */ *** a/src/include/commands/proclang.h --- b/src/include/commands/proclang.h *************** *** 12,23 **** #ifndef PROCLANG_H #define PROCLANG_H #include "nodes/parsenodes.h" extern void CreateProceduralLanguage(CreatePLangStmt *stmt); extern void DropProceduralLanguageById(Oid langOid); ! extern void RenameLanguage(const char *oldname, const char *newname); ! extern void AlterLanguageOwner(const char *name, Oid newOwnerId); extern void AlterLanguageOwner_oid(Oid oid, Oid newOwnerId); extern bool PLTemplateExists(const char *languageName); extern Oid get_language_oid(const char *langname, bool missing_ok); --- 12,24 ---- #ifndef PROCLANG_H #define PROCLANG_H + #include "commands/cmdtrigger.h" #include "nodes/parsenodes.h" extern void CreateProceduralLanguage(CreatePLangStmt *stmt); extern void DropProceduralLanguageById(Oid langOid); ! extern void RenameLanguage(const char *oldname, const char *newname, CommandContext cmd); ! extern void AlterLanguageOwner(const char *name, Oid newOwnerId, CommandContext cmd); extern void AlterLanguageOwner_oid(Oid oid, Oid newOwnerId); extern bool PLTemplateExists(const char *languageName); extern Oid get_language_oid(const char *langname, bool missing_ok); *** a/src/include/commands/schemacmds.h --- b/src/include/commands/schemacmds.h *************** *** 22,29 **** extern void CreateSchemaCommand(CreateSchemaStmt *parsetree, extern void RemoveSchemaById(Oid schemaOid); ! extern void RenameSchema(const char *oldname, const char *newname); ! extern void AlterSchemaOwner(const char *name, Oid newOwnerId); extern void AlterSchemaOwner_oid(Oid schemaOid, Oid newOwnerId); #endif /* SCHEMACMDS_H */ --- 22,29 ---- extern void RemoveSchemaById(Oid schemaOid); ! extern void RenameSchema(const char *oldname, const char *newname, CommandContext cmd); ! extern void AlterSchemaOwner(const char *name, Oid newOwnerId, CommandContext cmd); extern void AlterSchemaOwner_oid(Oid schemaOid, Oid newOwnerId); #endif /* SCHEMACMDS_H */ *** a/src/include/commands/tablecmds.h --- b/src/include/commands/tablecmds.h *************** *** 15,26 **** #define TABLECMDS_H #include "access/htup.h" #include "nodes/parsenodes.h" #include "storage/lock.h" #include "utils/relcache.h" ! extern Oid DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId); extern void RemoveRelations(DropStmt *drop); --- 15,28 ---- #define TABLECMDS_H #include "access/htup.h" + #include "commands/cmdtrigger.h" #include "nodes/parsenodes.h" #include "storage/lock.h" #include "utils/relcache.h" ! extern Oid DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ! CommandContext cmd); extern void RemoveRelations(DropStmt *drop); *************** *** 46,59 **** extern void ExecuteTruncate(TruncateStmt *stmt); extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass); ! extern void renameatt(RenameStmt *stmt); ! extern void RenameConstraint(RenameStmt *stmt); ! ! extern void RenameRelation(RenameStmt *stmt); extern void RenameRelationInternal(Oid myrelid, ! const char *newrelname); extern void find_composite_type_dependencies(Oid typeOid, Relation origRelation, --- 48,60 ---- extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass); ! extern void renameatt(RenameStmt *stmt, CommandContext cmd); ! extern void RenameRelation(RenameStmt *stmt, CommandContext cmd); ! extern void RenameConstraint(RenameStmt *stmt, CommandContext cmd); extern void RenameRelationInternal(Oid myrelid, ! const char *newrelname, CommandContext cmd); extern void find_composite_type_dependencies(Oid typeOid, Relation origRelation, *** a/src/include/commands/trigger.h --- b/src/include/commands/trigger.h *************** *** 13,18 **** --- 13,19 ---- #ifndef TRIGGER_H #define TRIGGER_H + #include "commands/cmdtrigger.h" #include "nodes/execnodes.h" #include "nodes/parsenodes.h" *************** *** 115,121 **** extern Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString, extern void RemoveTriggerById(Oid trigOid); extern Oid get_trigger_oid(Oid relid, const char *name, bool missing_ok); ! extern void renametrig(RenameStmt *stmt); extern void EnableDisableTrigger(Relation rel, const char *tgname, char fires_when, bool skip_system); --- 116,122 ---- extern void RemoveTriggerById(Oid trigOid); extern Oid get_trigger_oid(Oid relid, const char *name, bool missing_ok); ! extern void renametrig(RenameStmt *stmt, CommandContext cmd); extern void EnableDisableTrigger(Relation rel, const char *tgname, char fires_when, bool skip_system); *** a/src/include/commands/typecmds.h --- b/src/include/commands/typecmds.h *************** *** 14,50 **** #ifndef TYPECMDS_H #define TYPECMDS_H #include "nodes/parsenodes.h" #define DEFAULT_TYPDELIM ',' ! extern void DefineType(List *names, List *parameters); extern void RemoveTypeById(Oid typeOid); extern void DefineDomain(CreateDomainStmt *stmt); extern void DefineEnum(CreateEnumStmt *stmt); extern void DefineRange(CreateRangeStmt *stmt); extern void AlterEnum(AlterEnumStmt *stmt); ! extern Oid DefineCompositeType(RangeVar *typevar, List *coldeflist); extern Oid AssignTypeArrayOid(void); ! extern void AlterDomainDefault(List *names, Node *defaultRaw); ! extern void AlterDomainNotNull(List *names, bool notNull); ! extern void AlterDomainAddConstraint(List *names, Node *constr); ! extern void AlterDomainValidateConstraint(List *names, char *constrName); extern void AlterDomainDropConstraint(List *names, const char *constrName, ! DropBehavior behavior, bool missing_ok); extern List *GetDomainConstraints(Oid typeOid); ! extern void RenameType(RenameStmt *stmt); ! extern void AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype); extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId, bool hasDependEntry); ! extern void AlterTypeNamespace(List *names, const char *newschema, ObjectType objecttype); ! extern Oid AlterTypeNamespace_oid(Oid typeOid, Oid nspOid); extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, ! bool errorOnTableType); #endif /* TYPECMDS_H */ --- 14,56 ---- #ifndef TYPECMDS_H #define TYPECMDS_H + #include "commands/cmdtrigger.h" + #include "utils/lsyscache.h" #include "nodes/parsenodes.h" #define DEFAULT_TYPDELIM ',' ! extern void DefineType(List *names, List *parameters, CommandContext cmd); extern void RemoveTypeById(Oid typeOid); extern void DefineDomain(CreateDomainStmt *stmt); extern void DefineEnum(CreateEnumStmt *stmt); extern void DefineRange(CreateRangeStmt *stmt); extern void AlterEnum(AlterEnumStmt *stmt); ! extern Oid DefineCompositeType(RangeVar *typevar, List *coldeflist, CommandContext cmd); extern Oid AssignTypeArrayOid(void); ! extern void AlterDomainDefault(List *names, Node *defaultRaw, CommandContext cmd); ! extern void AlterDomainNotNull(List *names, bool notNull, CommandContext cmd); ! extern void AlterDomainAddConstraint(List *names, Node *constr, CommandContext cmd); ! extern void AlterDomainValidateConstraint(List *names, char *constrName, CommandContext cmd); extern void AlterDomainDropConstraint(List *names, const char *constrName, ! DropBehavior behavior, bool missing_ok, ! CommandContext cmd); extern List *GetDomainConstraints(Oid typeOid); ! extern void RenameType(RenameStmt *stmt, CommandContext cmd); ! extern void AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype, ! CommandContext cmd); extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId, bool hasDependEntry); ! extern void AlterTypeNamespace(List *names, const char *newschema, ! ObjectType objecttype, CommandContext cmd); ! extern Oid AlterTypeNamespace_oid(Oid typeOid, Oid nspOid, CommandContext cmd); extern Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, ! bool errorOnTableType, ! CommandContext cmd); #endif /* TYPECMDS_H */ *** a/src/include/nodes/nodes.h --- b/src/include/nodes/nodes.h *************** *** 289,294 **** typedef enum NodeTag --- 289,295 ---- T_IndexStmt, T_CreateFunctionStmt, T_AlterFunctionStmt, + T_RemoveFuncStmt, T_DoStmt, T_RenameStmt, T_RuleStmt, *************** *** 311,316 **** typedef enum NodeTag --- 312,318 ---- T_DiscardStmt, T_CreateTrigStmt, T_CreatePLangStmt, + T_DropPLangStmt, T_CreateRoleStmt, T_AlterRoleStmt, T_DropRoleStmt, *************** *** 324,332 **** typedef enum NodeTag --- 326,337 ---- T_AlterRoleSetStmt, T_CreateConversionStmt, T_CreateCastStmt, + T_DropCastStmt, T_CreateOpClassStmt, T_CreateOpFamilyStmt, T_AlterOpFamilyStmt, + T_RemoveOpClassStmt, + T_RemoveOpFamilyStmt, T_PrepareStmt, T_ExecuteStmt, T_DeallocateStmt, *************** *** 345,352 **** typedef enum NodeTag --- 350,359 ---- T_AlterTSConfigurationStmt, T_CreateFdwStmt, T_AlterFdwStmt, + T_DropFdwStmt, T_CreateForeignServerStmt, T_AlterForeignServerStmt, + T_DropForeignServerStmt, T_CreateUserMappingStmt, T_AlterUserMappingStmt, T_DropUserMappingStmt, *************** *** 356,361 **** typedef enum NodeTag --- 363,370 ---- T_CreateExtensionStmt, T_AlterExtensionStmt, T_AlterExtensionContentsStmt, + T_CreateCmdTrigStmt, + T_AlterCmdTrigStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) *************** *** 412,417 **** typedef enum NodeTag --- 421,427 ---- * pass multiple object types through the same pointer). */ T_TriggerData = 950, /* in commands/trigger.h */ + T_CommandTriggerData, /* in commands/cmdtrigger.h */ T_ReturnSetInfo, /* in nodes/execnodes.h */ T_WindowObjectData, /* private in nodeWindowAgg.c */ T_TIDBitmap, /* in nodes/tidbitmap.h */ *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 1105,1110 **** typedef enum ObjectType --- 1105,1111 ---- OBJECT_AGGREGATE, OBJECT_ATTRIBUTE, /* type's attribute, when distinct from column */ OBJECT_CAST, + OBJECT_CMDTRIGGER, OBJECT_COLUMN, OBJECT_CONSTRAINT, OBJECT_COLLATION, *************** *** 1728,1733 **** typedef struct CreateTrigStmt --- 1729,1761 ---- } CreateTrigStmt; /* ---------------------- + * Create COMMAND TRIGGER Statement + * ---------------------- + */ + typedef struct CreateCmdTrigStmt + { + NodeTag type; + char *command; /* command's name */ + char *trigname; /* TRIGGER's name */ + /* timing uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */ + char timing; /* BEFORE, AFTER */ + List *funcname; /* qual. name of function to call */ + } CreateCmdTrigStmt; + + /* ---------------------- + * Alter COMMAND TRIGGER Statement + * ---------------------- + */ + typedef struct AlterCmdTrigStmt + { + NodeTag type; + char *trigname; /* TRIGGER's name */ + char *tgenabled; /* trigger's firing configuration WRT + * session_replication_role */ + } AlterCmdTrigStmt; + + /* ---------------------- + * Create/Drop PROCEDURAL LANGUAGE Statements * Create PROCEDURAL LANGUAGE Statements * ---------------------- */ *** a/src/include/parser/kwlist.h --- b/src/include/parser/kwlist.h *************** *** 81,86 **** PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD) --- 81,87 ---- PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD) PG_KEYWORD("collation", COLLATION, RESERVED_KEYWORD) PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD) + PG_KEYWORD("command", COMMAND, UNRESERVED_KEYWORD) PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD) PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD) PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD) *** a/src/include/rewrite/rewriteDefine.h --- b/src/include/rewrite/rewriteDefine.h *************** *** 14,19 **** --- 14,20 ---- #ifndef REWRITEDEFINE_H #define REWRITEDEFINE_H + #include "commands/cmdtrigger.h" #include "nodes/parsenodes.h" #include "utils/relcache.h" *************** *** 30,36 **** extern void DefineQueryRewrite(char *rulename, CmdType event_type, bool is_instead, bool replace, ! List *action); extern void RenameRewriteRule(Oid owningRel, const char *oldName, const char *newName); --- 31,38 ---- CmdType event_type, bool is_instead, bool replace, ! List *action, ! CommandContext cmd); extern void RenameRewriteRule(Oid owningRel, const char *oldName, const char *newName); *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** *** 1012,1017 **** extern Datum pg_encoding_max_length_sql(PG_FUNCTION_ARGS); --- 1012,1018 ---- /* format_type.c */ extern Datum format_type(PG_FUNCTION_ARGS); extern char *format_type_be(Oid type_oid); + extern char *format_type_be_without_namespace(Oid type_oid); extern char *format_type_with_typemod(Oid type_oid, int32 typemod); extern Datum oidvectortypes(PG_FUNCTION_ARGS); extern int32 type_maximum_size(Oid type_oid, int32 typemod); *** a/src/pl/plperl/expected/plperl_trigger.out --- b/src/pl/plperl/expected/plperl_trigger.out *************** *** 309,311 **** $$ LANGUAGE plperl; --- 309,348 ---- SELECT direct_trigger(); ERROR: trigger functions can only be called as triggers CONTEXT: compilation of PL/Perl function "direct_trigger" + -- test plperl command triggers + create or replace function perlsnitch() returns command_trigger language plperl as $$ + elog(NOTICE, "perlsnitch: " + . $_TD->{when} . " " + . $_TD->{tag} . " " + . $_TD->{schemaname} . " " + . $_TD->{objectname}); + $$; + create command trigger perl_a_snitch after any command execute procedure perlsnitch(); + create command trigger perl_b_snitch before any command execute procedure perlsnitch(); + create or replace function foobar() returns int language sql as $$select 1;$$; + NOTICE: perlsnitch: BEFORE CREATE FUNCTION public foobar + CONTEXT: PL/Perl function "perlsnitch" + NOTICE: perlsnitch: AFTER CREATE FUNCTION public foobar + CONTEXT: PL/Perl function "perlsnitch" + alter function foobar() cost 77; + NOTICE: perlsnitch: BEFORE ALTER FUNCTION public foobar + CONTEXT: PL/Perl function "perlsnitch" + NOTICE: perlsnitch: AFTER ALTER FUNCTION public foobar + CONTEXT: PL/Perl function "perlsnitch" + drop function foobar(); + NOTICE: perlsnitch: BEFORE DROP FUNCTION public foobar + CONTEXT: PL/Perl function "perlsnitch" + NOTICE: perlsnitch: AFTER DROP FUNCTION public foobar + CONTEXT: PL/Perl function "perlsnitch" + create table foo(); + NOTICE: perlsnitch: BEFORE CREATE TABLE public foo + CONTEXT: PL/Perl function "perlsnitch" + NOTICE: perlsnitch: AFTER CREATE TABLE public foo + CONTEXT: PL/Perl function "perlsnitch" + drop table foo; + NOTICE: perlsnitch: BEFORE DROP TABLE public foo + CONTEXT: PL/Perl function "perlsnitch" + NOTICE: perlsnitch: AFTER DROP TABLE public foo + CONTEXT: PL/Perl function "perlsnitch" + drop command trigger perl_a_snitch; + drop command trigger perl_b_snitch; *** a/src/pl/plperl/plperl.c --- b/src/pl/plperl/plperl.c *************** *** 234,241 **** static void set_interp_require(bool trusted); static Datum plperl_func_handler(PG_FUNCTION_ARGS); static Datum plperl_trigger_handler(PG_FUNCTION_ARGS); ! static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger); static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc); static SV *plperl_hash_from_datum(Datum attr); --- 234,244 ---- static Datum plperl_func_handler(PG_FUNCTION_ARGS); static Datum plperl_trigger_handler(PG_FUNCTION_ARGS); + static void plperl_command_trigger_handler(PG_FUNCTION_ARGS); ! static plperl_proc_desc *compile_plperl_function(Oid fn_oid, ! bool is_dml_trigger, ! bool is_cmd_trigger); static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc); static SV *plperl_hash_from_datum(Datum attr); *************** *** 1567,1572 **** plperl_trigger_build_args(FunctionCallInfo fcinfo) --- 1570,1604 ---- } + /* Set up the arguments for a command trigger call. */ + static SV * + plperl_command_trigger_build_args(FunctionCallInfo fcinfo) + { + CommandTriggerData *tdata; + char *objectid; + HV *hv; + + hv = newHV(); + hv_ksplit(hv, 12); /* pre-grow the hash */ + + tdata = (CommandTriggerData *) fcinfo->context; + + hv_store_string(hv, "when", cstr2sv(tdata->when)); + hv_store_string(hv, "tag", cstr2sv(tdata->tag)); + + if (tdata->objectId == InvalidOid) + objectid = pstrdup("NULL"); + else + objectid = DatumGetCString( + DirectFunctionCall1(oidout, ObjectIdGetDatum(tdata->objectId))); + hv_store_string(hv, "objectid", cstr2sv(objectid)); + + hv_store_string(hv, "objectname", cstr2sv(tdata->objectname == NULL ? "NULL" : tdata->objectname)); + hv_store_string(hv, "schemaname", cstr2sv(tdata->schemaname == NULL ? "NULL" : tdata->schemaname)); + + return newRV_noinc((SV *) hv); + } + /* Set up the new tuple returned from a trigger. */ static HeapTuple *************** *** 1668,1673 **** plperl_call_handler(PG_FUNCTION_ARGS) --- 1700,1707 ---- { if (CALLED_AS_TRIGGER(fcinfo)) retval = PointerGetDatum(plperl_trigger_handler(fcinfo)); + else if (CALLED_AS_COMMAND_TRIGGER(fcinfo)) + plperl_command_trigger_handler(fcinfo); else retval = plperl_func_handler(fcinfo); } *************** *** 1794,1800 **** plperl_validator(PG_FUNCTION_ARGS) Oid *argtypes; char **argnames; char *argmodes; ! bool istrigger = false; int i; /* Get the new function's pg_proc entry */ --- 1828,1834 ---- Oid *argtypes; char **argnames; char *argmodes; ! bool is_dml_trigger = false, is_cmd_trigger = false; int i; /* Get the new function's pg_proc entry */ *************** *** 1806,1818 **** plperl_validator(PG_FUNCTION_ARGS) functyptype = get_typtype(proc->prorettype); /* Disallow pseudotype result */ ! /* except for TRIGGER, RECORD, or VOID */ if (functyptype == TYPTYPE_PSEUDO) { /* we assume OPAQUE with no arguments means a trigger */ if (proc->prorettype == TRIGGEROID || (proc->prorettype == OPAQUEOID && proc->pronargs == 0)) ! istrigger = true; else if (proc->prorettype != RECORDOID && proc->prorettype != VOIDOID) ereport(ERROR, --- 1840,1854 ---- functyptype = get_typtype(proc->prorettype); /* Disallow pseudotype result */ ! /* except for TRIGGER, CMDTRIGGER, RECORD, or VOID */ if (functyptype == TYPTYPE_PSEUDO) { /* we assume OPAQUE with no arguments means a trigger */ if (proc->prorettype == TRIGGEROID || (proc->prorettype == OPAQUEOID && proc->pronargs == 0)) ! is_dml_trigger = true; ! else if (proc->prorettype == CMDTRIGGEROID) ! is_cmd_trigger = true; else if (proc->prorettype != RECORDOID && proc->prorettype != VOIDOID) ereport(ERROR, *************** *** 1839,1845 **** plperl_validator(PG_FUNCTION_ARGS) /* Postpone body checks if !check_function_bodies */ if (check_function_bodies) { ! (void) compile_plperl_function(funcoid, istrigger); } /* the result of a validator is ignored */ --- 1875,1881 ---- /* Postpone body checks if !check_function_bodies */ if (check_function_bodies) { ! (void) compile_plperl_function(funcoid, is_dml_trigger, is_cmd_trigger); } /* the result of a validator is ignored */ *************** *** 2110,2115 **** plperl_call_perl_trigger_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo, --- 2146,2207 ---- } + static void + plperl_call_perl_command_trigger_func(plperl_proc_desc *desc, + FunctionCallInfo fcinfo, + SV *td) + { + dSP; + SV *retval, + *TDsv; + int count; + + ENTER; + SAVETMPS; + + TDsv = get_sv("main::_TD", 0); + if (!TDsv) + elog(ERROR, "couldn't fetch $_TD"); + + save_item(TDsv); /* local $_TD */ + sv_setsv(TDsv, td); + + PUSHMARK(sp); + PUTBACK; + + /* Do NOT use G_KEEPERR here */ + count = perl_call_sv(desc->reference, G_SCALAR | G_EVAL); + + SPAGAIN; + + if (count != 1) + { + PUTBACK; + FREETMPS; + LEAVE; + elog(ERROR, "didn't get a return item from trigger function"); + } + + if (SvTRUE(ERRSV)) + { + (void) POPs; + PUTBACK; + FREETMPS; + LEAVE; + /* XXX need to find a way to assign an errcode here */ + ereport(ERROR, + (errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV))))); + } + + retval = newSVsv(POPs); + + PUTBACK; + FREETMPS; + LEAVE; + + return; + } + static Datum plperl_func_handler(PG_FUNCTION_ARGS) { *************** *** 2129,2135 **** plperl_func_handler(PG_FUNCTION_ARGS) if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "could not connect to SPI manager"); ! prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false); current_call_data->prodesc = prodesc; /* Set a callback for error reporting */ --- 2221,2227 ---- if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "could not connect to SPI manager"); ! prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false, false); current_call_data->prodesc = prodesc; /* Set a callback for error reporting */ *************** *** 2249,2255 **** plperl_trigger_handler(PG_FUNCTION_ARGS) elog(ERROR, "could not connect to SPI manager"); /* Find or compile the function */ ! prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true); current_call_data->prodesc = prodesc; /* Set a callback for error reporting */ --- 2341,2347 ---- elog(ERROR, "could not connect to SPI manager"); /* Find or compile the function */ ! prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true, false); current_call_data->prodesc = prodesc; /* Set a callback for error reporting */ *************** *** 2339,2344 **** plperl_trigger_handler(PG_FUNCTION_ARGS) --- 2431,2481 ---- } + static void + plperl_command_trigger_handler(PG_FUNCTION_ARGS) + { + plperl_proc_desc *prodesc; + SV *svTD; + ErrorContextCallback pl_error_context; + + /* + * Create the call_data before connecting to SPI, so that it is not + * allocated in the SPI memory context + */ + current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data)); + current_call_data->fcinfo = fcinfo; + + /* Connect to SPI manager */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "could not connect to SPI manager"); + + /* Find or compile the function */ + prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false, true); + current_call_data->prodesc = prodesc; + + /* Set a callback for error reporting */ + pl_error_context.callback = plperl_exec_callback; + pl_error_context.previous = error_context_stack; + pl_error_context.arg = prodesc->proname; + error_context_stack = &pl_error_context; + + activate_interpreter(prodesc->interp); + + svTD = plperl_command_trigger_build_args(fcinfo); + plperl_call_perl_command_trigger_func(prodesc, fcinfo, svTD); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish() failed"); + + /* Restore the previous error callback */ + error_context_stack = pl_error_context.previous; + + SvREFCNT_dec(svTD); + + return; + } + + static bool validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup) { *************** *** 2378,2384 **** validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup) static plperl_proc_desc * ! compile_plperl_function(Oid fn_oid, bool is_trigger) { HeapTuple procTup; Form_pg_proc procStruct; --- 2515,2521 ---- static plperl_proc_desc * ! compile_plperl_function(Oid fn_oid, bool is_dml_trigger, bool is_cmd_trigger) { HeapTuple procTup; Form_pg_proc procStruct; *************** *** 2403,2409 **** compile_plperl_function(Oid fn_oid, bool is_trigger) /* Try to find function in plperl_proc_hash */ proc_key.proc_id = fn_oid; ! proc_key.is_trigger = is_trigger; proc_key.user_id = GetUserId(); proc_ptr = hash_search(plperl_proc_hash, &proc_key, --- 2540,2546 ---- /* Try to find function in plperl_proc_hash */ proc_key.proc_id = fn_oid; ! proc_key.is_trigger = is_dml_trigger; proc_key.user_id = GetUserId(); proc_ptr = hash_search(plperl_proc_hash, &proc_key, *************** *** 2480,2486 **** compile_plperl_function(Oid fn_oid, bool is_trigger) * Get the required information for input conversion of the * return value. ************************************************************/ ! if (!is_trigger) { typeTup = SearchSysCache1(TYPEOID, --- 2617,2623 ---- * Get the required information for input conversion of the * return value. ************************************************************/ ! if (!is_dml_trigger && !is_cmd_trigger) { typeTup = SearchSysCache1(TYPEOID, *************** *** 2538,2544 **** compile_plperl_function(Oid fn_oid, bool is_trigger) * Get the required information for output conversion * of all procedure arguments ************************************************************/ ! if (!is_trigger) { prodesc->nargs = procStruct->pronargs; for (i = 0; i < prodesc->nargs; i++) --- 2675,2681 ---- * Get the required information for output conversion * of all procedure arguments ************************************************************/ ! if (!is_dml_trigger && !is_cmd_trigger) { prodesc->nargs = procStruct->pronargs; for (i = 0; i < prodesc->nargs; i++) *** a/src/pl/plperl/sql/plperl_trigger.sql --- b/src/pl/plperl/sql/plperl_trigger.sql *************** *** 169,171 **** CREATE FUNCTION direct_trigger() RETURNS trigger AS $$ --- 169,193 ---- $$ LANGUAGE plperl; SELECT direct_trigger(); + + -- test plperl command triggers + create or replace function perlsnitch() returns command_trigger language plperl as $$ + elog(NOTICE, "perlsnitch: " + . $_TD->{when} . " " + . $_TD->{tag} . " " + . $_TD->{schemaname} . " " + . $_TD->{objectname}); + $$; + + create command trigger perl_a_snitch after any command execute procedure perlsnitch(); + create command trigger perl_b_snitch before any command execute procedure perlsnitch(); + + create or replace function foobar() returns int language sql as $$select 1;$$; + alter function foobar() cost 77; + drop function foobar(); + + create table foo(); + drop table foo; + + drop command trigger perl_a_snitch; + drop command trigger perl_b_snitch; *** a/src/pl/plpgsql/src/pl_comp.c --- b/src/pl/plpgsql/src/pl_comp.c *************** *** 263,269 **** do_compile(FunctionCallInfo fcinfo, bool forValidator) { Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup); ! bool is_trigger = CALLED_AS_TRIGGER(fcinfo); Datum prosrcdatum; bool isnull; char *proc_source; --- 263,270 ---- bool forValidator) { Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup); ! bool is_dml_trigger = CALLED_AS_TRIGGER(fcinfo); ! bool is_cmd_trigger = CALLED_AS_COMMAND_TRIGGER(fcinfo); Datum prosrcdatum; bool isnull; char *proc_source; *************** *** 345,356 **** do_compile(FunctionCallInfo fcinfo, function->fn_oid = fcinfo->flinfo->fn_oid; function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); function->fn_tid = procTup->t_self; - function->fn_is_trigger = is_trigger; function->fn_input_collation = fcinfo->fncollation; function->fn_cxt = func_cxt; function->out_param_varno = -1; /* set up for no OUT param */ function->resolve_option = plpgsql_variable_conflict; /* * Initialize the compiler, particularly the namespace stack. The * outermost namespace contains function parameters and other special --- 346,363 ---- function->fn_oid = fcinfo->flinfo->fn_oid; function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); function->fn_tid = procTup->t_self; function->fn_input_collation = fcinfo->fncollation; function->fn_cxt = func_cxt; function->out_param_varno = -1; /* set up for no OUT param */ function->resolve_option = plpgsql_variable_conflict; + if (is_dml_trigger) + function->fn_is_trigger = PLPGSQL_DML_TRIGGER; + else if (is_cmd_trigger) + function->fn_is_trigger = PLPGSQL_CMD_TRIGGER; + else + function->fn_is_trigger = PLPGSQL_NOT_TRIGGER; + /* * Initialize the compiler, particularly the namespace stack. The * outermost namespace contains function parameters and other special *************** *** 367,375 **** do_compile(FunctionCallInfo fcinfo, sizeof(PLpgSQL_datum *) * datums_alloc); datums_last = 0; ! switch (is_trigger) { ! case false: /* * Fetch info about the procedure's parameters. Allocations aren't --- 374,382 ---- sizeof(PLpgSQL_datum *) * datums_alloc); datums_last = 0; ! switch (function->fn_is_trigger) { ! case PLPGSQL_NOT_TRIGGER: /* * Fetch info about the procedure's parameters. Allocations aren't *************** *** 568,574 **** do_compile(FunctionCallInfo fcinfo, ReleaseSysCache(typeTup); break; ! case true: /* Trigger procedure's return type is unknown yet */ function->fn_rettype = InvalidOid; function->fn_retbyval = false; --- 575,581 ---- ReleaseSysCache(typeTup); break; ! case PLPGSQL_DML_TRIGGER: /* Trigger procedure's return type is unknown yet */ function->fn_rettype = InvalidOid; function->fn_retbyval = false; *************** *** 672,679 **** do_compile(FunctionCallInfo fcinfo, break; default: ! elog(ERROR, "unrecognized function typecode: %d", (int) is_trigger); break; } --- 679,741 ---- break; + case PLPGSQL_CMD_TRIGGER: + function->fn_rettype = VOIDOID; + function->fn_retbyval = false; + function->fn_retistuple = true; + function->fn_retset = false; + + /* shouldn't be any declared arguments */ + if (procStruct->pronargs != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("command trigger functions cannot have declared arguments"))); + + /* Add the variable tg_when */ + var = plpgsql_build_variable("tg_when", 0, + plpgsql_build_datatype(TEXTOID, + -1, + function->fn_input_collation), + true); + function->tg_when_varno = var->dno; + + /* Add the variable tg_tag */ + var = plpgsql_build_variable("tg_tag", 0, + plpgsql_build_datatype(TEXTOID, + -1, + function->fn_input_collation), + true); + function->tg_tag_varno = var->dno; + + /* Add the variable tg_objectid */ + var = plpgsql_build_variable("tg_objectid", 0, + plpgsql_build_datatype(OIDOID, + -1, + InvalidOid), + true); + function->tg_objectid_varno = var->dno; + + /* Add the variable tg_schemaname */ + var = plpgsql_build_variable("tg_schemaname", 0, + plpgsql_build_datatype(NAMEOID, + -1, + InvalidOid), + true); + function->tg_schemaname_varno = var->dno; + + /* Add the variable tg_objectname */ + var = plpgsql_build_variable("tg_objectname", 0, + plpgsql_build_datatype(NAMEOID, + -1, + InvalidOid), + true); + function->tg_objectname_varno = var->dno; + + break; + default: ! elog(ERROR, "unrecognized function typecode: %d", ! (int) function->fn_is_trigger); break; } *************** *** 803,809 **** plpgsql_compile_inline(char *proc_source) compile_tmp_cxt = MemoryContextSwitchTo(func_cxt); function->fn_signature = pstrdup(func_name); ! function->fn_is_trigger = false; function->fn_input_collation = InvalidOid; function->fn_cxt = func_cxt; function->out_param_varno = -1; /* set up for no OUT param */ --- 865,871 ---- compile_tmp_cxt = MemoryContextSwitchTo(func_cxt); function->fn_signature = pstrdup(func_name); ! function->fn_is_trigger = PLPGSQL_NOT_TRIGGER; function->fn_input_collation = InvalidOid; function->fn_cxt = func_cxt; function->out_param_varno = -1; /* set up for no OUT param */ *** a/src/pl/plpgsql/src/pl_exec.c --- b/src/pl/plpgsql/src/pl_exec.c *************** *** 773,778 **** plpgsql_exec_trigger(PLpgSQL_function *func, --- 773,909 ---- return rettup; } + void plpgsql_exec_command_trigger(PLpgSQL_function *func, + CommandTriggerData *trigdata) + { + PLpgSQL_execstate estate; + ErrorContextCallback plerrcontext; + int i; + int rc; + PLpgSQL_var *var; + + /* + * Setup the execution state + */ + plpgsql_estate_setup(&estate, func, NULL); + + /* + * Setup error traceback support for ereport() + */ + plerrcontext.callback = plpgsql_exec_error_callback; + plerrcontext.arg = &estate; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + /* + * Make local execution copies of all the datums + */ + estate.err_text = gettext_noop("during initialization of execution state"); + for (i = 0; i < estate.ndatums; i++) + estate.datums[i] = copy_plpgsql_datum(func->datums[i]); + + /* + * Assign the special tg_ variables + */ + var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]); + var->value = CStringGetTextDatum(trigdata->when); + var->isnull = false; + var->freeval = true; + + var = (PLpgSQL_var *) (estate.datums[func->tg_tag_varno]); + var->value = CStringGetTextDatum(trigdata->tag); + var->isnull = false; + var->freeval = true; + + var = (PLpgSQL_var *) (estate.datums[func->tg_objectid_varno]); + if (trigdata->objectId == InvalidOid) + { + var->isnull = true; + } + else + { + var->value = ObjectIdGetDatum(trigdata->objectId); + var->isnull = false; + } + var->freeval = false; + + var = (PLpgSQL_var *) (estate.datums[func->tg_schemaname_varno]); + if (trigdata->schemaname == NULL) + { + var->isnull = true; + } + else + { + var->value = DirectFunctionCall1(namein, + CStringGetDatum(trigdata->schemaname)); + var->isnull = false; + } + var->freeval = true; + + var = (PLpgSQL_var *) (estate.datums[func->tg_objectname_varno]); + if (trigdata->objectname == NULL) + { + var->isnull = true; + } + else + { + var->value = DirectFunctionCall1(namein, + CStringGetDatum(trigdata->objectname)); + var->isnull = false; + } + var->freeval = true; + + /* + * Let the instrumentation plugin peek at this function + */ + if (*plugin_ptr && (*plugin_ptr)->func_beg) + ((*plugin_ptr)->func_beg) (&estate, func); + + /* + * Now call the toplevel block of statements + */ + estate.err_text = NULL; + estate.err_stmt = (PLpgSQL_stmt *) (func->action); + rc = exec_stmt_block(&estate, func->action); + if (rc != PLPGSQL_RC_RETURN) + { + estate.err_stmt = NULL; + estate.err_text = NULL; + + /* + * Provide a more helpful message if a CONTINUE or RAISE has been used + * outside the context it can work in. + */ + if (rc == PLPGSQL_RC_CONTINUE) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("CONTINUE cannot be used outside a loop"))); + else + ereport(ERROR, + (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT), + errmsg("control reached end of trigger procedure without RETURN"))); + } + + estate.err_stmt = NULL; + estate.err_text = gettext_noop("during function exit"); + + /* + * Let the instrumentation plugin peek at this function + */ + if (*plugin_ptr && (*plugin_ptr)->func_end) + ((*plugin_ptr)->func_end) (&estate, func); + + /* Clean up any leftover temporary memory */ + plpgsql_destroy_econtext(&estate); + exec_eval_cleanup(&estate); + + /* + * Pop the error context stack + */ + error_context_stack = plerrcontext.previous; + + return; + } /* * error context callback to let us supply a call-stack traceback *** a/src/pl/plpgsql/src/pl_handler.c --- b/src/pl/plpgsql/src/pl_handler.c *************** *** 118,123 **** plpgsql_call_handler(PG_FUNCTION_ARGS) --- 118,126 ---- if (CALLED_AS_TRIGGER(fcinfo)) retval = PointerGetDatum(plpgsql_exec_trigger(func, (TriggerData *) fcinfo->context)); + else if (CALLED_AS_COMMAND_TRIGGER(fcinfo)) + plpgsql_exec_command_trigger(func, + (CommandTriggerData *) fcinfo->context); else retval = plpgsql_exec_function(func, fcinfo); } *************** *** 224,230 **** plpgsql_validator(PG_FUNCTION_ARGS) Oid *argtypes; char **argnames; char *argmodes; ! bool istrigger = false; int i; /* Get the new function's pg_proc entry */ --- 227,234 ---- Oid *argtypes; char **argnames; char *argmodes; ! bool is_dml_trigger = false; ! bool is_cmd_trigger = false; int i; /* Get the new function's pg_proc entry */ *************** *** 242,248 **** plpgsql_validator(PG_FUNCTION_ARGS) /* we assume OPAQUE with no arguments means a trigger */ if (proc->prorettype == TRIGGEROID || (proc->prorettype == OPAQUEOID && proc->pronargs == 0)) ! istrigger = true; else if (proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && !IsPolymorphicType(proc->prorettype)) --- 246,254 ---- /* we assume OPAQUE with no arguments means a trigger */ if (proc->prorettype == TRIGGEROID || (proc->prorettype == OPAQUEOID && proc->pronargs == 0)) ! is_dml_trigger = true; ! else if (proc->prorettype == CMDTRIGGEROID) ! is_cmd_trigger = true; else if (proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && !IsPolymorphicType(proc->prorettype)) *************** *** 273,279 **** plpgsql_validator(PG_FUNCTION_ARGS) { FunctionCallInfoData fake_fcinfo; FmgrInfo flinfo; - TriggerData trigdata; int rc; /* --- 279,284 ---- *************** *** 291,302 **** plpgsql_validator(PG_FUNCTION_ARGS) fake_fcinfo.flinfo = &flinfo; flinfo.fn_oid = funcoid; flinfo.fn_mcxt = CurrentMemoryContext; ! if (istrigger) { MemSet(&trigdata, 0, sizeof(trigdata)); trigdata.type = T_TriggerData; fake_fcinfo.context = (Node *) &trigdata; } /* Test-compile the function */ plpgsql_compile(&fake_fcinfo, true); --- 296,315 ---- fake_fcinfo.flinfo = &flinfo; flinfo.fn_oid = funcoid; flinfo.fn_mcxt = CurrentMemoryContext; ! if (is_dml_trigger) { + TriggerData trigdata; MemSet(&trigdata, 0, sizeof(trigdata)); trigdata.type = T_TriggerData; fake_fcinfo.context = (Node *) &trigdata; } + else if (is_cmd_trigger) + { + CommandTriggerData trigdata; + MemSet(&trigdata, 0, sizeof(trigdata)); + trigdata.type = T_CommandTriggerData; + fake_fcinfo.context = (Node *) &trigdata; + } /* Test-compile the function */ plpgsql_compile(&fake_fcinfo, true); *** a/src/pl/plpgsql/src/plpgsql.h --- b/src/pl/plpgsql/src/plpgsql.h *************** *** 20,25 **** --- 20,26 ---- #include "access/xact.h" #include "commands/trigger.h" + #include "commands/cmdtrigger.h" #include "executor/spi.h" /********************************************************************** *************** *** 675,680 **** typedef struct PLpgSQL_func_hashkey --- 676,687 ---- Oid argtypes[FUNC_MAX_ARGS]; } PLpgSQL_func_hashkey; + typedef enum PLpgSQL_trigtype + { + PLPGSQL_DML_TRIGGER, + PLPGSQL_CMD_TRIGGER, + PLPGSQL_NOT_TRIGGER + } PLpgSQL_trigtype; typedef struct PLpgSQL_function { /* Complete compiled function */ *************** *** 682,688 **** typedef struct PLpgSQL_function Oid fn_oid; TransactionId fn_xmin; ItemPointerData fn_tid; ! bool fn_is_trigger; Oid fn_input_collation; PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */ MemoryContext fn_cxt; --- 689,695 ---- Oid fn_oid; TransactionId fn_xmin; ItemPointerData fn_tid; ! PLpgSQL_trigtype fn_is_trigger; Oid fn_input_collation; PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */ MemoryContext fn_cxt; *************** *** 713,718 **** typedef struct PLpgSQL_function --- 720,730 ---- int tg_nargs_varno; int tg_argv_varno; + int tg_tag_varno; + int tg_objectid_varno; + int tg_schemaname_varno; + int tg_objectname_varno; + PLpgSQL_resolve_option resolve_option; int ndatums; *************** *** 920,925 **** extern Datum plpgsql_exec_function(PLpgSQL_function *func, --- 932,939 ---- FunctionCallInfo fcinfo); extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func, TriggerData *trigdata); + extern void plpgsql_exec_command_trigger(PLpgSQL_function *func, + CommandTriggerData *trigdata); extern void plpgsql_xact_cb(XactEvent event, void *arg); extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void *arg); *** a/src/pl/plpython/expected/plpython_trigger.out --- b/src/pl/plpython/expected/plpython_trigger.out *************** *** 567,569 **** SELECT * FROM composite_trigger_test; --- 567,596 ---- (3,f) | (7,t) (1 row) + -- test plpython command triggers + create or replace function pysnitch() returns command_trigger language plpythonu as $$ + plpy.notice(" pysnitch: %s %s %s.%s [%s]" % + (TD["when"], TD["tag"], TD["schemaname"], TD["objectname"])); + $$; + create command trigger py_a_snitch after any command execute procedure pysnitch(); + create command trigger py_b_snitch before any command execute procedure pysnitch(); + create or replace function foobar() returns int language sql as $$select 1;$$; + ERROR: TypeError: not enough arguments for format string + CONTEXT: Traceback (most recent call last): + PL/Python function "pysnitch", line 3, in + (TD["when"], TD["tag"], TD["schemaname"], TD["objectname"])); + PL/Python function "pysnitch" + alter function foobar() cost 77; + ERROR: function foobar() does not exist + drop function foobar(); + ERROR: function foobar() does not exist + create table foo(); + ERROR: TypeError: not enough arguments for format string + CONTEXT: Traceback (most recent call last): + PL/Python function "pysnitch", line 3, in + (TD["when"], TD["tag"], TD["schemaname"], TD["objectname"])); + PL/Python function "pysnitch" + drop table foo; + ERROR: table "foo" does not exist + drop command trigger py_a_snitch; + drop command trigger py_b_snitch; *** a/src/pl/plpython/plpy_exec.c --- b/src/pl/plpython/plpy_exec.c *************** *** 342,347 **** PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc) --- 342,420 ---- return rv; } + /* command trigger handler + */ + void + PLy_exec_command_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc) + { + CommandTriggerData *tdata; + + Assert(CALLED_AS_COMMAND_TRIGGER(fcinfo)); + + tdata = (CommandTriggerData *) fcinfo->context; + + PG_TRY(); + { + /* build command trigger args */ + PyObject *pltwhen, + *plttag, + *pltobjectid, + *pltschemaname, + *pltobjectname; + PyObject *volatile pltdata = NULL; + char *stroid; + + pltdata = PyDict_New(); + if (!pltdata) + PLy_elog(ERROR, "could not create new dictionary while building command trigger arguments"); + + pltwhen = PyString_FromString(tdata->when); + PyDict_SetItemString(pltdata, "when", pltwhen); + Py_DECREF(pltwhen); + + plttag = PyString_FromString(tdata->tag); + PyDict_SetItemString(pltdata, "tag", plttag); + Py_DECREF(plttag); + + if (tdata->objectId == InvalidOid) + stroid = pstrdup("NULL"); + else + stroid = DatumGetCString( + DirectFunctionCall1(oidout, ObjectIdGetDatum(tdata->objectId))); + pltobjectid = PyString_FromString(stroid); + PyDict_SetItemString(pltdata, "objectId", pltobjectid); + Py_DECREF(pltobjectid); + pfree(stroid); + + pltobjectname = PyString_FromString( + tdata->objectname == NULL ? "NULL" : tdata->objectname); + PyDict_SetItemString(pltdata, "objectname", pltobjectname); + Py_DECREF(pltobjectname); + + pltschemaname = PyString_FromString( + tdata->schemaname == NULL ? "NULL" : tdata->schemaname); + PyDict_SetItemString(pltdata, "schemaname", pltschemaname); + Py_DECREF(pltschemaname); + + /* now call the procedure */ + PLy_procedure_call(proc, "TD", pltdata); + + /* + * Disconnect from SPI manager + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + } + PG_CATCH(); + { + PG_RE_THROW(); + } + PG_END_TRY(); + + return; + } + /* helper functions for Python code execution */ static PyObject * *** a/src/pl/plpython/plpy_exec.h --- b/src/pl/plpython/plpy_exec.h *************** *** 9,13 **** --- 9,14 ---- extern Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc); extern HeapTuple PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc); + extern void PLy_exec_command_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc); #endif /* PLPY_EXEC_H */ *** a/src/pl/plpython/plpy_main.c --- b/src/pl/plpython/plpy_main.c *************** *** 63,68 **** PG_FUNCTION_INFO_V1(plpython2_inline_handler); --- 63,69 ---- static bool PLy_procedure_is_trigger(Form_pg_proc procStruct); + static bool PLy_procedure_is_command_trigger(Form_pg_proc procStruct); static void plpython_error_callback(void *arg); static void plpython_inline_error_callback(void *arg); static void PLy_init_interp(void); *************** *** 156,162 **** plpython_validator(PG_FUNCTION_ARGS) Oid funcoid = PG_GETARG_OID(0); HeapTuple tuple; Form_pg_proc procStruct; ! bool is_trigger; if (!check_function_bodies) { --- 157,163 ---- Oid funcoid = PG_GETARG_OID(0); HeapTuple tuple; Form_pg_proc procStruct; ! bool is_dml_trigger, is_cmd_trigger; if (!check_function_bodies) { *************** *** 169,179 **** plpython_validator(PG_FUNCTION_ARGS) elog(ERROR, "cache lookup failed for function %u", funcoid); procStruct = (Form_pg_proc) GETSTRUCT(tuple); ! is_trigger = PLy_procedure_is_trigger(procStruct); ReleaseSysCache(tuple); ! PLy_procedure_get(funcoid, is_trigger); PG_RETURN_VOID(); } --- 170,181 ---- elog(ERROR, "cache lookup failed for function %u", funcoid); procStruct = (Form_pg_proc) GETSTRUCT(tuple); ! is_dml_trigger = PLy_procedure_is_trigger(procStruct); ! is_cmd_trigger = PLy_procedure_is_command_trigger(procStruct); ReleaseSysCache(tuple); ! PLy_procedure_get(funcoid, is_dml_trigger, is_cmd_trigger); PG_RETURN_VOID(); } *************** *** 220,233 **** plpython_call_handler(PG_FUNCTION_ARGS) { HeapTuple trv; ! proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true); exec_ctx->curr_proc = proc; trv = PLy_exec_trigger(fcinfo, proc); retval = PointerGetDatum(trv); } else { ! proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false); exec_ctx->curr_proc = proc; retval = PLy_exec_function(fcinfo, proc); } --- 222,241 ---- { HeapTuple trv; ! proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true, false); exec_ctx->curr_proc = proc; trv = PLy_exec_trigger(fcinfo, proc); retval = PointerGetDatum(trv); } + else if (CALLED_AS_COMMAND_TRIGGER(fcinfo)) + { + proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false, true); + exec_ctx->curr_proc = proc; + PLy_exec_command_trigger(fcinfo, proc); + } else { ! proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false, false); exec_ctx->curr_proc = proc; retval = PLy_exec_function(fcinfo, proc); } *************** *** 337,342 **** static bool PLy_procedure_is_trigger(Form_pg_proc procStruct) --- 345,355 ---- procStruct->pronargs == 0)); } + static bool PLy_procedure_is_command_trigger(Form_pg_proc procStruct) + { + return (procStruct->prorettype == CMDTRIGGEROID); + } + static void plpython_error_callback(void *arg) { *** a/src/pl/plpython/plpy_procedure.c --- b/src/pl/plpython/plpy_procedure.c *************** *** 25,31 **** static HTAB *PLy_procedure_cache = NULL; static HTAB *PLy_trigger_cache = NULL; ! static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger); static bool PLy_procedure_argument_valid(PLyTypeInfo *arg); static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup); static char *PLy_procedure_munge_source(const char *name, const char *src); --- 25,32 ---- static HTAB *PLy_procedure_cache = NULL; static HTAB *PLy_trigger_cache = NULL; ! static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, ! bool is_dml_trigger, bool is_cmd_trigger); static bool PLy_procedure_argument_valid(PLyTypeInfo *arg); static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup); static char *PLy_procedure_munge_source(const char *name, const char *src); *************** *** 73,79 **** PLy_procedure_name(PLyProcedure *proc) * function calls. */ PLyProcedure * ! PLy_procedure_get(Oid fn_oid, bool is_trigger) { HeapTuple procTup; PLyProcedureEntry *volatile entry; --- 74,80 ---- * function calls. */ PLyProcedure * ! PLy_procedure_get(Oid fn_oid, bool is_dml_trigger, bool is_cmd_trigger) { HeapTuple procTup; PLyProcedureEntry *volatile entry; *************** *** 84,90 **** PLy_procedure_get(Oid fn_oid, bool is_trigger) elog(ERROR, "cache lookup failed for function %u", fn_oid); /* Look for the function in the corresponding cache */ ! if (is_trigger) entry = hash_search(PLy_trigger_cache, &fn_oid, HASH_ENTER, &found); else --- 85,91 ---- elog(ERROR, "cache lookup failed for function %u", fn_oid); /* Look for the function in the corresponding cache */ ! if (is_dml_trigger || is_cmd_trigger) entry = hash_search(PLy_trigger_cache, &fn_oid, HASH_ENTER, &found); else *************** *** 96,116 **** PLy_procedure_get(Oid fn_oid, bool is_trigger) if (!found) { /* Haven't found it, create a new cache entry */ ! entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger); } else if (!PLy_procedure_valid(entry->proc, procTup)) { /* Found it, but it's invalid, free and reuse the cache entry */ PLy_procedure_delete(entry->proc); PLy_free(entry->proc); ! entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger); } /* Found it and it's valid, it's fine to use it */ } PG_CATCH(); { /* Do not leave an uninitialised entry in the cache */ ! if (is_trigger) hash_search(PLy_trigger_cache, &fn_oid, HASH_REMOVE, NULL); else --- 97,119 ---- if (!found) { /* Haven't found it, create a new cache entry */ ! entry->proc = PLy_procedure_create(procTup, fn_oid, ! is_dml_trigger, is_cmd_trigger); } else if (!PLy_procedure_valid(entry->proc, procTup)) { /* Found it, but it's invalid, free and reuse the cache entry */ PLy_procedure_delete(entry->proc); PLy_free(entry->proc); ! entry->proc = PLy_procedure_create(procTup, fn_oid, ! is_dml_trigger, is_cmd_trigger); } /* Found it and it's valid, it's fine to use it */ } PG_CATCH(); { /* Do not leave an uninitialised entry in the cache */ ! if (is_dml_trigger || is_cmd_trigger) hash_search(PLy_trigger_cache, &fn_oid, HASH_REMOVE, NULL); else *************** *** 129,135 **** PLy_procedure_get(Oid fn_oid, bool is_trigger) * Create a new PLyProcedure structure */ static PLyProcedure * ! PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) { char procName[NAMEDATALEN + 256]; Form_pg_proc procStruct; --- 132,139 ---- * Create a new PLyProcedure structure */ static PLyProcedure * ! PLy_procedure_create(HeapTuple procTup, Oid fn_oid, ! bool is_dml_trigger, bool is_cmd_trigger) { char procName[NAMEDATALEN + 256]; Form_pg_proc procStruct; *************** *** 173,179 **** PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) * get information required for output conversion of the return value, * but only if this isn't a trigger. */ ! if (!is_trigger) { HeapTuple rvTypeTup; Form_pg_type rvTypeStruct; --- 177,183 ---- * get information required for output conversion of the return value, * but only if this isn't a trigger. */ ! if (!is_dml_trigger && !is_cmd_trigger) { HeapTuple rvTypeTup; Form_pg_type rvTypeStruct; *** a/src/pl/plpython/plpy_procedure.h --- b/src/pl/plpython/plpy_procedure.h *************** *** 41,47 **** typedef struct PLyProcedureEntry /* PLyProcedure manipulation */ extern char *PLy_procedure_name(PLyProcedure *proc); ! extern PLyProcedure *PLy_procedure_get(Oid fn_oid, bool is_trigger); extern void PLy_procedure_compile(PLyProcedure *proc, const char *src); extern void PLy_procedure_delete(PLyProcedure *proc); --- 41,48 ---- /* PLyProcedure manipulation */ extern char *PLy_procedure_name(PLyProcedure *proc); ! extern PLyProcedure *PLy_procedure_get(Oid fn_oid, ! bool is_dml_trigger, bool is_cmd_trigger); extern void PLy_procedure_compile(PLyProcedure *proc, const char *src); extern void PLy_procedure_delete(PLyProcedure *proc); *** a/src/pl/plpython/sql/plpython_trigger.sql --- b/src/pl/plpython/sql/plpython_trigger.sql *************** *** 346,348 **** CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test --- 346,367 ---- INSERT INTO composite_trigger_test VALUES (NULL, NULL); SELECT * FROM composite_trigger_test; + + -- test plpython command triggers + create or replace function pysnitch() returns command_trigger language plpythonu as $$ + plpy.notice(" pysnitch: %s %s %s.%s [%s]" % + (TD["when"], TD["tag"], TD["schemaname"], TD["objectname"])); + $$; + + create command trigger py_a_snitch after any command execute procedure pysnitch(); + create command trigger py_b_snitch before any command execute procedure pysnitch(); + + create or replace function foobar() returns int language sql as $$select 1;$$; + alter function foobar() cost 77; + drop function foobar(); + + create table foo(); + drop table foo; + + drop command trigger py_a_snitch; + drop command trigger py_b_snitch; *** a/src/pl/tcl/expected/pltcl_setup.out --- b/src/pl/tcl/expected/pltcl_setup.out *************** *** 519,521 **** select tcl_date_week(2001,10,24); --- 519,544 ---- 42 (1 row) + -- test pltcl command triggers + create or replace function tclsnitch() returns command_trigger language pltcl as $$ + elog NOTICE " tclsnitch: $TG_when $TG_tag $TG_schemaname $TG_objectname" + $$; + create command trigger tcl_a_snitch after any command execute procedure tclsnitch(); + create command trigger tcl_b_snitch before any command execute procedure tclsnitch(); + create or replace function foobar() returns int language sql as $$select 1;$$; + NOTICE: tclsnitch: BEFORE CREATE FUNCTION public foobar + NOTICE: tclsnitch: AFTER CREATE FUNCTION public foobar + alter function foobar() cost 77; + NOTICE: tclsnitch: BEFORE ALTER FUNCTION public foobar + NOTICE: tclsnitch: AFTER ALTER FUNCTION public foobar + drop function foobar(); + NOTICE: tclsnitch: BEFORE DROP FUNCTION public foobar + NOTICE: tclsnitch: AFTER DROP FUNCTION public foobar + create table foo(); + NOTICE: tclsnitch: BEFORE CREATE TABLE public foo + NOTICE: tclsnitch: AFTER CREATE TABLE public foo + drop table foo; + NOTICE: tclsnitch: BEFORE DROP TABLE public foo + NOTICE: tclsnitch: AFTER DROP TABLE public foo + drop command trigger tcl_a_snitch; + drop command trigger tcl_b_snitch; *** a/src/pl/tcl/pltcl.c --- b/src/pl/tcl/pltcl.c *************** *** 194,204 **** static Datum pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted); static Datum pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted); static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted); static void throw_tcl_error(Tcl_Interp *interp, const char *proname); static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid, ! bool pltrusted); static int pltcl_elog(ClientData cdata, Tcl_Interp *interp, int argc, CONST84 char *argv[]); --- 194,206 ---- static Datum pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted); static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted); + static void pltcl_command_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted); static void throw_tcl_error(Tcl_Interp *interp, const char *proname); static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid, ! bool is_cmd_trigger, ! bool pltrusted); static int pltcl_elog(ClientData cdata, Tcl_Interp *interp, int argc, CONST84 char *argv[]); *************** *** 637,642 **** pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted) --- 639,649 ---- pltcl_current_fcinfo = NULL; retval = PointerGetDatum(pltcl_trigger_handler(fcinfo, pltrusted)); } + else if (CALLED_AS_COMMAND_TRIGGER(fcinfo)) + { + pltcl_current_fcinfo = NULL; + pltcl_command_trigger_handler(fcinfo, pltrusted); + } else { pltcl_current_fcinfo = fcinfo; *************** *** 678,684 **** pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted) /* Find or compile the function */ prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid, ! pltrusted); pltcl_current_prodesc = prodesc; --- 685,691 ---- /* Find or compile the function */ prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid, ! false, pltrusted); pltcl_current_prodesc = prodesc; *************** *** 837,842 **** pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted) --- 844,850 ---- /* Find or compile the function */ prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, RelationGetRelid(trigdata->tg_relation), + false, /* not a command trigger */ pltrusted); pltcl_current_prodesc = prodesc; *************** *** 1123,1128 **** pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted) --- 1131,1210 ---- return rettup; } + /********************************************************************** + * pltcl_command_trigger_handler() - Handler for command trigger calls + **********************************************************************/ + static void + pltcl_command_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted) + { + pltcl_proc_desc *prodesc; + Tcl_Interp *volatile interp; + CommandTriggerData *tdata = (CommandTriggerData *) fcinfo->context; + char *stroid; + Tcl_DString tcl_cmd; + int tcl_rc; + + /* Connect to SPI manager */ + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "could not connect to SPI manager"); + + /* Find or compile the function */ + prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, + InvalidOid, true, pltrusted); + + pltcl_current_prodesc = prodesc; + + interp = prodesc->interp_desc->interp; + + /************************************************************ + * Create the tcl command to call the internal + * proc in the interpreter + ************************************************************/ + Tcl_DStringInit(&tcl_cmd); + Tcl_DStringAppendElement(&tcl_cmd, prodesc->internal_proname); + PG_TRY(); + { + Tcl_DStringAppendElement(&tcl_cmd, tdata->when); + Tcl_DStringAppendElement(&tcl_cmd, tdata->tag); + + if (tdata->objectId == InvalidOid) + stroid = pstrdup("NULL"); + else + stroid = DatumGetCString( + DirectFunctionCall1(oidout, ObjectIdGetDatum(tdata->objectId))); + Tcl_DStringAppendElement(&tcl_cmd, stroid); + pfree(stroid); + + Tcl_DStringAppendElement(&tcl_cmd, tdata->schemaname); + Tcl_DStringAppendElement(&tcl_cmd, tdata->objectname); + } + PG_CATCH(); + { + Tcl_DStringFree(&tcl_cmd); + PG_RE_THROW(); + } + PG_END_TRY(); + + /************************************************************ + * Call the Tcl function + * + * We assume no PG error can be thrown directly from this call. + ************************************************************/ + tcl_rc = Tcl_GlobalEval(interp, Tcl_DStringValue(&tcl_cmd)); + Tcl_DStringFree(&tcl_cmd); + + /************************************************************ + * Check for errors reported by Tcl. + ************************************************************/ + if (tcl_rc != TCL_OK) + throw_tcl_error(interp, prodesc->user_proname); + + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish() failed"); + + return; + } + /********************************************************************** * throw_tcl_error - ereport an error returned from the Tcl interpreter *************** *** 1161,1167 **** throw_tcl_error(Tcl_Interp *interp, const char *proname) * (InvalidOid) when compiling a plain function. **********************************************************************/ static pltcl_proc_desc * ! compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) { HeapTuple procTup; Form_pg_proc procStruct; --- 1243,1250 ---- * (InvalidOid) when compiling a plain function. **********************************************************************/ static pltcl_proc_desc * ! compile_pltcl_function(Oid fn_oid, Oid tgreloid, ! bool is_cmd_trigger, bool pltrusted) { HeapTuple procTup; Form_pg_proc procStruct; *************** *** 1218,1224 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) ************************************************************/ if (prodesc == NULL) { ! bool is_trigger = OidIsValid(tgreloid); char internal_proname[128]; HeapTuple typeTup; Form_pg_type typeStruct; --- 1301,1307 ---- ************************************************************/ if (prodesc == NULL) { ! bool is_dml_trigger = OidIsValid(tgreloid); char internal_proname[128]; HeapTuple typeTup; Form_pg_type typeStruct; *************** *** 1238,1247 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) * "_trigger" when appropriate to ensure the normal and trigger * cases are kept separate. ************************************************************/ ! if (!is_trigger) snprintf(internal_proname, sizeof(internal_proname), "__PLTcl_proc_%u", fn_oid); ! else snprintf(internal_proname, sizeof(internal_proname), "__PLTcl_proc_%u_trigger", fn_oid); --- 1321,1333 ---- * "_trigger" when appropriate to ensure the normal and trigger * cases are kept separate. ************************************************************/ ! if (!is_dml_trigger && !is_cmd_trigger) snprintf(internal_proname, sizeof(internal_proname), "__PLTcl_proc_%u", fn_oid); ! else if (is_cmd_trigger) ! snprintf(internal_proname, sizeof(internal_proname), ! "__PLTcl_proc_%u_cmdtrigger", fn_oid); ! else if (is_dml_trigger) snprintf(internal_proname, sizeof(internal_proname), "__PLTcl_proc_%u_trigger", fn_oid); *************** *** 1279,1285 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) * Get the required information for input conversion of the * return value. ************************************************************/ ! if (!is_trigger) { typeTup = SearchSysCache1(TYPEOID, --- 1365,1371 ---- * Get the required information for input conversion of the * return value. ************************************************************/ ! if (!is_dml_trigger && !is_cmd_trigger) { typeTup = SearchSysCache1(TYPEOID, *************** *** 1340,1346 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) * Get the required information for output conversion * of all procedure arguments ************************************************************/ ! if (!is_trigger) { prodesc->nargs = procStruct->pronargs; proc_internal_args[0] = '\0'; --- 1426,1432 ---- * Get the required information for output conversion * of all procedure arguments ************************************************************/ ! if (!is_dml_trigger && !is_cmd_trigger) { prodesc->nargs = procStruct->pronargs; proc_internal_args[0] = '\0'; *************** *** 1390,1401 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) ReleaseSysCache(typeTup); } } ! else { /* trigger procedure has fixed args */ strcpy(proc_internal_args, "TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args"); } /************************************************************ * Create the tcl command to define the internal --- 1476,1493 ---- ReleaseSysCache(typeTup); } } ! else if (is_dml_trigger) { /* trigger procedure has fixed args */ strcpy(proc_internal_args, "TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args"); } + else if (is_cmd_trigger) + { + /* command trigger procedure has fixed args */ + strcpy(proc_internal_args, + "TG_when TG_tag TG_objectid TG_schemaname TG_objectname"); + } /************************************************************ * Create the tcl command to define the internal *************** *** 1415,1434 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) Tcl_DStringAppend(&proc_internal_body, "upvar #0 ", -1); Tcl_DStringAppend(&proc_internal_body, internal_proname, -1); Tcl_DStringAppend(&proc_internal_body, " GD\n", -1); ! if (!is_trigger) ! { ! for (i = 0; i < prodesc->nargs; i++) ! { ! if (prodesc->arg_is_rowtype[i]) ! { ! snprintf(buf, sizeof(buf), ! "array set %d $__PLTcl_Tup_%d\n", ! i + 1, i + 1); ! Tcl_DStringAppend(&proc_internal_body, buf, -1); ! } ! } ! } ! else { Tcl_DStringAppend(&proc_internal_body, "array set NEW $__PLTcl_Tup_NEW\n", -1); --- 1507,1513 ---- Tcl_DStringAppend(&proc_internal_body, "upvar #0 ", -1); Tcl_DStringAppend(&proc_internal_body, internal_proname, -1); Tcl_DStringAppend(&proc_internal_body, " GD\n", -1); ! if (is_dml_trigger) { Tcl_DStringAppend(&proc_internal_body, "array set NEW $__PLTcl_Tup_NEW\n", -1); *************** *** 1444,1449 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) --- 1523,1545 ---- "}\n" "unset i v\n\n", -1); } + else if (is_cmd_trigger) + { + /* no argument support for command triggers */ + } + else + { + for (i = 0; i < prodesc->nargs; i++) + { + if (prodesc->arg_is_rowtype[i]) + { + snprintf(buf, sizeof(buf), + "array set %d $__PLTcl_Tup_%d\n", + i + 1, i + 1); + Tcl_DStringAppend(&proc_internal_body, buf, -1); + } + } + } /************************************************************ * Add user's function definition to proc body *** a/src/pl/tcl/sql/pltcl_setup.sql --- b/src/pl/tcl/sql/pltcl_setup.sql *************** *** 559,561 **** $$ language pltcl immutable; --- 559,579 ---- select tcl_date_week(2010,1,24); select tcl_date_week(2001,10,24); + + -- test pltcl command triggers + create or replace function tclsnitch() returns command_trigger language pltcl as $$ + elog NOTICE " tclsnitch: $TG_when $TG_tag $TG_schemaname $TG_objectname" + $$; + + create command trigger tcl_a_snitch after any command execute procedure tclsnitch(); + create command trigger tcl_b_snitch before any command execute procedure tclsnitch(); + + create or replace function foobar() returns int language sql as $$select 1;$$; + alter function foobar() cost 77; + drop function foobar(); + + create table foo(); + drop table foo; + + drop command trigger tcl_a_snitch; + drop command trigger tcl_b_snitch; *** /dev/null --- b/src/test/regress/expected/cmdtriggers.out *************** *** 0 **** --- 1,628 ---- + -- + -- COMMAND TRIGGERS + -- + create or replace function any_snitch() + returns command_trigger + language plpgsql + as $$ + begin + -- can't output tg_objectid here that would break pg_regress + raise notice 'snitch: % any % %.%', tg_when, tg_tag, tg_schemaname, tg_objectname; + end; + $$; + create or replace function snitch() + returns command_trigger + language plpgsql + as $$ + begin + -- can't output tg_objectid here that would break pg_regress + raise notice 'snitch: % % %.%', tg_when, tg_tag, tg_schemaname, tg_objectname; + end; + $$; + create command trigger snitch_before before any command execute procedure any_snitch(); + create command trigger snitch_after_ after any command execute procedure any_snitch(); + alter command trigger snitch_before set disable; + alter command trigger snitch_before set enable; + alter command trigger snitch_after_ rename to snitch_after; + create command trigger snitch_create_table after create table execute procedure snitch(); + create command trigger snitch_create_table_as after create table as execute procedure snitch(); + create command trigger snitch_select_into after select into execute procedure snitch(); + create command trigger snitch_create_seq after create sequence execute procedure snitch(); + create command trigger snitch_create_view after create view execute procedure snitch(); + create command trigger snitch_alter_table after alter table execute procedure snitch(); + create command trigger snitch_alter_seq after alter sequence execute procedure snitch(); + create command trigger snitch_alter_view after alter view execute procedure snitch(); + create command trigger snitch_drop_table after drop table execute procedure snitch(); + create command trigger snitch_create_function after create function execute procedure snitch(); + create command trigger snitch_create_collation after create collation execute procedure snitch(); + create command trigger snitch_alter_operator after alter operator execute procedure snitch(); + create command trigger snitch_create_domain after create domain execute procedure snitch(); + create command trigger snitch_alter_schema after alter schema execute procedure snitch(); + create command trigger snitch_create_tsconfig after create text search configuration execute procedure snitch(); + create command trigger snitch_create_tsdict after create text search dictionary execute procedure snitch(); + create command trigger snitch_create_tsparser after create text search parser execute procedure snitch(); + create command trigger snitch_create_tstmpl after create text search template execute procedure snitch(); + create command trigger snitch_after_alter_function after alter function execute procedure snitch(); + create command trigger snitch_create_cast after create cast execute procedure snitch(); + create command trigger snitch_create_opclass after create operator class execute procedure snitch(); + create command trigger snitch_create_trigger before create trigger execute procedure snitch(); + create command trigger snitch_alter_trigger before alter trigger execute procedure snitch(); + create command trigger snitch_drop_trigger before drop trigger execute procedure snitch(); + create command trigger snitch_create_schema before create schema execute procedure snitch(); + create command trigger snitch_drop_schema before drop schema execute procedure snitch(); + create command trigger snitch_create_aggregate before create aggregate execute procedure snitch(); + create command trigger snitch_alter_collation before alter collation execute procedure snitch(); + create command trigger snitch_create_operator before create operator execute procedure snitch(); + create command trigger snitch_alter_domain before alter domain execute procedure snitch(); + create command trigger snitch_create_type before create type execute procedure snitch(); + create command trigger snitch_alter_type before alter type execute procedure snitch(); + create command trigger snitch_before_alter_function before alter function execute procedure snitch(); + create command trigger snitch_alter_conversion before alter conversion execute procedure snitch(); + create command trigger snitch_drop_agg before drop aggregate execute procedure snitch(); + create command trigger snitch_drop_domain before drop domain execute procedure snitch(); + create command trigger snitch_before_drop_tsconf before drop text search configuration execute procedure snitch(); + create command trigger snitch_before_drop_tsdict before drop text search dictionary execute procedure snitch(); + create command trigger snitch_before_drop_tsparser before drop text search parser execute procedure snitch(); + create command trigger snitch_before_drop_tstmpl before drop text search template execute procedure snitch(); + create command trigger snitch_vacuum before vacuum execute procedure snitch(); + create command trigger snitch_reindex before reindex execute procedure snitch(); + WARNING: REINDEX DATABASE triggers are not supported + DETAIL: The command trigger will not fire on REINDEX DATABASE. + create schema cmd; + NOTICE: snitch: BEFORE any CREATE SCHEMA .cmd + NOTICE: snitch: BEFORE CREATE SCHEMA .cmd + NOTICE: snitch: AFTER any CREATE SCHEMA .cmd + create schema cmd2; + NOTICE: snitch: BEFORE any CREATE SCHEMA .cmd2 + NOTICE: snitch: BEFORE CREATE SCHEMA .cmd2 + NOTICE: snitch: AFTER any CREATE SCHEMA .cmd2 + create role regbob; + create table cmd.foo(id bigserial primary key); + NOTICE: CREATE TABLE will create implicit sequence "foo_id_seq" for serial column "foo.id" + NOTICE: snitch: BEFORE any CREATE SEQUENCE cmd.foo_id_seq + NOTICE: snitch: AFTER CREATE SEQUENCE cmd.foo_id_seq + NOTICE: snitch: AFTER any CREATE SEQUENCE cmd.foo_id_seq + NOTICE: snitch: BEFORE any CREATE TABLE cmd.foo + NOTICE: snitch: BEFORE any CREATE INDEX cmd.foo_pkey + NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo" + NOTICE: snitch: AFTER any CREATE INDEX cmd.foo_pkey + NOTICE: snitch: BEFORE any ALTER SEQUENCE cmd.foo_id_seq + NOTICE: snitch: AFTER ALTER SEQUENCE cmd.foo_id_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE cmd.foo_id_seq + NOTICE: snitch: AFTER CREATE TABLE cmd.foo + NOTICE: snitch: AFTER any CREATE TABLE cmd.foo + create view cmd.v as select * from cmd.foo; + NOTICE: snitch: BEFORE any CREATE VIEW cmd.v + NOTICE: snitch: AFTER CREATE VIEW cmd.v + NOTICE: snitch: AFTER any CREATE VIEW cmd.v + alter table cmd.foo add column t text; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.foo + NOTICE: snitch: AFTER ALTER TABLE cmd.foo + NOTICE: snitch: AFTER any ALTER TABLE cmd.foo + create table cmd.bar as select 1; + NOTICE: snitch: BEFORE any CREATE TABLE AS cmd.bar + NOTICE: snitch: AFTER CREATE TABLE AS cmd.bar + NOTICE: snitch: AFTER any CREATE TABLE AS cmd.bar + drop table cmd.bar; + NOTICE: snitch: BEFORE any DROP TABLE cmd.bar + NOTICE: snitch: AFTER DROP TABLE cmd.bar + NOTICE: snitch: AFTER any DROP TABLE cmd.bar + select 1 into cmd.bar; + NOTICE: snitch: BEFORE any SELECT INTO cmd.bar + NOTICE: snitch: AFTER SELECT INTO cmd.bar + NOTICE: snitch: AFTER any SELECT INTO cmd.bar + drop table cmd.bar; + NOTICE: snitch: BEFORE any DROP TABLE cmd.bar + NOTICE: snitch: AFTER DROP TABLE cmd.bar + NOTICE: snitch: AFTER any DROP TABLE cmd.bar + create table test9 (id int, stuff text); + NOTICE: snitch: BEFORE any CREATE TABLE public.test9 + NOTICE: snitch: AFTER CREATE TABLE public.test9 + NOTICE: snitch: AFTER any CREATE TABLE public.test9 + alter table test9 rename to test; + NOTICE: snitch: BEFORE any ALTER TABLE public.test9 + NOTICE: snitch: AFTER ALTER TABLE public.test + NOTICE: snitch: AFTER any ALTER TABLE public.test + alter table test set schema cmd; + NOTICE: snitch: BEFORE any ALTER TABLE public.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test rename column stuff to things; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test add column alpha text; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test alter column alpha set data type varchar(300); + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test alter column alpha set default 'test'; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test alter column alpha drop default; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test alter column alpha set statistics 78; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test alter column alpha set storage plain; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test alter column alpha set not null; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test alter column alpha drop not null; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test alter column alpha set (n_distinct = -0.78); + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test alter column alpha reset (n_distinct); + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test drop column alpha; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test add check (id > 2) not valid; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test add check (id < 800000); + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test set without cluster; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test set with oids; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + alter table cmd.test set without oids; + NOTICE: snitch: BEFORE any ALTER TABLE cmd.test + NOTICE: snitch: AFTER ALTER TABLE cmd.test + NOTICE: snitch: AFTER any ALTER TABLE cmd.test + create sequence test_seq_; + NOTICE: snitch: BEFORE any CREATE SEQUENCE public.test_seq_ + NOTICE: snitch: AFTER CREATE SEQUENCE public.test_seq_ + NOTICE: snitch: AFTER any CREATE SEQUENCE public.test_seq_ + alter sequence test_seq_ owner to regbob; + NOTICE: snitch: BEFORE any ALTER SEQUENCE public.test_seq_ + NOTICE: snitch: AFTER ALTER SEQUENCE public.test_seq_ + NOTICE: snitch: AFTER any ALTER SEQUENCE public.test_seq_ + alter sequence test_seq_ rename to test_seq; + NOTICE: snitch: BEFORE any ALTER SEQUENCE public.test_seq_ + NOTICE: snitch: AFTER ALTER SEQUENCE public.test_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE public.test_seq + alter sequence test_seq set schema cmd; + NOTICE: snitch: BEFORE any ALTER SEQUENCE public.test_seq + NOTICE: snitch: AFTER ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE cmd.test_seq + alter sequence cmd.test_seq start with 3; + NOTICE: snitch: BEFORE any ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE cmd.test_seq + alter sequence cmd.test_seq restart with 4; + NOTICE: snitch: BEFORE any ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE cmd.test_seq + alter sequence cmd.test_seq minvalue 3; + NOTICE: snitch: BEFORE any ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE cmd.test_seq + alter sequence cmd.test_seq no minvalue; + NOTICE: snitch: BEFORE any ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE cmd.test_seq + alter sequence cmd.test_seq maxvalue 900000; + NOTICE: snitch: BEFORE any ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE cmd.test_seq + alter sequence cmd.test_seq no maxvalue; + NOTICE: snitch: BEFORE any ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE cmd.test_seq + alter sequence cmd.test_seq cache 876; + NOTICE: snitch: BEFORE any ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE cmd.test_seq + alter sequence cmd.test_seq cycle; + NOTICE: snitch: BEFORE any ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE cmd.test_seq + alter sequence cmd.test_seq no cycle; + NOTICE: snitch: BEFORE any ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER ALTER SEQUENCE cmd.test_seq + NOTICE: snitch: AFTER any ALTER SEQUENCE cmd.test_seq + create view view_test as select id, things from cmd.test; + NOTICE: snitch: BEFORE any CREATE VIEW public.view_test + NOTICE: snitch: AFTER CREATE VIEW public.view_test + NOTICE: snitch: AFTER any CREATE VIEW public.view_test + alter view view_test owner to regbob; + NOTICE: snitch: BEFORE any ALTER VIEW public.view_test + NOTICE: snitch: AFTER ALTER VIEW public.view_test + NOTICE: snitch: AFTER any ALTER VIEW public.view_test + alter view view_test rename to view_test2; + NOTICE: snitch: BEFORE any ALTER VIEW public.view_test + NOTICE: snitch: AFTER ALTER VIEW public.view_test2 + NOTICE: snitch: AFTER any ALTER VIEW public.view_test2 + alter view view_test2 set schema cmd; + NOTICE: snitch: BEFORE any ALTER VIEW public.view_test2 + NOTICE: snitch: AFTER ALTER VIEW cmd.view_test2 + NOTICE: snitch: AFTER any ALTER VIEW cmd.view_test2 + alter view cmd.view_test2 alter column id set default 9; + NOTICE: snitch: BEFORE any ALTER VIEW cmd.view_test2 + NOTICE: snitch: AFTER ALTER VIEW cmd.view_test2 + NOTICE: snitch: AFTER any ALTER VIEW cmd.view_test2 + alter view cmd.view_test2 alter column id drop default; + NOTICE: snitch: BEFORE any ALTER VIEW cmd.view_test2 + NOTICE: snitch: AFTER ALTER VIEW cmd.view_test2 + NOTICE: snitch: AFTER any ALTER VIEW cmd.view_test2 + cluster cmd.foo using foo_pkey; + NOTICE: snitch: BEFORE any CLUSTER cmd.foo + vacuum cmd.foo; + NOTICE: snitch: BEFORE any VACUUM cmd.foo + NOTICE: snitch: BEFORE VACUUM cmd.foo + vacuum; + NOTICE: snitch: BEFORE any VACUUM . + NOTICE: snitch: BEFORE VACUUM . + reindex table cmd.foo; + NOTICE: snitch: BEFORE any REINDEX cmd.foo + NOTICE: snitch: BEFORE REINDEX cmd.foo + NOTICE: snitch: AFTER any REINDEX cmd.foo + set session_replication_role to replica; + create table cmd.bar(); + reset session_replication_role; + create index idx_foo on cmd.foo(t); + NOTICE: snitch: BEFORE any CREATE INDEX cmd.idx_foo + NOTICE: snitch: AFTER any CREATE INDEX cmd.idx_foo + reindex index cmd.idx_foo; + NOTICE: snitch: BEFORE any REINDEX cmd.idx_foo + NOTICE: snitch: BEFORE REINDEX cmd.idx_foo + NOTICE: snitch: AFTER any REINDEX cmd.idx_foo + drop index cmd.idx_foo; + NOTICE: snitch: BEFORE any DROP INDEX cmd.idx_foo + NOTICE: snitch: AFTER any DROP INDEX cmd.idx_foo + create function fun(int) returns text language sql + as $$ select t from cmd.foo where id = $1; $$; + NOTICE: snitch: BEFORE any CREATE FUNCTION public.fun + NOTICE: snitch: AFTER CREATE FUNCTION public.fun + NOTICE: snitch: AFTER any CREATE FUNCTION public.fun + alter function fun(int) strict; + NOTICE: snitch: BEFORE any ALTER FUNCTION public.fun + NOTICE: snitch: BEFORE ALTER FUNCTION public.fun + NOTICE: snitch: AFTER ALTER FUNCTION public.fun + NOTICE: snitch: AFTER any ALTER FUNCTION public.fun + alter function fun(int) rename to notfun; + NOTICE: snitch: BEFORE any ALTER FUNCTION public.fun + NOTICE: snitch: BEFORE ALTER FUNCTION public.fun + NOTICE: snitch: AFTER ALTER FUNCTION public.notfun + NOTICE: snitch: AFTER any ALTER FUNCTION public.notfun + alter function notfun(int) set schema cmd; + NOTICE: snitch: BEFORE any ALTER FUNCTION public.notfun + NOTICE: snitch: BEFORE ALTER FUNCTION public.notfun + NOTICE: snitch: AFTER ALTER FUNCTION cmd.notfun + NOTICE: snitch: AFTER any ALTER FUNCTION cmd.notfun + alter function cmd.notfun(int) owner to regbob; + NOTICE: snitch: BEFORE any ALTER FUNCTION cmd.notfun + NOTICE: snitch: BEFORE ALTER FUNCTION cmd.notfun + NOTICE: snitch: AFTER ALTER FUNCTION cmd.notfun + NOTICE: snitch: AFTER any ALTER FUNCTION cmd.notfun + alter function cmd.notfun(int) cost 77; + NOTICE: snitch: BEFORE any ALTER FUNCTION cmd.notfun + NOTICE: snitch: BEFORE ALTER FUNCTION cmd.notfun + NOTICE: snitch: AFTER ALTER FUNCTION cmd.notfun + NOTICE: snitch: AFTER any ALTER FUNCTION cmd.notfun + drop function cmd.notfun(int); + NOTICE: snitch: BEFORE any DROP FUNCTION cmd.notfun + NOTICE: snitch: AFTER any DROP FUNCTION cmd.notfun + create function cmd.plus1(int) returns bigint language sql + as $$ select $1::bigint + 1; $$; + NOTICE: snitch: BEFORE any CREATE FUNCTION cmd.plus1 + NOTICE: snitch: AFTER CREATE FUNCTION cmd.plus1 + NOTICE: snitch: AFTER any CREATE FUNCTION cmd.plus1 + create operator cmd.+!(procedure = cmd.plus1, leftarg = int); + NOTICE: snitch: BEFORE any CREATE OPERATOR cmd.+! + NOTICE: snitch: BEFORE CREATE OPERATOR cmd.+! + NOTICE: snitch: AFTER any CREATE OPERATOR cmd.+! + alter operator cmd.+!(int, NONE) set schema public; + NOTICE: snitch: BEFORE any ALTER OPERATOR cmd.+! + NOTICE: snitch: AFTER ALTER OPERATOR public.+! + NOTICE: snitch: AFTER any ALTER OPERATOR public.+! + drop operator public.+!(int, NONE); + NOTICE: snitch: BEFORE any DROP OPERATOR public.+! + NOTICE: snitch: AFTER any DROP OPERATOR public.+! + create aggregate cmd.avg (float8) + ( + sfunc = float8_accum, + stype = float8[], + finalfunc = float8_avg, + initcond = '{0,0,0}' + ); + NOTICE: snitch: BEFORE any CREATE AGGREGATE cmd.avg + NOTICE: snitch: BEFORE CREATE AGGREGATE cmd.avg + NOTICE: snitch: AFTER any CREATE AGGREGATE cmd.avg + alter aggregate cmd.avg(float8) set schema public; + NOTICE: snitch: BEFORE any ALTER AGGREGATE cmd.avg + NOTICE: snitch: AFTER any ALTER AGGREGATE public.avg + drop aggregate public.avg(float8); + NOTICE: snitch: BEFORE any DROP AGGREGATE public.avg + NOTICE: snitch: BEFORE DROP AGGREGATE public.avg + NOTICE: snitch: AFTER any DROP AGGREGATE public.avg + create collation cmd.french (LOCALE = 'fr_FR'); + NOTICE: snitch: BEFORE any CREATE COLLATION cmd.french + NOTICE: snitch: AFTER CREATE COLLATION cmd.french + NOTICE: snitch: AFTER any CREATE COLLATION cmd.french + alter collation cmd.french rename to francais; + NOTICE: snitch: BEFORE any ALTER COLLATION cmd.french + NOTICE: snitch: BEFORE ALTER COLLATION cmd.french + NOTICE: snitch: AFTER any ALTER COLLATION cmd.francais + create type cmd.compfoo AS (f1 int, f2 text); + NOTICE: snitch: BEFORE any CREATE TYPE cmd.compfoo + NOTICE: snitch: BEFORE CREATE TYPE cmd.compfoo + NOTICE: snitch: AFTER any CREATE TYPE cmd.compfoo + alter type cmd.compfoo add attribute f3 text; + NOTICE: snitch: BEFORE any ALTER TYPE cmd.compfoo + NOTICE: snitch: BEFORE ALTER TYPE cmd.compfoo + NOTICE: snitch: AFTER any ALTER TYPE cmd.compfoo + create type cmd.type_test AS (a integer, b integer, c text); + NOTICE: snitch: BEFORE any CREATE TYPE cmd.type_test + NOTICE: snitch: BEFORE CREATE TYPE cmd.type_test + NOTICE: snitch: AFTER any CREATE TYPE cmd.type_test + alter type cmd.type_test owner to regbob; + NOTICE: snitch: BEFORE any ALTER TYPE cmd.type_test + NOTICE: snitch: BEFORE ALTER TYPE cmd.type_test + NOTICE: snitch: AFTER any ALTER TYPE cmd.type_test + alter type cmd.type_test rename to type_test2; + NOTICE: snitch: BEFORE any ALTER TYPE cmd.type_test + NOTICE: snitch: BEFORE ALTER TYPE cmd.type_test + NOTICE: snitch: AFTER any ALTER TYPE cmd.type_test2 + alter type cmd.type_test2 set schema public; + NOTICE: snitch: BEFORE any ALTER TYPE cmd.type_test2 + NOTICE: snitch: BEFORE ALTER TYPE cmd.type_test2 + NOTICE: snitch: AFTER any ALTER TYPE public.type_test2 + alter type public.type_test2 rename attribute a to z; + NOTICE: snitch: BEFORE any ALTER TYPE public.type_test2 + NOTICE: snitch: BEFORE ALTER TYPE public.type_test2 + NOTICE: snitch: AFTER any ALTER TYPE public.type_test2 + alter type public.type_test2 add attribute alpha text; + NOTICE: snitch: BEFORE any ALTER TYPE public.type_test2 + NOTICE: snitch: BEFORE ALTER TYPE public.type_test2 + NOTICE: snitch: AFTER any ALTER TYPE public.type_test2 + alter type public.type_test2 alter attribute alpha set data type char(90); + NOTICE: snitch: BEFORE any ALTER TYPE public.type_test2 + NOTICE: snitch: BEFORE ALTER TYPE public.type_test2 + NOTICE: snitch: AFTER any ALTER TYPE public.type_test2 + alter type public.type_test2 drop attribute alpha; + NOTICE: snitch: BEFORE any ALTER TYPE public.type_test2 + NOTICE: snitch: BEFORE ALTER TYPE public.type_test2 + NOTICE: snitch: AFTER any ALTER TYPE public.type_test2 + drop type cmd.compfoo; + NOTICE: snitch: BEFORE any DROP TYPE cmd.compfoo + NOTICE: snitch: AFTER any DROP TYPE cmd.compfoo + drop type public.type_test2; + NOTICE: snitch: BEFORE any DROP TYPE public.type_test2 + NOTICE: snitch: AFTER any DROP TYPE public.type_test2 + create type cmd.bug_status as enum ('new', 'open', 'closed'); + NOTICE: snitch: BEFORE any CREATE TYPE cmd.bug_status + NOTICE: snitch: BEFORE CREATE TYPE cmd.bug_status + NOTICE: snitch: AFTER any CREATE TYPE cmd.bug_status + alter type cmd.bug_status add value 'wontfix'; + create domain cmd.us_postal_code as text check(value ~ '^\d{5}$' or value ~ '^\d{5}-\d{4}$'); + NOTICE: snitch: BEFORE any CREATE DOMAIN cmd.us_postal_code + NOTICE: snitch: AFTER CREATE DOMAIN cmd.us_postal_code + NOTICE: snitch: AFTER any CREATE DOMAIN cmd.us_postal_code + alter domain cmd.us_postal_code set not null; + NOTICE: snitch: BEFORE any ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: BEFORE ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: AFTER any ALTER DOMAIN cmd.us_postal_code + alter domain cmd.us_postal_code set default 90210; + NOTICE: snitch: BEFORE any ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: BEFORE ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: AFTER any ALTER DOMAIN cmd.us_postal_code + alter domain cmd.us_postal_code drop default; + NOTICE: snitch: BEFORE any ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: BEFORE ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: AFTER any ALTER DOMAIN cmd.us_postal_code + alter domain cmd.us_postal_code drop not null; + NOTICE: snitch: BEFORE any ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: BEFORE ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: AFTER any ALTER DOMAIN cmd.us_postal_code + alter domain cmd.us_postal_code add constraint dummy_constraint check (value ~ '^\d{8}$'); + NOTICE: snitch: BEFORE any ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: BEFORE ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: AFTER any ALTER DOMAIN cmd.us_postal_code + alter domain cmd.us_postal_code drop constraint dummy_constraint; + NOTICE: snitch: BEFORE any ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: BEFORE ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: AFTER any ALTER DOMAIN cmd.us_postal_code + alter domain cmd.us_postal_code owner to regbob; + NOTICE: snitch: BEFORE any ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: BEFORE ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: AFTER any ALTER DOMAIN cmd.us_postal_code + alter domain cmd.us_postal_code set schema cmd2; + NOTICE: snitch: BEFORE any ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: BEFORE ALTER DOMAIN cmd.us_postal_code + NOTICE: snitch: AFTER any ALTER DOMAIN cmd2.us_postal_code + drop domain cmd2.us_postal_code; + NOTICE: snitch: BEFORE any DROP DOMAIN cmd2.us_postal_code + NOTICE: snitch: BEFORE DROP DOMAIN cmd2.us_postal_code + NOTICE: snitch: AFTER any DROP DOMAIN cmd2.us_postal_code + create function cmd.trigfunc() returns trigger language plpgsql as + $$ begin raise notice 'trigfunc'; end;$$; + NOTICE: snitch: BEFORE any CREATE FUNCTION cmd.trigfunc + NOTICE: snitch: AFTER CREATE FUNCTION cmd.trigfunc + NOTICE: snitch: AFTER any CREATE FUNCTION cmd.trigfunc + create trigger footg before update on cmd.foo for each row execute procedure cmd.trigfunc(); + NOTICE: snitch: BEFORE any CREATE TRIGGER .footg + NOTICE: snitch: BEFORE CREATE TRIGGER .footg + NOTICE: snitch: AFTER any CREATE TRIGGER .footg + alter trigger footg on cmd.foo rename to foo_trigger; + NOTICE: snitch: BEFORE any ALTER TRIGGER cmd.footg + NOTICE: snitch: BEFORE ALTER TRIGGER cmd.footg + NOTICE: snitch: AFTER any ALTER TRIGGER cmd.foo_trigger + drop trigger foo_trigger on cmd.foo; + NOTICE: snitch: BEFORE any DROP TRIGGER .foo_trigger + NOTICE: snitch: BEFORE DROP TRIGGER .foo_trigger + NOTICE: snitch: AFTER any DROP TRIGGER .foo_trigger + create conversion test for 'utf8' to 'sjis' from utf8_to_sjis; + NOTICE: snitch: BEFORE any CREATE CONVERSION public.test + NOTICE: snitch: AFTER any CREATE CONVERSION public.test + create default conversion test2 for 'utf8' to 'sjis' from utf8_to_sjis; + NOTICE: snitch: BEFORE any CREATE CONVERSION public.test2 + NOTICE: snitch: AFTER any CREATE CONVERSION public.test2 + alter conversion test2 rename to test3; + NOTICE: snitch: BEFORE any ALTER CONVERSION public.test2 + NOTICE: snitch: BEFORE ALTER CONVERSION public.test2 + NOTICE: snitch: AFTER any ALTER CONVERSION public.test3 + drop conversion test3; + NOTICE: snitch: BEFORE any DROP CONVERSION public.test3 + NOTICE: snitch: AFTER any DROP CONVERSION public.test3 + drop conversion test; + NOTICE: snitch: BEFORE any DROP CONVERSION public.test + NOTICE: snitch: AFTER any DROP CONVERSION public.test + create operator class test_op_class + for type anyenum using hash as + operator 1 =, + function 1 hashenum(anyenum); + NOTICE: snitch: BEFORE any CREATE OPERATOR CLASS public.test_op_class + NOTICE: snitch: AFTER CREATE OPERATOR CLASS public.test_op_class + NOTICE: snitch: AFTER any CREATE OPERATOR CLASS public.test_op_class + create text search configuration test (parser = "default"); + NOTICE: snitch: BEFORE any CREATE TEXT SEARCH CONFIGURATION public.test + NOTICE: snitch: AFTER CREATE TEXT SEARCH CONFIGURATION public.test + NOTICE: snitch: AFTER any CREATE TEXT SEARCH CONFIGURATION public.test + create text search dictionary test_stem ( + template = snowball, + language = 'english', stopwords = 'english' + ); + NOTICE: snitch: BEFORE any CREATE TEXT SEARCH DICTIONARY public.test_stem + NOTICE: snitch: AFTER CREATE TEXT SEARCH DICTIONARY public.test_stem + NOTICE: snitch: AFTER any CREATE TEXT SEARCH DICTIONARY public.test_stem + alter text search dictionary test_stem (StopWords = dutch ); + NOTICE: snitch: BEFORE any ALTER TEXT SEARCH DICTIONARY public.test_stem + NOTICE: snitch: AFTER any ALTER TEXT SEARCH DICTIONARY public.test_stem + create text search parser test_parser ( + start = prsd_start, + gettoken = prsd_nexttoken, + end = prsd_end, + lextypes = prsd_lextype, + headline = prsd_headline + ); + NOTICE: snitch: BEFORE any CREATE TEXT SEARCH PARSER public.test_parser + NOTICE: snitch: AFTER CREATE TEXT SEARCH PARSER public.test_parser + NOTICE: snitch: AFTER any CREATE TEXT SEARCH PARSER public.test_parser + create text search template test_template ( + init = dsimple_init, + lexize = dsimple_lexize + ); + NOTICE: snitch: BEFORE any CREATE TEXT SEARCH TEMPLATE public.test_template + NOTICE: snitch: AFTER CREATE TEXT SEARCH TEMPLATE public.test_template + NOTICE: snitch: AFTER any CREATE TEXT SEARCH TEMPLATE public.test_template + drop text search configuration test; + NOTICE: snitch: BEFORE any DROP TEXT SEARCH CONFIGURATION public.test + NOTICE: snitch: BEFORE DROP TEXT SEARCH CONFIGURATION public.test + NOTICE: snitch: AFTER any DROP TEXT SEARCH CONFIGURATION public.test + drop text search dictionary test_stem; + NOTICE: snitch: BEFORE any DROP TEXT SEARCH DICTIONARY public.test_stem + NOTICE: snitch: BEFORE DROP TEXT SEARCH DICTIONARY public.test_stem + NOTICE: snitch: AFTER any DROP TEXT SEARCH DICTIONARY public.test_stem + drop text search parser test_parser; + NOTICE: snitch: BEFORE any DROP TEXT SEARCH PARSER public.test_parser + NOTICE: snitch: BEFORE DROP TEXT SEARCH PARSER public.test_parser + NOTICE: snitch: AFTER any DROP TEXT SEARCH PARSER public.test_parser + drop text search template test_template; + NOTICE: snitch: BEFORE any DROP TEXT SEARCH TEMPLATE public.test_template + NOTICE: snitch: BEFORE DROP TEXT SEARCH TEMPLATE public.test_template + NOTICE: snitch: AFTER any DROP TEXT SEARCH TEMPLATE public.test_template + create function cmd.testcast(text) returns int4 language plpgsql as $$begin return 4::int4;end;$$; + NOTICE: snitch: BEFORE any CREATE FUNCTION cmd.testcast + NOTICE: snitch: AFTER CREATE FUNCTION cmd.testcast + NOTICE: snitch: AFTER any CREATE FUNCTION cmd.testcast + create cast (text as int4) with function cmd.testcast(text) as assignment; + NOTICE: snitch: BEFORE any CREATE CAST . + NOTICE: snitch: AFTER CREATE CAST . + NOTICE: snitch: AFTER any CREATE CAST . + alter schema cmd rename to cmd1; + NOTICE: snitch: BEFORE any ALTER SCHEMA .cmd + NOTICE: snitch: AFTER ALTER SCHEMA .cmd1 + NOTICE: snitch: AFTER any ALTER SCHEMA .cmd1 + drop schema cmd1 cascade; + NOTICE: snitch: BEFORE any DROP SCHEMA .cmd1 + NOTICE: snitch: BEFORE DROP SCHEMA .cmd1 + NOTICE: drop cascades to 12 other objects + DETAIL: drop cascades to table cmd1.foo + drop cascades to view cmd1.v + drop cascades to table cmd1.test + drop cascades to sequence cmd1.test_seq + drop cascades to view cmd1.view_test2 + drop cascades to table cmd1.bar + drop cascades to function cmd1.plus1(integer) + drop cascades to collation francais + drop cascades to type cmd1.bug_status + drop cascades to function cmd1.trigfunc() + drop cascades to function cmd1.testcast(text) + drop cascades to cast from text to integer + NOTICE: snitch: AFTER any DROP SCHEMA .cmd1 + drop schema cmd2 cascade; + NOTICE: snitch: BEFORE any DROP SCHEMA .cmd2 + NOTICE: snitch: BEFORE DROP SCHEMA .cmd2 + NOTICE: snitch: AFTER any DROP SCHEMA .cmd2 + drop role regbob; + drop command trigger snitch_before; + drop command trigger snitch_after; + drop command trigger snitch_create_table; + drop command trigger snitch_create_table_as; + drop command trigger snitch_select_into; + drop command trigger snitch_create_seq; + drop command trigger snitch_create_view; + drop command trigger snitch_alter_table; + drop command trigger snitch_alter_seq; + drop command trigger snitch_alter_view; + drop command trigger snitch_drop_table; + drop command trigger snitch_create_function; + drop command trigger snitch_create_collation; + drop command trigger snitch_alter_operator; + drop command trigger snitch_create_domain; + drop command trigger snitch_alter_schema; + drop command trigger snitch_create_tsconfig; + drop command trigger snitch_create_tsdict; + drop command trigger snitch_create_tsparser; + drop command trigger snitch_create_tstmpl; + drop command trigger snitch_after_alter_function; + drop command trigger snitch_create_cast; + drop command trigger snitch_create_opclass; + drop command trigger snitch_create_trigger; + drop command trigger snitch_alter_trigger; + drop command trigger snitch_drop_trigger; + drop command trigger snitch_create_schema; + drop command trigger snitch_drop_schema; + drop command trigger snitch_create_aggregate; + drop command trigger snitch_alter_collation; + drop command trigger snitch_create_operator; + drop command trigger snitch_alter_domain; + drop command trigger snitch_create_type; + drop command trigger snitch_alter_type; + drop command trigger snitch_before_alter_function; + drop command trigger snitch_alter_conversion; + drop command trigger snitch_drop_agg; + drop command trigger snitch_drop_domain; + drop command trigger snitch_before_drop_tsconf; + drop command trigger snitch_before_drop_tsdict; + drop command trigger snitch_before_drop_tsparser; + drop command trigger snitch_before_drop_tstmpl; + drop command trigger snitch_vacuum; + drop command trigger snitch_reindex; *** a/src/test/regress/expected/sanity_check.out --- b/src/test/regress/expected/sanity_check.out *************** *** 93,98 **** SELECT relname, relhasindex --- 93,99 ---- pg_authid | t pg_cast | t pg_class | t + pg_cmdtrigger | t pg_collation | t pg_constraint | t pg_conversion | t *************** *** 164,170 **** SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (153 rows) -- -- another sanity check: every system catalog that has OIDs should have --- 165,171 ---- timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (154 rows) -- -- another sanity check: every system catalog that has OIDs should have *** a/src/test/regress/expected/type_sanity.out --- b/src/test/regress/expected/type_sanity.out *************** *** 128,143 **** WHERE p1.typinput = p2.oid AND p1.typtype in ('b', 'p') AND NOT -- As of 8.0, this check finds refcursor, which is borrowing -- other types' I/O routines SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 WHERE p1.typinput = p2.oid AND p1.typtype in ('b', 'p') AND NOT (p1.typelem != 0 AND p1.typlen < 0) AND NOT (p2.prorettype = p1.oid AND NOT p2.proretset) ORDER BY 1; ! oid | typname | oid | proname ! ------+-----------+-----+--------- ! 1790 | refcursor | 46 | textin ! (1 row) -- Varlena array types will point to array_in -- Exception as of 8.1: int2vector and oidvector have their own I/O routines --- 128,145 ---- -- As of 8.0, this check finds refcursor, which is borrowing -- other types' I/O routines + -- As of 9.2, this also finds cmdtrigger. SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 WHERE p1.typinput = p2.oid AND p1.typtype in ('b', 'p') AND NOT (p1.typelem != 0 AND p1.typlen < 0) AND NOT (p2.prorettype = p1.oid AND NOT p2.proretset) ORDER BY 1; ! oid | typname | oid | proname ! ------+-----------------+------+------------ ! 1790 | refcursor | 46 | textin ! 3838 | command_trigger | 2300 | trigger_in ! (2 rows) -- Varlena array types will point to array_in -- Exception as of 8.1: int2vector and oidvector have their own I/O routines *************** *** 169,174 **** ORDER BY 1; --- 171,177 ---- -- Check for bogus typoutput routines -- As of 8.0, this check finds refcursor, which is borrowing -- other types' I/O routines + -- In 9.2 cmdtrigger is borrowing trigger routines SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 WHERE p1.typoutput = p2.oid AND p1.typtype in ('b', 'p') AND NOT *************** *** 177,186 **** WHERE p1.typoutput = p2.oid AND p1.typtype in ('b', 'p') AND NOT (p2.oid = 'array_out'::regproc AND p1.typelem != 0 AND p1.typlen = -1))) ORDER BY 1; ! oid | typname | oid | proname ! ------+-----------+-----+--------- ! 1790 | refcursor | 47 | textout ! (1 row) SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 --- 180,190 ---- (p2.oid = 'array_out'::regproc AND p1.typelem != 0 AND p1.typlen = -1))) ORDER BY 1; ! oid | typname | oid | proname ! ------+-----------------+------+------------- ! 1790 | refcursor | 47 | textout ! 3838 | command_trigger | 2301 | trigger_out ! (2 rows) SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 *** a/src/test/regress/serial_schedule --- b/src/test/regress/serial_schedule *************** *** 62,67 **** test: create_aggregate --- 62,68 ---- test: create_cast test: constraints test: triggers + test: cmdtriggers test: inherit test: create_table_like test: typed_table *** /dev/null --- b/src/test/regress/sql/cmdtriggers.sql *************** *** 0 **** --- 1,304 ---- + -- + -- COMMAND TRIGGERS + -- + create or replace function any_snitch() + returns command_trigger + language plpgsql + as $$ + begin + -- can't output tg_objectid here that would break pg_regress + raise notice 'snitch: % any % %.%', tg_when, tg_tag, tg_schemaname, tg_objectname; + end; + $$; + + create or replace function snitch() + returns command_trigger + language plpgsql + as $$ + begin + -- can't output tg_objectid here that would break pg_regress + raise notice 'snitch: % % %.%', tg_when, tg_tag, tg_schemaname, tg_objectname; + end; + $$; + + create command trigger snitch_before before any command execute procedure any_snitch(); + create command trigger snitch_after_ after any command execute procedure any_snitch(); + + alter command trigger snitch_before set disable; + alter command trigger snitch_before set enable; + alter command trigger snitch_after_ rename to snitch_after; + + create command trigger snitch_create_table after create table execute procedure snitch(); + create command trigger snitch_create_table_as after create table as execute procedure snitch(); + create command trigger snitch_select_into after select into execute procedure snitch(); + create command trigger snitch_create_seq after create sequence execute procedure snitch(); + create command trigger snitch_create_view after create view execute procedure snitch(); + create command trigger snitch_alter_table after alter table execute procedure snitch(); + create command trigger snitch_alter_seq after alter sequence execute procedure snitch(); + create command trigger snitch_alter_view after alter view execute procedure snitch(); + create command trigger snitch_drop_table after drop table execute procedure snitch(); + create command trigger snitch_create_function after create function execute procedure snitch(); + create command trigger snitch_create_collation after create collation execute procedure snitch(); + create command trigger snitch_alter_operator after alter operator execute procedure snitch(); + create command trigger snitch_create_domain after create domain execute procedure snitch(); + create command trigger snitch_alter_schema after alter schema execute procedure snitch(); + create command trigger snitch_create_tsconfig after create text search configuration execute procedure snitch(); + create command trigger snitch_create_tsdict after create text search dictionary execute procedure snitch(); + create command trigger snitch_create_tsparser after create text search parser execute procedure snitch(); + create command trigger snitch_create_tstmpl after create text search template execute procedure snitch(); + create command trigger snitch_after_alter_function after alter function execute procedure snitch(); + create command trigger snitch_create_cast after create cast execute procedure snitch(); + create command trigger snitch_create_opclass after create operator class execute procedure snitch(); + + create command trigger snitch_create_trigger before create trigger execute procedure snitch(); + create command trigger snitch_alter_trigger before alter trigger execute procedure snitch(); + create command trigger snitch_drop_trigger before drop trigger execute procedure snitch(); + create command trigger snitch_create_schema before create schema execute procedure snitch(); + create command trigger snitch_drop_schema before drop schema execute procedure snitch(); + create command trigger snitch_create_aggregate before create aggregate execute procedure snitch(); + create command trigger snitch_alter_collation before alter collation execute procedure snitch(); + create command trigger snitch_create_operator before create operator execute procedure snitch(); + create command trigger snitch_alter_domain before alter domain execute procedure snitch(); + create command trigger snitch_create_type before create type execute procedure snitch(); + create command trigger snitch_alter_type before alter type execute procedure snitch(); + create command trigger snitch_before_alter_function before alter function execute procedure snitch(); + create command trigger snitch_alter_conversion before alter conversion execute procedure snitch(); + create command trigger snitch_drop_agg before drop aggregate execute procedure snitch(); + create command trigger snitch_drop_domain before drop domain execute procedure snitch(); + + create command trigger snitch_before_drop_tsconf before drop text search configuration execute procedure snitch(); + create command trigger snitch_before_drop_tsdict before drop text search dictionary execute procedure snitch(); + create command trigger snitch_before_drop_tsparser before drop text search parser execute procedure snitch(); + create command trigger snitch_before_drop_tstmpl before drop text search template execute procedure snitch(); + + create command trigger snitch_vacuum before vacuum execute procedure snitch(); + create command trigger snitch_reindex before reindex execute procedure snitch(); + + create schema cmd; + create schema cmd2; + create role regbob; + + create table cmd.foo(id bigserial primary key); + create view cmd.v as select * from cmd.foo; + alter table cmd.foo add column t text; + + create table cmd.bar as select 1; + drop table cmd.bar; + select 1 into cmd.bar; + drop table cmd.bar; + + create table test9 (id int, stuff text); + alter table test9 rename to test; + alter table test set schema cmd; + alter table cmd.test rename column stuff to things; + alter table cmd.test add column alpha text; + alter table cmd.test alter column alpha set data type varchar(300); + alter table cmd.test alter column alpha set default 'test'; + alter table cmd.test alter column alpha drop default; + alter table cmd.test alter column alpha set statistics 78; + alter table cmd.test alter column alpha set storage plain; + alter table cmd.test alter column alpha set not null; + alter table cmd.test alter column alpha drop not null; + alter table cmd.test alter column alpha set (n_distinct = -0.78); + alter table cmd.test alter column alpha reset (n_distinct); + alter table cmd.test drop column alpha; + alter table cmd.test add check (id > 2) not valid; + alter table cmd.test add check (id < 800000); + alter table cmd.test set without cluster; + alter table cmd.test set with oids; + alter table cmd.test set without oids; + + create sequence test_seq_; + alter sequence test_seq_ owner to regbob; + alter sequence test_seq_ rename to test_seq; + alter sequence test_seq set schema cmd; + alter sequence cmd.test_seq start with 3; + alter sequence cmd.test_seq restart with 4; + alter sequence cmd.test_seq minvalue 3; + alter sequence cmd.test_seq no minvalue; + alter sequence cmd.test_seq maxvalue 900000; + alter sequence cmd.test_seq no maxvalue; + alter sequence cmd.test_seq cache 876; + alter sequence cmd.test_seq cycle; + alter sequence cmd.test_seq no cycle; + + create view view_test as select id, things from cmd.test; + alter view view_test owner to regbob; + alter view view_test rename to view_test2; + alter view view_test2 set schema cmd; + alter view cmd.view_test2 alter column id set default 9; + alter view cmd.view_test2 alter column id drop default; + + cluster cmd.foo using foo_pkey; + vacuum cmd.foo; + vacuum; + reindex table cmd.foo; + + set session_replication_role to replica; + create table cmd.bar(); + reset session_replication_role; + + create index idx_foo on cmd.foo(t); + reindex index cmd.idx_foo; + drop index cmd.idx_foo; + + create function fun(int) returns text language sql + as $$ select t from cmd.foo where id = $1; $$; + + alter function fun(int) strict; + alter function fun(int) rename to notfun; + alter function notfun(int) set schema cmd; + alter function cmd.notfun(int) owner to regbob; + alter function cmd.notfun(int) cost 77; + drop function cmd.notfun(int); + + create function cmd.plus1(int) returns bigint language sql + as $$ select $1::bigint + 1; $$; + + create operator cmd.+!(procedure = cmd.plus1, leftarg = int); + alter operator cmd.+!(int, NONE) set schema public; + drop operator public.+!(int, NONE); + + create aggregate cmd.avg (float8) + ( + sfunc = float8_accum, + stype = float8[], + finalfunc = float8_avg, + initcond = '{0,0,0}' + ); + alter aggregate cmd.avg(float8) set schema public; + drop aggregate public.avg(float8); + + create collation cmd.french (LOCALE = 'fr_FR'); + alter collation cmd.french rename to francais; + + create type cmd.compfoo AS (f1 int, f2 text); + alter type cmd.compfoo add attribute f3 text; + + create type cmd.type_test AS (a integer, b integer, c text); + alter type cmd.type_test owner to regbob; + alter type cmd.type_test rename to type_test2; + alter type cmd.type_test2 set schema public; + alter type public.type_test2 rename attribute a to z; + alter type public.type_test2 add attribute alpha text; + alter type public.type_test2 alter attribute alpha set data type char(90); + alter type public.type_test2 drop attribute alpha; + + drop type cmd.compfoo; + drop type public.type_test2; + + create type cmd.bug_status as enum ('new', 'open', 'closed'); + alter type cmd.bug_status add value 'wontfix'; + + create domain cmd.us_postal_code as text check(value ~ '^\d{5}$' or value ~ '^\d{5}-\d{4}$'); + alter domain cmd.us_postal_code set not null; + alter domain cmd.us_postal_code set default 90210; + alter domain cmd.us_postal_code drop default; + alter domain cmd.us_postal_code drop not null; + alter domain cmd.us_postal_code add constraint dummy_constraint check (value ~ '^\d{8}$'); + alter domain cmd.us_postal_code drop constraint dummy_constraint; + alter domain cmd.us_postal_code owner to regbob; + alter domain cmd.us_postal_code set schema cmd2; + drop domain cmd2.us_postal_code; + + create function cmd.trigfunc() returns trigger language plpgsql as + $$ begin raise notice 'trigfunc'; end;$$; + + create trigger footg before update on cmd.foo for each row execute procedure cmd.trigfunc(); + alter trigger footg on cmd.foo rename to foo_trigger; + drop trigger foo_trigger on cmd.foo; + + create conversion test for 'utf8' to 'sjis' from utf8_to_sjis; + create default conversion test2 for 'utf8' to 'sjis' from utf8_to_sjis; + alter conversion test2 rename to test3; + drop conversion test3; + drop conversion test; + + create operator class test_op_class + for type anyenum using hash as + operator 1 =, + function 1 hashenum(anyenum); + + create text search configuration test (parser = "default"); + + create text search dictionary test_stem ( + template = snowball, + language = 'english', stopwords = 'english' + ); + alter text search dictionary test_stem (StopWords = dutch ); + + create text search parser test_parser ( + start = prsd_start, + gettoken = prsd_nexttoken, + end = prsd_end, + lextypes = prsd_lextype, + headline = prsd_headline + ); + + create text search template test_template ( + init = dsimple_init, + lexize = dsimple_lexize + ); + + drop text search configuration test; + drop text search dictionary test_stem; + drop text search parser test_parser; + drop text search template test_template; + + create function cmd.testcast(text) returns int4 language plpgsql as $$begin return 4::int4;end;$$; + create cast (text as int4) with function cmd.testcast(text) as assignment; + + alter schema cmd rename to cmd1; + + drop schema cmd1 cascade; + drop schema cmd2 cascade; + drop role regbob; + + drop command trigger snitch_before; + drop command trigger snitch_after; + + drop command trigger snitch_create_table; + drop command trigger snitch_create_table_as; + drop command trigger snitch_select_into; + drop command trigger snitch_create_seq; + drop command trigger snitch_create_view; + drop command trigger snitch_alter_table; + drop command trigger snitch_alter_seq; + drop command trigger snitch_alter_view; + drop command trigger snitch_drop_table; + drop command trigger snitch_create_function; + drop command trigger snitch_create_collation; + drop command trigger snitch_alter_operator; + drop command trigger snitch_create_domain; + drop command trigger snitch_alter_schema; + drop command trigger snitch_create_tsconfig; + drop command trigger snitch_create_tsdict; + drop command trigger snitch_create_tsparser; + drop command trigger snitch_create_tstmpl; + drop command trigger snitch_after_alter_function; + drop command trigger snitch_create_cast; + drop command trigger snitch_create_opclass; + + drop command trigger snitch_create_trigger; + drop command trigger snitch_alter_trigger; + drop command trigger snitch_drop_trigger; + drop command trigger snitch_create_schema; + drop command trigger snitch_drop_schema; + drop command trigger snitch_create_aggregate; + drop command trigger snitch_alter_collation; + drop command trigger snitch_create_operator; + drop command trigger snitch_alter_domain; + drop command trigger snitch_create_type; + drop command trigger snitch_alter_type; + drop command trigger snitch_before_alter_function; + drop command trigger snitch_alter_conversion; + drop command trigger snitch_drop_agg; + drop command trigger snitch_drop_domain; + + drop command trigger snitch_before_drop_tsconf; + drop command trigger snitch_before_drop_tsdict; + drop command trigger snitch_before_drop_tsparser; + drop command trigger snitch_before_drop_tstmpl; + + drop command trigger snitch_vacuum; + drop command trigger snitch_reindex; *** a/src/test/regress/sql/triggers.sql --- b/src/test/regress/sql/triggers.sql *************** *** 962,968 **** SELECT * FROM city_view; DROP TABLE city_table CASCADE; DROP TABLE country_table; - -- Test pg_trigger_depth() create table depth_a (id int not null primary key); --- 962,967 ---- *** a/src/test/regress/sql/type_sanity.sql --- b/src/test/regress/sql/type_sanity.sql *************** *** 104,109 **** WHERE p1.typinput = p2.oid AND p1.typtype in ('b', 'p') AND NOT --- 104,110 ---- -- As of 8.0, this check finds refcursor, which is borrowing -- other types' I/O routines + -- As of 9.2, this also finds cmdtrigger. SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 WHERE p1.typinput = p2.oid AND p1.typtype in ('b', 'p') AND NOT *************** *** 130,135 **** ORDER BY 1; --- 131,137 ---- -- As of 8.0, this check finds refcursor, which is borrowing -- other types' I/O routines + -- In 9.2 cmdtrigger is borrowing trigger routines SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 WHERE p1.typoutput = p2.oid AND p1.typtype in ('b', 'p') AND NOT