*** a/doc/src/sgml/ref/create_trigger.sgml --- b/doc/src/sgml/ref/create_trigger.sgml *************** *** 29,40 **** CREATE [ CONSTRAINT ] TRIGGER name --- 29,109 ---- [ WHEN ( condition ) ] EXECUTE PROCEDURE function_name ( arguments ) + CREATE TRIGGER name { BEFORE | AFTER } ANY COMMAND + EXECUTE PROCEDURE function_name () + + CREATE TRIGGER name { BEFORE | AFTER } COMMAND command [, ... ] + EXECUTE PROCEDURE function_name () + where event can be one of: INSERT UPDATE [ OF column_name [, ... ] ] DELETE TRUNCATE + + and where command can be one of: + + CREATE SCHEMA + CREATE EXTENSION + CREATE FUNCTION + CREATE TABLE + CREATE FOREIGN TABLE + CREATE INDEX + CREATE SEQUENCE + CREATE VIEW + CREATE AGGREGATE + CREATE OPERATOR + CREATE COLLATION + CREATE TEXT SEARCH PARSER + CREATE TEXT SEARCH DICTIONARY + CREATE TEXT SEARCH TEMPLATE + CREATE TEXT SEARCH CONFIGURATION + CREATE TYPE + CREATE DOMAIN + ALTER TABLE + ALTER SCHEMA + ALTER EXTENSION + ALTER FUNCTION + ALTER TABLE + ALTER AGGREGATE + ALTER OPERATOR + ALTER COLLATION + ALTER TEXT_P SEARCH PARSER + ALTER TEXT_P SEARCH DICTIONARY + ALTER TEXT_P SEARCH TEMPLATE + ALTER TEXT_P SEARCH CONFIGURATION + ALTER TYPE_P + ALTER DOMAIN_P + ALTER TRIGGER + DROP TABLE + DROP SEQUENCE + DROP VIEW + DROP INDEX + DROP FOREIGN TABLE + DROP TYPE + DROP DOMAIN + DROP COLLATION + DROP CONVERSION + DROP SCHEMA + DROP EXTENSION + DROP TEXT SEARCH PARSER + DROP TEXT SEARCH DICTIONARY + DROP TEXT SEARCH TEMPLATE + DROP TEXT SEARCH CONFIGURATION + DROP LANGUAGE + DROP FOREIGN DATA WRAPPER + DROP SERVER + DROP TRIGGER + DROP ASSERTION + DROP OPERATOR CLASS + DROP OPERATOR FAMILY + DROP FUNCTION + DROP AGGREGATE + DROP OPERATOR + DROP CAST + DROP RULE + *************** *** 42,51 **** CREATE [ CONSTRAINT ] TRIGGER name Description ! CREATE TRIGGER creates a new trigger. The ! trigger will be associated with the specified table or view and will ! execute the specified function function_name when certain events occur. --- 111,127 ---- Description ! CREATE TRIGGER creates a new trigger. The trigger will ! be associated with the specified table, view or command and will execute ! the specified ! function function_name when ! certain events occur. ! ! ! ! The command trigger can be specified to fire before or after the command ! is executed, or instead of executing the command. A command trigger's ! function must return void. *************** *** 251,256 **** UPDATE OF column_name1 [, column_name2 + command + + + The tag of the command the trigger is for. + + + + + referenced_table_name *************** *** 334,340 **** UPDATE OF column_name1 [, column_name2 A user-supplied function that is declared as taking no arguments and returning type trigger, which is executed when ! the trigger fires. --- 419,447 ---- A user-supplied function that is declared as taking no arguments and returning type trigger, which is executed when ! the trigger fires, for table and view triggers. ! ! ! In the case of a COMMAND trigger, the user-supplied ! function must be declared as taking 5 arguments of type text, text, ! oid, text, text and returning void. ! ! ! If your command trigger is implemented in C then it ! will be called with yet another argument, of ! type internal, which is a pointer to ! the Node * parse tree. ! ! ! The command trigger function is called with the ! parameters tg_when (which is set to either 'BEFORE' ! or 'AFTER'), command ! tag, objectid (can be null in case of a ! BEFORE CREATE or an AFTER DROP command trigger ! timing), schemaname (can be null for objects not ! living in a schema, and for sequences due to an implementation limit) ! and object name (can be null for any command ! triggers). *************** *** 469,474 **** CREATE TRIGGER view_insert --- 576,617 ---- FOR EACH ROW EXECUTE PROCEDURE view_insert_row(); + + Forbids the execution of any DDL command: + + + CREATE OR REPLACE FUNCTION abort_any_command + (tg_when text, cmd_tag text, objectid oid, schemaname text, objectname text) + RETURNS void LANGUAGE plpgsql AS $$ + BEGIN + RAISE EXCEPTION 'command % is disabled' % cmd_tag; + END; + $$; + + CREATE TRIGGER abort_ddl + BEFORE COMMAND CREATE TABLE + 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 + (tg_when text, cmd_tag text, objectid oid, schemaname text, objectname text) + RETURNS bool LANGUAGE plpgsql AS $$ + BEGIN + IF substring(objectname, 0, 4) NOT IN ('ab_', 'cz_', 'fr_') + THEN + RAISE EXCEPTION 'invalid relation name: %', objectname; + END IF; + END; + $$; + + CREATE TRIGGER check_style + BEFORE COMMAND CREATE TABLE + EXECUTE PROCEDURE enforce_local_style(); + *************** *** 531,536 **** CREATE TRIGGER view_insert --- 674,684 ---- + The ability to run triggers on commands is PostgreSQL + extension of the SQL standard. + + + The ability to specify multiple actions for a single trigger using OR is a PostgreSQL extension of the SQL standard. *************** *** 548,553 **** CREATE TRIGGER view_insert --- 696,707 ---- standard. + + CREATE TRIGGER ON COMMAND is a + PostgreSQL extension of the SQL + standard. + + *** a/doc/src/sgml/ref/drop_trigger.sgml --- b/doc/src/sgml/ref/drop_trigger.sgml *************** *** 22,27 **** PostgreSQL documentation --- 22,82 ---- DROP TRIGGER [ IF EXISTS ] name ON table [ CASCADE | RESTRICT ] + DROP TRIGGER [ IF EXISTS ] name ON COMMAND command [, ... ] [ CASCADE | RESTRICT ] + DROP TRIGGER [ IF EXISTS ] name ON ANY COMMAND [ CASCADE | RESTRICT ] + + where command can be one of: + + CREATE DATABASE + DROP DATABASE + CREATE SCHEMA + CREATE EXTENSION + CREATE TABLESPACE + CREATE FUNCTION + CREATE TABLE + CREATE FOREIGN TABLE + CREATE INDEX + CREATE SEQUENCE + CREATE VIEW + CREATE AGGREGATE + CREATE OPERATOR + CREATE COLLATION + CREATE TEXT SEARCH PARSER + CREATE TEXT SEARCH DICTIONARY + CREATE TEXT SEARCH TEMPLATE + CREATE TEXT SEARCH CONFIGURATION + CREATE TYPE + CREATE DOMAIN + ALTER TABLE + DROP TABLESPACE + DROP TABLE + DROP SEQUENCE + DROP VIEW + DROP INDEX + DROP FOREIGN TABLE + DROP TYPE + DROP DOMAIN + DROP COLLATION + DROP CONVERSION + DROP SCHEMA + DROP EXTENSION + DROP TEXT SEARCH PARSER + DROP TEXT SEARCH DICTIONARY + DROP TEXT SEARCH TEMPLATE + DROP TEXT SEARCH CONFIGURATION + DROP LANGUAGE + DROP FOREIGN DATA WRAPPER + DROP SERVER + DROP TRIGGER + DROP ASSERTION + DROP OPERATOR CLASS + DROP OPERATOR FAMILY + DROP FUNCTION + DROP AGGREGATE + DROP OPERATOR + DROP CAST + DROP RULE + *************** *** 29,37 **** DROP TRIGGER [ IF EXISTS ] name ON Description ! DROP TRIGGER removes an existing ! trigger definition. To execute this command, the current ! user must be the owner of the table for which the trigger is defined. --- 84,93 ---- Description ! DROP TRIGGER removes an existing trigger definition. ! To execute this command, the current user must be the owner of the table ! for which the trigger is defined, or a database owner in case of a ! command trigger. *** 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 *************** *** 25,30 **** --- 25,31 ---- #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" *************** *** 52,57 **** --- 53,59 ---- #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" *************** *** 157,163 **** static const Oid object_classes[MAX_OCLASS] = { ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ DefaultAclRelationId, /* OCLASS_DEFACL */ ! ExtensionRelationId /* OCLASS_EXTENSION */ }; --- 159,166 ---- ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ DefaultAclRelationId, /* OCLASS_DEFACL */ ! ExtensionRelationId, /* OCLASS_EXTENSION */ ! CmdTriggerRelationId /* OCLASS_CMDTRIGGER */ }; *************** *** 1067,1072 **** doDeletion(const ObjectAddress *object) --- 1070,1079 ---- break; } + case OCLASS_CMDTRIGGER: + RemoveCmdTriggerById(object->objectId); + break; + case OCLASS_PROC: RemoveFunctionById(object->objectId); break; *************** *** 2188,2193 **** getObjectClass(const ObjectAddress *object) --- 2195,2203 ---- case ExtensionRelationId: return OCLASS_EXTENSION; + + case CmdTriggerRelationId: + return OCLASS_CMDTRIGGER; } /* shouldn't get here */ *************** *** 2822,2827 **** getObjectDescription(const ObjectAddress *object) --- 2832,2876 ---- 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/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, *** 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 *************** *** 1325,1335 **** 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: --- 1325,1335 ---- switch (sdepForm->classid) { case CollationRelationId: ! AlterCollationOwner_oid(sdepForm->objid, newrole, NULL); break; case ConversionRelationId: ! AlterConversionOwner_oid(sdepForm->objid, newrole, NULL); break; case TypeRelationId: *************** *** 1355,1361 **** shdepReassignOwned(List *roleids, Oid newrole) break; case ProcedureRelationId: ! AlterFunctionOwner_oid(sdepForm->objid, newrole); break; case LanguageRelationId: --- 1355,1361 ---- 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 *************** *** 676,682 **** GenerateTypeDependencies(Oid typeNamespace, * ALTER TYPE RENAME TO command. */ void ! RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace) { Relation pg_type_desc; HeapTuple tuple; --- 676,683 ---- * ALTER TYPE RENAME TO command. */ void ! RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace, ! CommandContext cmd) { Relation pg_type_desc; HeapTuple tuple; *************** *** 703,708 **** RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace) --- 704,719 ---- (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); *************** *** 719,727 **** RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace) { char *arrname = makeArrayTypeName(newTypeName, typeNamespace); ! RenameTypeInternal(arrayOid, arrname, typeNamespace); pfree(arrname); } } --- 730,745 ---- { 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); + } } *************** *** 822,828 **** 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 --- 840,846 ---- 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,19 **** 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 \ dbcommands.o define.o discard.o dropcmds.o explain.o extension.o \ foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ --- 12,19 ---- top_builddir = ../../.. include $(top_builddir)/src/Makefile.global ! OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o cmdtrigger.o \ ! comment.o collationcmds.o constraint.o conversioncmds.o copy.o \ dbcommands.o define.o discard.o dropcmds.o explain.o extension.o \ foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o operatorcmds.o opclasscmds.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 = 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 = (char *)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,64 **** 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_CONVERSION: ! RenameConversion(stmt->object, stmt->newname); break; case OBJECT_DATABASE: --- 48,72 ---- void ExecRenameStmt(RenameStmt *stmt) { + CommandContextData cmd; + InitCommandContext(&cmd, (Node *)stmt, false); + 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_CONVERSION: ! RenameConversion(stmt->object, stmt->newname, &cmd); ! break; ! ! case OBJECT_CMDTRIGGER: ! RenameCmdTrigger(stmt->object, stmt->subname, stmt->newname); break; case OBJECT_DATABASE: *************** *** 74,80 **** ExecRenameStmt(RenameStmt *stmt) break; case OBJECT_FUNCTION: ! RenameFunction(stmt->object, stmt->objarg, stmt->newname); break; case OBJECT_LANGUAGE: --- 82,88 ---- break; case OBJECT_FUNCTION: ! RenameFunction(stmt->object, stmt->objarg, stmt->newname, &cmd); break; case OBJECT_LANGUAGE: *************** *** 94,100 **** ExecRenameStmt(RenameStmt *stmt) break; case OBJECT_SCHEMA: ! RenameSchema(stmt->subname, stmt->newname); break; case OBJECT_TABLESPACE: --- 102,108 ---- break; case OBJECT_SCHEMA: ! RenameSchema(stmt->subname, stmt->newname, &cmd); break; case OBJECT_TABLESPACE: *************** *** 106,112 **** ExecRenameStmt(RenameStmt *stmt) case OBJECT_VIEW: case OBJECT_INDEX: case OBJECT_FOREIGN_TABLE: ! RenameRelation(stmt); break; case OBJECT_COLUMN: --- 114,120 ---- case OBJECT_VIEW: case OBJECT_INDEX: case OBJECT_FOREIGN_TABLE: ! RenameRelation(stmt, &cmd); break; case OBJECT_COLUMN: *************** *** 115,142 **** ExecRenameStmt(RenameStmt *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: --- 123,150 ---- 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: *************** *** 152,191 **** 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: --- 160,202 ---- void ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) { + CommandContextData cmd; + InitCommandContext(&cmd, (Node *)stmt, false); + 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: *************** *** 196,219 **** 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: --- 207,230 ---- 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: *************** *** 235,240 **** ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) --- 246,254 ---- * * 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) *************** *** 271,285 **** 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: --- 285,299 ---- } 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: *************** *** 349,355 **** 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; --- 363,369 ---- 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; *************** *** 419,424 **** AlterObjectNamespace(Relation rel, int oidCacheId, int nameCacheId, --- 433,448 ---- getObjectDescriptionOids(classId, objid), get_namespace_name(nspOid)))); + /* Call BEFORE ALTER OBJECT triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = objid; + cmd->objectname = 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)); *************** *** 441,446 **** AlterObjectNamespace(Relation rel, int oidCacheId, int nameCacheId, --- 465,476 ---- changeDependencyFor(classId, objid, NamespaceRelationId, oldNspOid, nspOid); + /* Call AFTER ALTER OBJECT triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->schemaname = get_namespace_name(nspOid); + ExecAfterCommandTriggers(cmd); + } return oldNspOid; } *************** *** 453,471 **** 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: --- 483,504 ---- ExecAlterOwnerStmt(AlterOwnerStmt *stmt) { Oid newowner = get_role_oid(stmt->newowner, false); + CommandContextData cmd; + + InitCommandContext(&cmd, (Node *)stmt, false); 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: *************** *** 473,479 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt) break; case OBJECT_FUNCTION: ! AlterFunctionOwner(stmt->object, stmt->objarg, newowner); break; case OBJECT_LANGUAGE: --- 506,512 ---- break; case OBJECT_FUNCTION: ! AlterFunctionOwner(stmt->object, stmt->objarg, newowner, &cmd); break; case OBJECT_LANGUAGE: *************** *** 489,495 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt) AlterOperatorOwner(stmt->object, (TypeName *) linitial(stmt->objarg), (TypeName *) lsecond(stmt->objarg), ! newowner); break; case OBJECT_OPCLASS: --- 522,528 ---- AlterOperatorOwner(stmt->object, (TypeName *) linitial(stmt->objarg), (TypeName *) lsecond(stmt->objarg), ! newowner, &cmd); break; case OBJECT_OPCLASS: *************** *** 501,507 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt) break; case OBJECT_SCHEMA: ! AlterSchemaOwner(strVal(linitial(stmt->object)), newowner); break; case OBJECT_TABLESPACE: --- 534,540 ---- break; case OBJECT_SCHEMA: ! AlterSchemaOwner(strVal(linitial(stmt->object)), newowner, &cmd); break; case OBJECT_TABLESPACE: *************** *** 510,524 **** 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: --- 543,557 ---- 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: *** a/src/backend/commands/cluster.c --- b/src/backend/commands/cluster.c *************** *** 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); } --- 1486,1498 ---- 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,731 ---- + /*------------------------------------------------------------------------- + * + * 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 *command, 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. + */ + void + CreateCmdTrigger(CreateCmdTrigStmt *stmt, const char *queryString) + { + Relation tgrel; + ListCell *c; + /* cmd trigger args: when, cmd_tag, objectId, schemaname, objectname [,parsetree] */ + Oid fargtypes[5] = {TEXTOID, TEXTOID, OIDOID, TEXTOID, TEXTOID}; + Oid fargtypes_c[6] = {TEXTOID, TEXTOID, OIDOID, TEXTOID, TEXTOID, INTERNALOID}; + Oid funcoid; + 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, 6, fargtypes_c, true); + if (funcoid == InvalidOid) + funcoid = LookupFuncName(stmt->funcname, 5, fargtypes, false); + + /* 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); + + foreach(c, stmt->command) + { + Oid trigoid; + A_Const *con = (A_Const *) lfirst(c); + char *command = strVal(&con->val); + + /* + * 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(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(command, "CREATE INDEX") == 0)) + ereport(WARNING, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CREATE INDEX CONCURRENTLY is not supported"), + errdetail("The command trigger will not get fired."))); + + /* + * 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(command, stmt->trigname, tgrel); + + if (funcrettype != VOIDOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("function \"%s\" must return type \"void\"", + NameListToString(stmt->funcname)))); + + trigoid = InsertCmdTriggerTuple(tgrel, command, stmt->trigname, funcoid, stmt->timing); + } + heap_close(tgrel, RowExclusiveLock); + } + + /* + * DropTrigger - drop an individual trigger by name + */ + void + DropCmdTrigger(DropCmdTrigStmt *stmt) + { + ListCell *c; + + CheckCmdTriggerPrivileges(); + + foreach(c, stmt->command) + { + ObjectAddress object; + A_Const *con = (A_Const *) lfirst(c); + char *command = strVal(&con->val); + + object.classId = CmdTriggerRelationId; + object.objectId = get_cmdtrigger_oid(command, stmt->trigname, + stmt->missing_ok); + object.objectSubId = 0; + + if (!OidIsValid(object.objectId)) + { + ereport(NOTICE, + (errmsg("trigger \"%s\" for command \"%s\" does not exist, skipping", + stmt->trigname, command))); + break; + } + + /* + * Do the deletion + */ + performDeletion(&object, stmt->behavior, 0); + } + } + + /* + * 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[2]; + 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_ctgcommand, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->command)); + ScanKeyInit(&skey[1], + Anum_pg_cmdtrigger_ctgname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->trigname)); + + tgscan = systable_beginscan(tgrel, CmdTriggerCommandNameIndexId, true, + SnapshotNow, 2, skey); + + tup = systable_getnext(tgscan); + + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("trigger \"%s\" for command \"%s\" does not exist, skipping", + stmt->trigname, stmt->command))); + + /* 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 *trigname, const char *newname) + { + SysScanDesc tgscan; + ScanKeyData skey[2]; + HeapTuple tup; + Relation rel; + Form_pg_cmdtrigger cmdForm; + char *command; + + CheckCmdTriggerPrivileges(); + + Assert(list_length(name) == 1); + command = strVal((Value *)linitial(name)); + + rel = heap_open(CmdTriggerRelationId, RowExclusiveLock); + + //FIXME: need a row level lock here + /* newname must be available */ + check_cmdtrigger_name(command, newname, rel); + + /* get existing tuple */ + ScanKeyInit(&skey[0], + Anum_pg_cmdtrigger_ctgcommand, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(command)); + ScanKeyInit(&skey[1], + Anum_pg_cmdtrigger_ctgname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(trigname)); + + tgscan = systable_beginscan(rel, CmdTriggerCommandNameIndexId, true, + SnapshotNow, 2, skey); + + tup = systable_getnext(tgscan); + + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("trigger \"%s\" for command \"%s\" does not exist, skipping", + trigname, command))); + + /* 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 *command, const char *trigname, bool missing_ok) + { + Relation tgrel; + ScanKeyData skey[2]; + 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_ctgcommand, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(command)); + ScanKeyInit(&skey[1], + Anum_pg_cmdtrigger_ctgname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(trigname)); + + tgscan = systable_beginscan(tgrel, CmdTriggerCommandNameIndexId, true, + SnapshotNow, 1, skey); + + tup = systable_getnext(tgscan); + + if (!HeapTupleIsValid(tup)) + { + if (!missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("trigger \"%s\" for command \"%s\" does not exist, skipping", + trigname, command))); + 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 *command, const char *trigname, Relation tgrel) + { + SysScanDesc tgscan; + ScanKeyData skey[2]; + HeapTuple tuple; + + ScanKeyInit(&skey[0], + Anum_pg_cmdtrigger_ctgcommand, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(command)); + ScanKeyInit(&skey[1], + Anum_pg_cmdtrigger_ctgname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(trigname)); + + tgscan = systable_beginscan(tgrel, CmdTriggerCommandNameIndexId, true, + SnapshotNow, 2, skey); + + tuple = systable_getnext(tgscan); + + elog(DEBUG1, "check_cmdtrigger_name(%s, %s)", command, trigname); + + if (HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("trigger \"%s\" for command \"%s\" already exists", + trigname, command))); + 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. + */ + bool + ListCommandTriggers(CommandContext cmd) + { + int count = 0; + Relation rel, irel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + 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(cmd->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: + cmd->before = lappend_oid(cmd->before, form->ctgfoid); + break; + + case CMD_TRIGGER_FIRED_AFTER: + 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 bool + call_cmdtrigger_procedure(CommandContext cmd, + RegProcedure proc, + const char *when, + MemoryContext per_command_context) + { + FmgrInfo flinfo; + FunctionCallInfoData fcinfo; + PgStat_FunctionCallUsage fcusage; + Datum result; + HeapTuple procedureTuple; + Form_pg_proc procedureStruct; + int nargs = 5; + + fmgr_info_cxt(proc, &flinfo, per_command_context); + + /* we need the procedure's language here to know how many args to call it + * with + */ + procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(proc)); + if (!HeapTupleIsValid(procedureTuple)) + elog(ERROR, "cache lookup failed for function %u", proc); + procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple); + + if (procedureStruct->prolang == ClanguageId) + nargs = 6; + + ReleaseSysCache(procedureTuple); + + /* Can't use OidFunctionCallN because we might get a NULL result */ + InitFunctionCallInfoData(fcinfo, &flinfo, nargs, InvalidOid, NULL, NULL); + + fcinfo.arg[0] = PointerGetDatum(cstring_to_text(pstrdup(when))); + + /* We support triggers ON ANY COMMAND so all fields here are nullable. */ + if (cmd->tag != NULL) + fcinfo.arg[1] = PointerGetDatum(cstring_to_text(pstrdup(cmd->tag))); + + fcinfo.arg[2] = ObjectIdGetDatum(cmd->objectId); + + if (cmd->schemaname != NULL) + fcinfo.arg[3] = PointerGetDatum(cstring_to_text(pstrdup(cmd->schemaname))); + + if (cmd->objectname != NULL) + fcinfo.arg[4] = PointerGetDatum(cstring_to_text(pstrdup(cmd->objectname))); + + fcinfo.argnull[0] = false; + fcinfo.argnull[1] = cmd->tag == NULL; + fcinfo.argnull[2] = cmd->objectId == InvalidOid; + fcinfo.argnull[3] = cmd->schemaname == NULL; + fcinfo.argnull[4] = cmd->objectname == NULL; + + if (nargs == 6) + { + fcinfo.arg[5] = PointerGetDatum(cmd->parsetree); + fcinfo.argnull[5] = false; + } + + pgstat_init_function_usage(&fcinfo, &fcusage); + + result = FunctionCallInvoke(&fcinfo); + + pgstat_end_function_usage(&fcusage, true); + + + if (!fcinfo.isnull && DatumGetBool(result) == false) + return false; + return true; + } + + /* + * A BEFORE command trigger can choose to "abort" the command by returning + * false. This function is called by ExecBeforeOrInsteadOfCommandTriggers() so + * is not exposed to other modules. + */ + static void + exec_command_triggers_internal(CommandContext cmd, List *procs, const char *when) + { + MemoryContext per_command_context, oldContext; + ListCell *cell; + + /* + * Do the functions evaluation in a per-command memory context, so that + * leaked memory will be reclaimed once per command. + */ + per_command_context = + AllocSetContextCreate(CurrentMemoryContext, + "CommandTriggerContext", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + oldContext = MemoryContextSwitchTo(per_command_context); + MemoryContextReset(per_command_context); + + foreach(cell, procs) + { + Oid proc = lfirst_oid(cell); + call_cmdtrigger_procedure(cmd, (RegProcedure)proc, when, per_command_context); + } + MemoryContextSwitchTo(oldContext); + } + + /* + * Routine to call to setup a CommandContextData structure. + * + * This ensures that cmd->before and cmd->after are set to meaningful values, + * always NIL when list_triggers is false. + * + * In case of ANY trigger init we don't want to list triggers associated with + * the real command tag, we have another API to do that, see + * ExecBeforeAnyCommandTriggers() and ExecAfterAnyCommandTriggers(). + */ + void + InitCommandContext(CommandContext cmd, const Node *stmt, bool list_any_triggers) + { + 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; + + if (list_any_triggers) + { + /* list procedures for "ANY" command */ + char *tag = cmd->tag; + + cmd->tag = "ANY"; + ListCommandTriggers(cmd); + cmd->tag = tag; + } + else + ListCommandTriggers(cmd); + } + + /* + * 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. + */ + bool + CommandFiresTriggers(CommandContext cmd) + { + return cmd != NULL && (cmd->before != NIL || cmd->after != NIL); + } + + /* + * 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) + { + return cmd != NULL && cmd->after != NIL; + } + + /* + * 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 && cmd->before != NIL) + exec_command_triggers_internal(cmd, cmd->before, "BEFORE"); + } + + void + ExecAfterCommandTriggers(CommandContext cmd) + { + if (cmd != NULL && cmd->after != NIL) + exec_command_triggers_internal(cmd, cmd->after, "AFTER"); + } *** 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 = 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 = (char *)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 = 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 *************** *** 121,127 **** CreateConversionCommand(CreateConversionStmt *stmt) * Rename conversion */ void ! RenameConversion(List *name, const char *newname) { Oid conversionOid; Oid namespaceOid; --- 121,127 ---- * Rename conversion */ void ! RenameConversion(List *name, const char *newname, CommandContext cmd) { Oid conversionOid; Oid namespaceOid; *************** *** 159,164 **** RenameConversion(List *name, const char *newname) --- 159,174 ---- aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); + /* Call BEFORE ALTER CONVERSION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = 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; --- 176,195 ---- heap_close(rel, NoLock); heap_freetuple(tup); + + /* Call AFTER ALTER CONVERSION triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = (char *)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); } --- 198,204 ---- 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); } --- 207,219 ---- * 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; --- 225,232 ---- * 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) --- 267,282 ---- get_namespace_name(convForm->connamespace)); } + /* Call BEFORE ALTER CONVERSION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(tup); + cmd->objectname = 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) --- 289,298 ---- /* 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; --- 302,308 ---- * 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); } --- 320,326 ---- 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); --- 341,347 ---- Anum_pg_conversion_conname, Anum_pg_conversion_connamespace, Anum_pg_conversion_conowner, ! ACL_KIND_CONVERSION, NULL); heap_close(rel, RowExclusiveLock); *** 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" *************** *** 732,739 **** 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; --- 733,742 ---- * 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,32 **** --- 21,35 ---- #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, *************** *** 49,54 **** RemoveObjects(DropStmt *stmt) --- 52,59 ---- 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) --- 64,70 ---- List *objargs = NIL; Relation relation = NULL; Oid namespaceId; + CommandContextData cmd; if (stmt->arguments) { *************** *** 77,82 **** RemoveObjects(DropStmt *stmt) --- 83,89 ---- if (!OidIsValid(address.objectId)) { does_not_exist_skipping(stmt->removeType, objname, objargs); + cmds[i++] = NULL; continue; } *************** *** 115,126 **** RemoveObjects(DropStmt *stmt) --- 122,157 ---- if (relation) heap_close(relation, NoLock); + /* + * Call BEFORE DROP command triggers + */ + InitCommandContext(&cmd, (Node *)stmt, false); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = address.objectId; + cmd.objectname = strVal(list_nth(objname, list_length(objname)-1)); + 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); } *** 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,1200 **** CreateExtension(CreateExtensionStmt *stmt) --- 1191,1215 ---- 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, false); + + 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 *************** *** 1467,1472 **** CreateExtension(CreateExtensionStmt *stmt) --- 1482,1494 ---- */ 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; --- 2208,2214 ---- * 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) --- 2269,2284 ---- 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) --- 2374,2383 ---- /* 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) --- 2399,2405 ---- 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) --- 2452,2470 ---- stmt->extname); /* + * Call BEFORE ALTER EXTENSION triggers + */ + InitCommandContext(&cmd, (Node *)stmt, false); + + 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) --- 2530,2539 ---- */ ApplyExtensionUpdates(extensionOid, control, oldVersionName, updateVersions); + + /* Call AFTER ALTER EXTENSION triggers */ + if (CommandFiresAfterTriggers(&cmd)) + 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, false); + + 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 = NameListToString(name); + 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 = (char *)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 = 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); *************** *** 1822,1828 **** DropCastById(Oid castOid) */ void AlterFunctionNamespace(List *name, List *argtypes, bool isagg, ! const char *newschema) { Oid procOid; Oid nspOid; --- 1878,1884 ---- */ void AlterFunctionNamespace(List *name, List *argtypes, bool isagg, ! const char *newschema, CommandContext cmd) { Oid procOid; Oid nspOid; *************** *** 1836,1846 **** 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; --- 1892,1902 ---- /* 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; *************** *** 1875,1880 **** AlterFunctionNamespace_oid(Oid procOid, Oid nspOid) --- 1931,1946 ---- NameStr(proc->proname), get_namespace_name(nspOid)))); + /* Call BEFORE ALTER FUNCTION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = procOid; + cmd->objectname = 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 */ *************** *** 1893,1898 **** AlterFunctionNamespace_oid(Oid procOid, Oid nspOid) --- 1959,1969 ---- 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; } *** a/src/backend/commands/opclasscmds.c --- b/src/backend/commands/opclasscmds.c *************** *** 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; --- 1919,1926 ---- * 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); } --- 1942,1948 ---- 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); --- 1961,1967 ---- Anum_pg_opclass_opcname, Anum_pg_opclass_opcnamespace, Anum_pg_opclass_opcowner, ! ACL_KIND_OPCLASS, NULL); heap_close(rel, RowExclusiveLock); *************** *** 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; --- 2129,2136 ---- * 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); } --- 2152,2158 ---- 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); --- 2171,2177 ---- 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 = 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/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, false); + + 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 = (char *)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 = (char *)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 = 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,216 **** DefineSequence(CreateSeqStmt *seq) --- 213,232 ---- stmt->tablespacename = NULL; stmt->if_not_exists = false; + /* + * Call BEFORE CREATE SEQUENCE triggers + */ + InitCommandContext(&cmd, (Node *)seq, false); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = NameStr(name); + cmd.schemaname = NULL; /* can't publish it easily enough here */ + + ExecBeforeCommandTriggers(&cmd); + } + seqoid = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId); Assert(seqoid != InvalidOid); *************** *** 226,231 **** DefineSequence(CreateSeqStmt *seq) --- 242,254 ---- process_owned_by(rel, owned_by); heap_close(rel, NoLock); + + /* Call AFTER CREATE SEQUENCE triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = seqoid; + 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" *************** *** 735,740 **** RemoveRelations(DropStmt *drop) --- 737,744 ---- 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 *************** *** 781,786 **** RemoveRelations(DropStmt *drop) --- 785,791 ---- Oid relOid; ObjectAddress obj; struct DropRelationCallbackState state; + CommandContextData cmd; /* * These next few steps are a great deal like relation_openrv, but we *************** *** 806,814 **** RemoveRelations(DropStmt *drop) --- 811,835 ---- if (!OidIsValid(relOid)) { DropErrorMsgNonExistent(rel->relname, relkind, drop->missing_ok); + cmds[i++] = NULL; continue; } + /* + * Call BEFORE DROP command triggers + */ + InitCommandContext(&cmd, (Node *)drop, false); + + 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; *************** *** 819,824 **** RemoveRelations(DropStmt *drop) --- 840,855 ---- performMultipleDeletions(objects, drop->behavior, 0); + /* Call AFTER DROP command triggers */ + for(i = 0; iobjectId = InvalidOid; + ExecAfterCommandTriggers(cmds[i]); + } + } + free_object_addresses(objects); } *************** *** 2328,2334 **** renameatt(RenameStmt *stmt) * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/FOREIGN TABLE RENAME */ void ! RenameRelation(RenameStmt *stmt) { Oid relid; --- 2359,2365 ---- * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/FOREIGN TABLE RENAME */ void ! RenameRelation(RenameStmt *stmt, CommandContext cmd) { Oid relid; *************** *** 2353,2359 **** RenameRelation(RenameStmt *stmt) } /* Do the work */ ! RenameRelationInternal(relid, stmt->newname); } /* --- 2384,2390 ---- } /* Do the work */ ! RenameRelationInternal(relid, stmt->newname, cmd); } /* *************** *** 2366,2372 **** 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 */ --- 2397,2403 ---- * 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 */ *************** *** 2397,2402 **** RenameRelationInternal(Oid myrelid, const char *newrelname) --- 2428,2443 ---- errmsg("relation \"%s\" already exists", newrelname))); + /* Call BEFORE ALTER relation triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = HeapTupleGetOid(reltup); + cmd->objectname = 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...) *************** *** 2416,2422 **** 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. --- 2457,2463 ---- */ if (OidIsValid(targetrelation->rd_rel->reltype)) RenameTypeInternal(targetrelation->rd_rel->reltype, ! newrelname, namespaceId, NULL); /* * Also rename the associated constraint, if any. *************** *** 2433,2438 **** RenameRelationInternal(Oid myrelid, const char *newrelname) --- 2474,2486 ---- * Close rel, but keep exclusive lock! */ relation_close(targetrelation, NoLock); + + /* Call AFTER ALTER relation triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectname = (char *)newrelname; + ExecAfterCommandTriggers(cmd); + } } /* *************** *** 5331,5337 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel, check_rights, skip_build, quiet, ! false); /* * If TryReuseIndex() stashed a relfilenode for us, we used it for the new --- 5379,5385 ---- check_rights, skip_build, quiet, ! false, NULL); /* * If TryReuseIndex() stashed a relfilenode for us, we used it for the new *************** *** 5390,5396 **** 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 */ --- 5438,5444 ---- 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 */ *************** *** 9479,9484 **** AlterTableNamespace(AlterObjectSchemaStmt *stmt) --- 9527,9533 ---- Oid nspOid; Relation classRel; RangeVar *newrv; + CommandContextData cmd; relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock, stmt->missing_ok, false, *************** *** 9519,9531 **** 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) --- 9568,9592 ---- /* common checks on switching namespaces */ CheckSetNamespace(oldNspOid, nspOid, RelationRelationId, relid); + /* Call BEFORE ALTER TABLE triggers */ + InitCommandContext(&cmd, (Node *)stmt, false); + + 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) *************** *** 9540,9545 **** AlterTableNamespace(AlterObjectSchemaStmt *stmt) --- 9601,9613 ---- /* 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); + } } /* *************** *** 9687,9693 **** 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); --- 9755,9761 ---- * 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), *************** *** 518,523 **** DropTableSpace(DropTableSpaceStmt *stmt) --- 520,526 ---- /* We keep the lock on pg_tablespace until commit */ heap_close(rel, NoLock); + #else /* !HAVE_SYMLINK */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), *************** *** 812,817 **** RenameTableSpace(const char *oldname, const char *newname) --- 815,821 ---- HeapScanDesc scan; HeapTuple tup; HeapTuple newtuple; + Oid oid; Form_pg_tablespace newform; /* Search pg_tablespace */ *************** *** 829,834 **** RenameTableSpace(const char *oldname, const char *newname) --- 833,839 ---- errmsg("tablespace \"%s\" does not exist", oldname))); + oid = HeapTupleGetOid(tup); newtuple = heap_copytuple(tup); newform = (Form_pg_tablespace) GETSTRUCT(newtuple); *** 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); *************** *** 419,424 **** CreateTrigger(CreateTrigStmt *stmt, const char *queryString, --- 420,442 ---- } /* + * Call BEFORE CREATE TRIGGER triggers + */ + if (!isInternal) + { + InitCommandContext(&cmd, (Node *)stmt, false); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = InvalidOid; + cmd.objectname = stmt->trigname; + cmd.schemaname = get_namespace_name(RelationGetNamespace(rel)); + + ExecBeforeCommandTriggers(&cmd); + } + } + + /* * If it's a user-entered CREATE CONSTRAINT TRIGGER command, make a * corresponding pg_constraint entry. */ *************** *** 754,759 **** CreateTrigger(CreateTrigStmt *stmt, const char *queryString, --- 772,783 ---- /* 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; } *************** *** 1201,1207 **** RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid, * update row in catalog */ void ! renametrig(RenameStmt *stmt) { Relation targetrel; Relation tgrel; --- 1225,1231 ---- * update row in catalog */ void ! renametrig(RenameStmt *stmt, CommandContext cmd) { Relation targetrel; Relation tgrel; *************** *** 1268,1273 **** renametrig(RenameStmt *stmt) --- 1292,1307 ---- 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. */ *************** *** 1304,1309 **** renametrig(RenameStmt *stmt) --- 1338,1350 ---- * 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 = NameStr(pname); + cmd->schemaname = get_namespace_name(namespaceoid); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Looks good, insert */ prsRel = heap_open(TSParserRelationId, RowExclusiveLock); *************** *** 276,281 **** DefineTSParser(List *names, List *parameters) --- 289,301 ---- heap_freetuple(tup); heap_close(prsRel, RowExclusiveLock); + + /* Call AFTER CREATE TEXT SEARCH PARSER triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = prsOid; + ExecAfterCommandTriggers(cmd); + } } /* *************** *** 305,311 **** RemoveTSParserById(Oid prsId) * ALTER TEXT SEARCH PARSER RENAME */ void ! RenameTSParser(List *oldname, const char *newname) { HeapTuple tup; Relation rel; --- 325,331 ---- * ALTER TEXT SEARCH PARSER RENAME */ void ! RenameTSParser(List *oldname, const char *newname, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 336,354 **** 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; --- 356,391 ---- errmsg("text search parser \"%s\" already exists", newname))); + /* Call BEFORE ALTER TEXT SEARCH PARSER triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = prsId; + cmd->objectname = 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 = (char *)newname; + ExecAfterCommandTriggers(cmd); + } } /* * ALTER TEXT SEARCH PARSER any_name SET SCHEMA name */ void ! AlterTSParserNamespace(List *name, const char *newschema, CommandContext cmd) { Oid prsId, nspOid; *************** *** 365,371 **** AlterTSParserNamespace(List *name, const char *newschema) prsId, nspOid, Anum_pg_ts_parser_prsname, Anum_pg_ts_parser_prsnamespace, ! -1, -1); heap_close(rel, RowExclusiveLock); } --- 402,408 ---- prsId, nspOid, Anum_pg_ts_parser_prsname, Anum_pg_ts_parser_prsnamespace, ! -1, -1, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 383,389 **** AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid) prsId, newNspOid, Anum_pg_ts_parser_prsname, Anum_pg_ts_parser_prsnamespace, ! -1, -1); heap_close(rel, RowExclusiveLock); --- 420,426 ---- prsId, newNspOid, Anum_pg_ts_parser_prsname, Anum_pg_ts_parser_prsnamespace, ! -1, -1, NULL); heap_close(rel, RowExclusiveLock); *************** *** 484,490 **** verify_dictoptions(Oid tmplId, List *dictoptions) * CREATE TEXT SEARCH DICTIONARY */ void ! DefineTSDictionary(List *names, List *parameters) { ListCell *pl; Relation dictRel; --- 521,527 ---- * CREATE TEXT SEARCH DICTIONARY */ void ! DefineTSDictionary(List *names, List *parameters, CommandContext cmd) { ListCell *pl; Relation dictRel; *************** *** 537,542 **** DefineTSDictionary(List *names, List *parameters) --- 574,591 ---- verify_dictoptions(templId, dictoptions); /* + * Call BEFORE CREATE TEXT SEARCH DICTIONARY triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = NameStr(dname); + cmd->schemaname = get_namespace_name(namespaceoid); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Looks good, insert */ memset(values, 0, sizeof(values)); *************** *** 570,582 **** 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; --- 619,638 ---- 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; *************** *** 615,633 **** 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; --- 671,706 ---- 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 = 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 = (char *)newname; + ExecAfterCommandTriggers(cmd); + } } /* * ALTER TEXT SEARCH DICTIONARY any_name SET SCHEMA name */ void ! AlterTSDictionaryNamespace(List *name, const char *newschema, CommandContext cmd) { Oid dictId, nspOid; *************** *** 645,651 **** 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); } --- 718,724 ---- Anum_pg_ts_dict_dictname, Anum_pg_ts_dict_dictnamespace, Anum_pg_ts_dict_dictowner, ! ACL_KIND_TSDICTIONARY, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 664,670 **** 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); --- 737,743 ---- Anum_pg_ts_dict_dictname, Anum_pg_ts_dict_dictnamespace, Anum_pg_ts_dict_dictowner, ! ACL_KIND_TSDICTIONARY, NULL); heap_close(rel, RowExclusiveLock); *************** *** 812,818 **** AlterTSDictionary(AlterTSDictionaryStmt *stmt) * ALTER TEXT SEARCH DICTIONARY OWNER */ void ! AlterTSDictionaryOwner(List *name, Oid newOwnerId) { HeapTuple tup; Relation rel; --- 885,891 ---- * ALTER TEXT SEARCH DICTIONARY OWNER */ void ! AlterTSDictionaryOwner(List *name, Oid newOwnerId, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 854,859 **** AlterTSDictionaryOwner(List *name, Oid newOwnerId) --- 927,942 ---- get_namespace_name(namespaceOid)); } + /* Call BEFORE ALTER TEXT SEARCH DICTIONARY triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = dictId; + cmd->objectname = NameStr(form->dictname); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + form->dictowner = newOwnerId; simple_heap_update(rel, &tup->t_self, tup); *************** *** 862,867 **** AlterTSDictionaryOwner(List *name, Oid newOwnerId) --- 945,954 ---- /* 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); *************** *** 956,962 **** makeTSTemplateDependencies(HeapTuple tuple) * CREATE TEXT SEARCH TEMPLATE */ void ! DefineTSTemplate(List *names, List *parameters) { ListCell *pl; Relation tmplRel; --- 1043,1049 ---- * CREATE TEXT SEARCH TEMPLATE */ void ! DefineTSTemplate(List *names, List *parameters, CommandContext cmd) { ListCell *pl; Relation tmplRel; *************** *** 1022,1027 **** DefineTSTemplate(List *names, List *parameters) --- 1109,1126 ---- errmsg("text search template lexize method is required"))); /* + * Call BEFORE CREATE TEXT SEARCH TEMPLATE triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = NameStr(dname); + cmd->schemaname = get_namespace_name(namespaceoid); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Looks good, insert */ *************** *** 1041,1053 **** 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; --- 1140,1159 ---- 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; *************** *** 1079,1097 **** 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; --- 1185,1220 ---- errmsg("text search template \"%s\" already exists", newname))); + /* Call BEFORE ALTER TEXT SEARCH TEMPLATE triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = tmplId; + cmd->objectname = 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 = (char *)newname; + ExecAfterCommandTriggers(cmd); + } } /* * ALTER TEXT SEARCH TEMPLATE any_name SET SCHEMA name */ void ! AlterTSTemplateNamespace(List *name, const char *newschema, CommandContext cmd) { Oid tmplId, nspOid; *************** *** 1108,1114 **** AlterTSTemplateNamespace(List *name, const char *newschema) tmplId, nspOid, Anum_pg_ts_template_tmplname, Anum_pg_ts_template_tmplnamespace, ! -1, -1); heap_close(rel, RowExclusiveLock); } --- 1231,1237 ---- tmplId, nspOid, Anum_pg_ts_template_tmplname, Anum_pg_ts_template_tmplnamespace, ! -1, -1, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 1126,1132 **** AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid) tmplId, newNspOid, Anum_pg_ts_template_tmplname, Anum_pg_ts_template_tmplnamespace, ! -1, -1); heap_close(rel, RowExclusiveLock); --- 1249,1255 ---- tmplId, newNspOid, Anum_pg_ts_template_tmplname, Anum_pg_ts_template_tmplnamespace, ! -1, -1, NULL); heap_close(rel, RowExclusiveLock); *************** *** 1274,1280 **** makeConfigurationDependencies(HeapTuple tuple, bool removeOld, * CREATE TEXT SEARCH CONFIGURATION */ void ! DefineTSConfiguration(List *names, List *parameters) { Relation cfgRel; Relation mapRel = NULL; --- 1397,1403 ---- * CREATE TEXT SEARCH CONFIGURATION */ void ! DefineTSConfiguration(List *names, List *parameters, CommandContext cmd) { Relation cfgRel; Relation mapRel = NULL; *************** *** 1351,1356 **** DefineTSConfiguration(List *names, List *parameters) --- 1474,1491 ---- errmsg("text search parser is required"))); /* + * Call BEFORE CREATE TEXT SEARCH CONFIGURATION triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = NameStr(cname); + cmd->schemaname = get_namespace_name(namespaceoid); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Looks good, build tuple and insert */ memset(values, 0, sizeof(values)); *************** *** 1426,1438 **** 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; --- 1561,1580 ---- 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; *************** *** 1470,1488 **** 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; --- 1612,1647 ---- 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 = 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 = (char *)newname; + ExecAfterCommandTriggers(cmd); + } } /* * ALTER TEXT SEARCH CONFIGURATION any_name SET SCHEMA name */ void ! AlterTSConfigurationNamespace(List *name, const char *newschema, CommandContext cmd) { Oid cfgId, nspOid; *************** *** 1500,1506 **** 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); } --- 1659,1665 ---- Anum_pg_ts_config_cfgname, Anum_pg_ts_config_cfgnamespace, Anum_pg_ts_config_cfgowner, ! ACL_KIND_TSCONFIGURATION, cmd); heap_close(rel, RowExclusiveLock); } *************** *** 1519,1525 **** 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); --- 1678,1684 ---- Anum_pg_ts_config_cfgname, Anum_pg_ts_config_cfgnamespace, Anum_pg_ts_config_cfgowner, ! ACL_KIND_TSCONFIGURATION, NULL); heap_close(rel, RowExclusiveLock); *************** *** 1578,1584 **** RemoveTSConfigurationById(Oid cfgId) * ALTER TEXT SEARCH CONFIGURATION OWNER */ void ! AlterTSConfigurationOwner(List *name, Oid newOwnerId) { HeapTuple tup; Relation rel; --- 1737,1743 ---- * ALTER TEXT SEARCH CONFIGURATION OWNER */ void ! AlterTSConfigurationOwner(List *name, Oid newOwnerId, CommandContext cmd) { HeapTuple tup; Relation rel; *************** *** 1620,1625 **** AlterTSConfigurationOwner(List *name, Oid newOwnerId) --- 1779,1794 ---- get_namespace_name(namespaceOid)); } + /* Call BEFORE ALTER TEXT SEARCH CONFIGURATION triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = cfgId; + cmd->objectname = NameStr(form->cfgname); + cmd->schemaname = get_namespace_name(namespaceOid); + + ExecBeforeCommandTriggers(cmd); + } + form->cfgowner = newOwnerId; simple_heap_update(rel, &tup->t_self, tup); *************** *** 1628,1635 **** AlterTSConfigurationOwner(List *name, Oid newOwnerId) /* Update owner dependency reference */ changeDependencyOnOwner(TSConfigRelationId, HeapTupleGetOid(tup), newOwnerId); - } heap_close(rel, NoLock); heap_freetuple(tup); } --- 1797,1807 ---- /* 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); } *** 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 = (char *)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, false); + + 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, false); + + 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, false); + + 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(const RangeVar *typevar, List *coldeflist) { CreateStmt *createStmt = makeNode(CreateStmt); Oid old_type_oid; --- 2067,2074 ---- *------------------------------------------------------------------- */ Oid ! DefineCompositeType(const RangeVar *typevar, List *coldeflist, ! CommandContext cmd) { CreateStmt *createStmt = makeNode(CreateStmt); Oid old_type_oid; *************** *** 2022,2031 **** DefineCompositeType(const RangeVar *typevar, List *coldeflist) --- 2110,2138 ---- } /* + * Call BEFORE CREATE (composite) TYPE triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = createStmt->relation->relname; + cmd->schemaname = get_namespace_name(typeNamespace); + + ExecBeforeCommandTriggers(cmd); + } + + /* * Finally create the relation. This also creates the type. */ relid = DefineRelation(createStmt, RELKIND_COMPOSITE_TYPE, InvalidOid); Assert(relid != InvalidOid); + + /* Call AFTER CREATE (composite) TYPE triggers */ + if (CommandFiresAfterTriggers(cmd)) + { + cmd->objectId = relid; + ExecAfterCommandTriggers(cmd); + } return relid; } *************** *** 3091,3097 **** GetDomainConstraints(Oid typeOid) * Execute ALTER TYPE RENAME */ void ! RenameType(RenameStmt *stmt) { List *names = stmt->object; const char *newTypeName = stmt->newname; --- 3198,3204 ---- * 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); --- 3260,3269 ---- * 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; --- 3273,3280 ---- * 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) --- 3360,3375 ---- get_namespace_name(typTup->typnamespace)); } + /* Call BEFORE ALTER TYPE triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = typeOid; + cmd->objectname = (char *)typename; + 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) --- 3397,3406 ---- 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; --- 3458,3465 ---- * 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; --- 3479,3489 ---- /* 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); } /* --- 3503,3509 ---- 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; --- 3524,3531 ---- Oid AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, bool isImplicitArray, ! bool errorOnTableType, ! CommandContext cmd) { Relation rel; HeapTuple tup; *************** *** 3447,3452 **** AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid, --- 3571,3586 ---- format_type_be(typeOid)), errhint("Use ALTER TABLE instead."))); + /* Call BEFORE ALTER TYPE triggers */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = typeOid; + cmd->objectname = 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; } --- 3638,3649 ---- /* 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,144 ---- else in_outer_xact = IsInTransactionChain(isTopLevel); + /* CAll BEFORE VACUUM command triggers */ + if (!IsAutoVacuumWorkerProcess() && vacstmt->relation != NULL) + { + CommandContextData cmd; + InitCommandContext(&cmd, (Node *)vacstmt, false); + + if (CommandFiresTriggers(&cmd)) + { + cmd.objectId = relid; + 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. *** 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; *************** *** 168,173 **** DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace, --- 169,186 ---- lockmode = replace ? AccessExclusiveLock : NoLock; (void) RangeVarGetAndCheckCreationNamespace(relation, lockmode, &viewOid); + /* + * Call BEFORE CREATE VIEW triggers + */ + if (CommandFiresTriggers(cmd)) + { + cmd->objectId = InvalidOid; + cmd->objectname = relation->relname; + cmd->schemaname = relation->schemaname; + + ExecBeforeCommandTriggers(cmd); + } + if (OidIsValid(viewOid) && replace) { Relation rel; *************** *** 426,431 **** DefineView(ViewStmt *stmt, const char *queryString) --- 439,445 ---- Query *viewParse; Oid viewOid; RangeVar *view; + CommandContextData cmd; /* * Run parse analysis to convert the raw parse tree to a Query. Note this *************** *** 511,523 **** 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 --- 525,545 ---- } /* + * Prepare BEFORE CREATE VIEW triggers + */ + InitCommandContext(&cmd, (Node *)stmt, false); + + /* * 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 *************** *** 536,539 **** DefineView(ViewStmt *stmt, const char *queryString) --- 558,568 ---- * 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 *************** *** 3457,3462 **** _copyCreateTrigStmt(const CreateTrigStmt *from) --- 3457,3514 ---- return newnode; } + static DropPropertyStmt * + _copyDropPropertyStmt(const DropPropertyStmt *from) + { + DropPropertyStmt *newnode = makeNode(DropPropertyStmt); + + COPY_NODE_FIELD(relation); + COPY_STRING_FIELD(property); + COPY_SCALAR_FIELD(removeType); + COPY_SCALAR_FIELD(behavior); + COPY_SCALAR_FIELD(missing_ok); + + return newnode; + } + + static CreateCmdTrigStmt * + _copyCreateCmdTrigStmt(const CreateCmdTrigStmt *from) + { + CreateCmdTrigStmt *newnode = makeNode(CreateCmdTrigStmt); + + COPY_NODE_FIELD(command); + COPY_STRING_FIELD(trigname); + COPY_SCALAR_FIELD(timing); + COPY_NODE_FIELD(funcname); + + return newnode; + } + + static DropCmdTrigStmt * + _copyDropCmdTrigStmt(const DropCmdTrigStmt *from) + { + DropCmdTrigStmt *newnode = makeNode(DropCmdTrigStmt); + + COPY_NODE_FIELD(command); + COPY_STRING_FIELD(trigname); + COPY_SCALAR_FIELD(behavior); + COPY_SCALAR_FIELD(missing_ok); + + return newnode; + } + + static AlterCmdTrigStmt * + _copyAlterCmdTrigStmt(const AlterCmdTrigStmt *from) + { + AlterCmdTrigStmt *newnode = makeNode(AlterCmdTrigStmt); + + COPY_STRING_FIELD(command); + COPY_STRING_FIELD(trigname); + COPY_STRING_FIELD(tgenabled); + + return newnode; + } + static CreatePLangStmt * _copyCreatePLangStmt(const CreatePLangStmt *from) { *************** *** 3686,3692 **** _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)); \ --- 3738,3744 ---- /* * 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)); \ *************** *** 4309,4314 **** copyObject(const void *from) --- 4361,4378 ---- case T_CreateTrigStmt: retval = _copyCreateTrigStmt(from); break; + case T_DropPropertyStmt: + retval = _copyDropPropertyStmt(from); + break; + case T_CreateCmdTrigStmt: + retval = _copyCreateCmdTrigStmt(from); + break; + case T_DropCmdTrigStmt: + retval = _copyDropCmdTrigStmt(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 *************** *** 1778,1783 **** _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b) --- 1778,1827 ---- } static bool + _equalDropPropertyStmt(const DropPropertyStmt *a, const DropPropertyStmt *b) + { + COMPARE_NODE_FIELD(relation); + COMPARE_STRING_FIELD(property); + COMPARE_SCALAR_FIELD(removeType); + COMPARE_SCALAR_FIELD(behavior); + COMPARE_SCALAR_FIELD(missing_ok); + + return true; + } + + static bool + _equalCreateCmdTrigStmt(const CreateCmdTrigStmt *a, const CreateCmdTrigStmt *b) + { + COMPARE_NODE_FIELD(command); + COMPARE_STRING_FIELD(trigname); + COMPARE_SCALAR_FIELD(timing); + COMPARE_NODE_FIELD(funcname); + + return true; + } + + static bool + _equalDropCmdTrigStmt(const DropCmdTrigStmt *a, const DropCmdTrigStmt *b) + { + COMPARE_NODE_FIELD(command); + COMPARE_STRING_FIELD(trigname); + COMPARE_SCALAR_FIELD(behavior); + COMPARE_SCALAR_FIELD(missing_ok); + + return true; + } + + static bool + _equalAlterCmdTrigStmt(const AlterCmdTrigStmt *a, const AlterCmdTrigStmt *b) + { + COMPARE_STRING_FIELD(command); + COMPARE_STRING_FIELD(trigname); + COMPARE_STRING_FIELD(tgenabled); + + return true; + } + + static bool _equalCreatePLangStmt(const CreatePLangStmt *a, const CreatePLangStmt *b) { COMPARE_SCALAR_FIELD(replace); *************** *** 2852,2857 **** equal(const void *a, const void *b) --- 2896,2913 ---- case T_CreateTrigStmt: retval = _equalCreateTrigStmt(a, b); break; + case T_DropPropertyStmt: + retval = _equalDropPropertyStmt(a, b); + break; + case T_CreateCmdTrigStmt: + retval = _equalCreateCmdTrigStmt(a, b); + break; + case T_DropCmdTrigStmt: + retval = _equalDropCmdTrigStmt(a, b); + break; + case T_AlterCmdTrigStmt: + retval = _equalAlterCmdTrigStmt(a, b); + break; case T_CreatePLangStmt: retval = _equalCreatePLangStmt(a, b); break; *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** *** 1934,1939 **** _outPlannerParamItem(StringInfo str, const PlannerParamItem *node) --- 1934,2070 ---- *****************************************************************************/ static void + _outInsertStmt(StringInfo str, const InsertStmt *node) + { + WRITE_NODE_TYPE("INSERTSTMT"); + + WRITE_NODE_FIELD(relation); + WRITE_NODE_FIELD(cols); + WRITE_NODE_FIELD(selectStmt); + WRITE_NODE_FIELD(returningList); + WRITE_NODE_FIELD(withClause); + } + + static void + _outDeleteStmt(StringInfo str, const DeleteStmt *node) + { + WRITE_NODE_TYPE("DELETESTMT"); + + WRITE_NODE_FIELD(relation); + WRITE_NODE_FIELD(usingClause); + WRITE_NODE_FIELD(whereClause); + WRITE_NODE_FIELD(returningList); + WRITE_NODE_FIELD(withClause); + } + + static void + _outUpdateStmt(StringInfo str, const UpdateStmt *node) + { + WRITE_NODE_TYPE("UPDATESTMT"); + + WRITE_NODE_FIELD(relation); + WRITE_NODE_FIELD(targetList); + WRITE_NODE_FIELD(whereClause); + WRITE_NODE_FIELD(fromClause); + WRITE_NODE_FIELD(returningList); + WRITE_NODE_FIELD(withClause); + } + + static void + _outAlterTableStmt(StringInfo str, const AlterTableStmt *node) + { + WRITE_NODE_TYPE("ALTERTABLESTMT"); + + WRITE_NODE_FIELD(relation); + WRITE_NODE_FIELD(cmds); + WRITE_CHAR_FIELD(relkind); + } + + static void + _outAlterTableCmd(StringInfo str, const AlterTableCmd *node) + { + WRITE_NODE_TYPE("ALTERTABLECMD"); + + WRITE_ENUM_FIELD(subtype, AlterTableType); + WRITE_STRING_FIELD(name); + WRITE_NODE_FIELD(def); + WRITE_ENUM_FIELD(behavior, DropBehavior); + WRITE_BOOL_FIELD(missing_ok); + } + + static void + _outAlterDomainStmt(StringInfo str, const AlterDomainStmt *node) + { + WRITE_NODE_TYPE("ALTERDOMAINSTMT"); + + WRITE_CHAR_FIELD(subtype); + WRITE_NODE_FIELD(typeName); + WRITE_STRING_FIELD(name); + WRITE_NODE_FIELD(def); + WRITE_ENUM_FIELD(behavior, DropBehavior); + } + + static void + _outGrantStmt(StringInfo str, const GrantStmt *node) + { + WRITE_NODE_TYPE("GRANTSTMT"); + + WRITE_BOOL_FIELD(is_grant); + WRITE_ENUM_FIELD(targtype, GrantTargetType); + WRITE_ENUM_FIELD(objtype, GrantObjectType); + WRITE_NODE_FIELD(objects); + WRITE_NODE_FIELD(privileges); + WRITE_NODE_FIELD(grantees); + WRITE_BOOL_FIELD(grant_option); + WRITE_ENUM_FIELD(behavior, DropBehavior); + } + + static void + _outGrantRoleStmt(StringInfo str, const GrantRoleStmt *node) + { + WRITE_NODE_TYPE("GRANTROLESTMT"); + + WRITE_NODE_FIELD(granted_roles); + WRITE_NODE_FIELD(grantee_roles); + WRITE_BOOL_FIELD(is_grant); + WRITE_BOOL_FIELD(admin_opt); + WRITE_STRING_FIELD(grantor); + WRITE_ENUM_FIELD(behavior, DropBehavior); + } + + static void + _outAlterDefaultPrivilegesStmt(StringInfo str, const AlterDefaultPrivilegesStmt *node) + { + WRITE_NODE_TYPE("ALTERDEFAULTPRIVILEGESSTMT"); + + WRITE_NODE_FIELD(options); + WRITE_NODE_FIELD(action); + } + + static void + _outClusterStmt(StringInfo str, const ClusterStmt *node) + { + WRITE_NODE_TYPE("CLUSTERSTMT"); + + WRITE_NODE_FIELD(relation); + WRITE_STRING_FIELD(indexname); + WRITE_BOOL_FIELD(verbose); + } + + static void + _outCopyStmt(StringInfo str, const CopyStmt *node) + { + WRITE_NODE_TYPE("COPYSTMT"); + + WRITE_NODE_FIELD(relation); + WRITE_NODE_FIELD(query); + WRITE_NODE_FIELD(attlist); + WRITE_BOOL_FIELD(is_from); + WRITE_STRING_FIELD(filename); + WRITE_NODE_FIELD(options); + } + + static void _outCreateStmt(StringInfo str, const CreateStmt *node) { WRITE_NODE_TYPE("CREATESTMT"); *************** *** 1950,1955 **** _outCreateStmt(StringInfo str, const CreateStmt *node) --- 2081,2111 ---- } static void + _outDefineStmt(StringInfo str, const DefineStmt *node) + { + WRITE_NODE_TYPE("DEFINESTMT"); + + WRITE_ENUM_FIELD(kind, ObjectType); + WRITE_BOOL_FIELD(oldstyle); + WRITE_NODE_FIELD(defnames); + WRITE_NODE_FIELD(args); + WRITE_NODE_FIELD(definition); + } + + static void + _outAlterTSConfigurationStmt(StringInfo str, const AlterTSConfigurationStmt *node) + { + WRITE_NODE_TYPE("ALTERTSCONFIGURATIONSTMT"); + + WRITE_NODE_FIELD(cfgname); + WRITE_NODE_FIELD(tokentype); + WRITE_NODE_FIELD(dicts); + WRITE_BOOL_FIELD(override); + WRITE_BOOL_FIELD(replace); + WRITE_BOOL_FIELD(missing_ok); + } + + static void _outCreateForeignTableStmt(StringInfo str, const CreateForeignTableStmt *node) { WRITE_NODE_TYPE("CREATEFOREIGNTABLESTMT"); *************** *** 1961,1966 **** _outCreateForeignTableStmt(StringInfo str, const CreateForeignTableStmt *node) --- 2117,2144 ---- } static void + _outDropStmt(StringInfo str, const DropStmt *node) + { + WRITE_NODE_TYPE("DROPSTMT"); + + WRITE_NODE_FIELD(objects); + WRITE_ENUM_FIELD(removeType,ObjectType); + WRITE_ENUM_FIELD(behavior,DropBehavior); + WRITE_BOOL_FIELD(missing_ok); + } + + static void + _outCommentStmt(StringInfo str, const CommentStmt *node) + { + WRITE_NODE_TYPE("COMMENTSTMT"); + + WRITE_ENUM_FIELD(objtype, ObjectType); + WRITE_NODE_FIELD(objname); + WRITE_NODE_FIELD(objargs); + WRITE_STRING_FIELD(comment); + } + + static void _outIndexStmt(StringInfo str, const IndexStmt *node) { WRITE_NODE_TYPE("INDEXSTMT"); *************** *** 1984,1989 **** _outIndexStmt(StringInfo str, const IndexStmt *node) --- 2162,2194 ---- } static void + _outCreateFunctionStmt(StringInfo str, const CreateFunctionStmt *node) + { + WRITE_NODE_TYPE("CREATEFUNCTIONSTMT"); + + WRITE_BOOL_FIELD(replace); + WRITE_NODE_FIELD(funcname); + WRITE_NODE_FIELD(parameters); + WRITE_NODE_FIELD(returnType); + WRITE_NODE_FIELD(options); + WRITE_NODE_FIELD(withClause); + } + + static void + _outRuleStmt(StringInfo str, const RuleStmt *node) + { + WRITE_NODE_TYPE("RULESTMT"); + + WRITE_NODE_FIELD(relation); + WRITE_STRING_FIELD(rulename); + WRITE_NODE_FIELD(whereClause); + WRITE_ENUM_FIELD(event, CmdType); + WRITE_BOOL_FIELD(instead); + WRITE_NODE_FIELD(actions); + WRITE_BOOL_FIELD(replace); + } + + static void _outNotifyStmt(StringInfo str, const NotifyStmt *node) { WRITE_NODE_TYPE("NOTIFY"); *************** *** 1993,1998 **** _outNotifyStmt(StringInfo str, const NotifyStmt *node) --- 2198,2292 ---- } static void + _outViewStmt(StringInfo str, const ViewStmt *node) + { + WRITE_NODE_TYPE("VIEWSTMT"); + + WRITE_NODE_FIELD(view); + WRITE_NODE_FIELD(aliases); + WRITE_NODE_FIELD(query); + WRITE_BOOL_FIELD(replace); + } + + static void + _outCreateDomainStmt(StringInfo str, const CreateDomainStmt *node) + { + WRITE_NODE_TYPE("CREATECONVERSIONSTMT"); + + WRITE_NODE_FIELD(domainname); + WRITE_NODE_FIELD(typeName); + WRITE_NODE_FIELD(collClause); + WRITE_NODE_FIELD(constraints); + } + + static void + _outCreatedbStmt(StringInfo str, const CreatedbStmt *node) + { + WRITE_NODE_TYPE("CREATEDBSTMT"); + + WRITE_STRING_FIELD(dbname); + WRITE_NODE_FIELD(options); + } + + static void + _outVacuumStmt(StringInfo str, const VacuumStmt *node) + { + WRITE_NODE_TYPE("VACUUMSTMT"); + + WRITE_INT_FIELD(options); + WRITE_INT_FIELD(freeze_min_age); + WRITE_INT_FIELD(freeze_table_age); + WRITE_NODE_FIELD(relation); + WRITE_NODE_FIELD(va_cols); + } + + static void + _outVariableSetStmt(StringInfo str, const VariableSetStmt *node) + { + WRITE_NODE_TYPE("VARIABLESETSTMT"); + + WRITE_ENUM_FIELD(kind, VariableSetKind); + WRITE_STRING_FIELD(name); + WRITE_NODE_FIELD(args); + WRITE_BOOL_FIELD(is_local); + } + + static void + _outCreatePLangStmt(StringInfo str, const CreatePLangStmt *node) + { + WRITE_NODE_TYPE("CREATESPLANGSTMT"); + + WRITE_BOOL_FIELD(replace); + WRITE_STRING_FIELD(plname); + WRITE_NODE_FIELD(plhandler); + WRITE_NODE_FIELD(plinline); + WRITE_NODE_FIELD(plvalidator); + WRITE_BOOL_FIELD(pltrusted); + } + + static void + _outCreateSchemaStmt(StringInfo str, const CreateSchemaStmt *node) + { + WRITE_NODE_TYPE("CREATESCHEMASTMT"); + + WRITE_STRING_FIELD(schemaname); + WRITE_STRING_FIELD(authid); + WRITE_NODE_FIELD(schemaElts); + } + + static void + _outCreateConversionStmt(StringInfo str, const CreateConversionStmt *node) + { + WRITE_NODE_TYPE("CREATECONVERSIONSTMT"); + + WRITE_NODE_FIELD(conversion_name); + WRITE_STRING_FIELD(for_encoding_name); + WRITE_STRING_FIELD(to_encoding_name); + WRITE_NODE_FIELD(func_name); + WRITE_BOOL_FIELD(def); + } + + static void _outDeclareCursorStmt(StringInfo str, const DeclareCursorStmt *node) { WRITE_NODE_TYPE("DECLARECURSOR"); *************** *** 2084,2089 **** _outXmlSerialize(StringInfo str, const XmlSerialize *node) --- 2378,2393 ---- } static void + _outCreateExtensionStmt(StringInfo str, const CreateExtensionStmt *node) + { + WRITE_NODE_TYPE("CREATEEXTENSIONSTMT"); + + WRITE_STRING_FIELD(extname); + WRITE_BOOL_FIELD(if_not_exists); + WRITE_NODE_FIELD(options); + } + + static void _outColumnDef(StringInfo str, const ColumnDef *node) { WRITE_NODE_TYPE("COLUMNDEF"); *************** *** 2241,2246 **** _outWindowClause(StringInfo str, const WindowClause *node) --- 2545,2561 ---- } static void + _outFunctionParameter(StringInfo str, const FunctionParameter *node) + { + WRITE_NODE_TYPE("FUNCTIONPARAMETER"); + + WRITE_STRING_FIELD(name); + WRITE_NODE_FIELD(argType); + WRITE_CHAR_FIELD(mode); + WRITE_NODE_FIELD(defexpr); + } + + static void _outRowMarkClause(StringInfo str, const RowMarkClause *node) { WRITE_NODE_TYPE("ROWMARKCLAUSE"); *************** *** 2349,2354 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) --- 2664,2679 ---- } static void + _outAlterCmdTrigStmt(StringInfo str, const AlterCmdTrigStmt *node) + { + WRITE_NODE_TYPE("ALTERCMDTRIGSTMT"); + + WRITE_STRING_FIELD(command); + WRITE_STRING_FIELD(trigname); + WRITE_STRING_FIELD(tgenabled); + } + + static void _outAExpr(StringInfo str, const A_Expr *node) { WRITE_NODE_TYPE("AEXPR"); *************** *** 3022,3045 **** _outNode(StringInfo str, const void *obj) --- 3347,3448 ---- _outPlannerParamItem(str, obj); break; + case T_InsertStmt: + _outInsertStmt(str, obj); + break; + case T_DeleteStmt: + _outDeleteStmt(str, obj); + break; + case T_UpdateStmt: + _outUpdateStmt(str, obj); + break; + case T_AlterTableStmt: + _outAlterTableStmt(str, obj); + break; + case T_AlterTableCmd: + _outAlterTableCmd(str, obj); + break; + case T_AlterDomainStmt: + _outAlterDomainStmt(str, obj); + break; + case T_GrantStmt: + _outGrantStmt(str, obj); + break; + case T_GrantRoleStmt: + _outGrantRoleStmt(str, obj); + break; + case T_AlterDefaultPrivilegesStmt: + _outAlterDefaultPrivilegesStmt(str, obj); + break; + case T_ClusterStmt: + _outClusterStmt(str, obj); + break; + case T_CopyStmt: + _outCopyStmt(str, obj); + break; case T_CreateStmt: _outCreateStmt(str, obj); break; + case T_DefineStmt: + _outDefineStmt(str, obj); + break; + case T_AlterTSConfigurationStmt: + _outAlterTSConfigurationStmt(str, obj); + break; case T_CreateForeignTableStmt: _outCreateForeignTableStmt(str, obj); break; + case T_DropStmt: + _outDropStmt(str, obj); + break; + case T_CommentStmt: + _outCommentStmt(str, obj); + break; case T_IndexStmt: _outIndexStmt(str, obj); break; + case T_CreateFunctionStmt: + _outCreateFunctionStmt(str, obj); + break; + case T_RuleStmt: + _outRuleStmt(str, obj); + break; case T_NotifyStmt: _outNotifyStmt(str, obj); break; + case T_ViewStmt: + _outViewStmt(str, obj); + break; + case T_CreateDomainStmt: + _outCreateDomainStmt(str, obj); + break; + case T_CreatedbStmt: + _outCreatedbStmt(str, obj); + break; + case T_VacuumStmt: + _outVacuumStmt(str, obj); + break; + case T_VariableSetStmt: + _outVariableSetStmt(str, obj); + break; + case T_CreatePLangStmt: + _outCreatePLangStmt(str, obj); + break; + case T_CreateSchemaStmt: + _outCreateSchemaStmt(str, obj); + break; + case T_CreateConversionStmt: + _outCreateConversionStmt(str, obj); + break; case T_DeclareCursorStmt: _outDeclareCursorStmt(str, obj); break; case T_SelectStmt: _outSelectStmt(str, obj); break; + case T_CreateExtensionStmt: + _outCreateExtensionStmt(str, obj); + break; case T_ColumnDef: _outColumnDef(str, obj); break; *************** *** 3064,3069 **** _outNode(StringInfo str, const void *obj) --- 3467,3475 ---- case T_WindowClause: _outWindowClause(str, obj); break; + case T_FunctionParameter: + _outFunctionParameter(str, obj); + break; case T_RowMarkClause: _outRowMarkClause(str, obj); break; *************** *** 3079,3084 **** _outNode(StringInfo str, const void *obj) --- 3485,3493 ---- case T_RangeTblEntry: _outRangeTblEntry(str, obj); break; + case T_AlterCmdTrigStmt: + _outAlterCmdTrigStmt(str, obj); + break; case T_A_Expr: _outAExpr(str, obj); break; *** a/src/backend/nodes/readfuncs.c --- b/src/backend/nodes/readfuncs.c *************** *** 255,260 **** _readDeclareCursorStmt(void) --- 255,463 ---- } /* + * _readCreateStmt + */ + static CreateStmt * + _readCreateStmt(void) + { + READ_LOCALS(CreateStmt); + + READ_NODE_FIELD(relation); + READ_NODE_FIELD(tableElts); + READ_NODE_FIELD(inhRelations); + READ_NODE_FIELD(ofTypename); + READ_NODE_FIELD(constraints); + READ_NODE_FIELD(options); + READ_ENUM_FIELD(oncommit, OnCommitAction); + READ_STRING_FIELD(tablespacename); + READ_BOOL_FIELD(if_not_exists); + + READ_DONE(); + } + + /* + * _readDropStmt + */ + static DropStmt * + _readDropStmt(void) + { + READ_LOCALS(DropStmt); + + READ_NODE_FIELD(objects); + READ_ENUM_FIELD(removeType,ObjectType); + READ_ENUM_FIELD(behavior,DropBehavior); + READ_BOOL_FIELD(missing_ok); + + READ_DONE(); + } + + /* + * _readCreateExtensionStmt + */ + static CreateExtensionStmt * + _readCreateExtensionStmt(void) + { + READ_LOCALS(CreateExtensionStmt); + + READ_STRING_FIELD(extname); + READ_BOOL_FIELD(if_not_exists); + READ_NODE_FIELD(options); + + READ_DONE(); + } + + /* + * _readAlterCmdTrigStmt + */ + static AlterCmdTrigStmt * + _readAlterCmdTrigStmt(void) + { + READ_LOCALS(AlterCmdTrigStmt); + + READ_STRING_FIELD(command); + READ_STRING_FIELD(trigname); + READ_STRING_FIELD(tgenabled); + + READ_DONE(); + } + + /* + * _readTypeName + */ + static TypeName * + _readTypeName(void) + { + READ_LOCALS(TypeName); + + READ_NODE_FIELD(names); + READ_OID_FIELD(typeOid); + READ_BOOL_FIELD(setof); + READ_BOOL_FIELD(pct_type); + READ_NODE_FIELD(typmods); + READ_INT_FIELD(typemod); + READ_NODE_FIELD(arrayBounds); + READ_LOCATION_FIELD(location); + + READ_DONE(); + } + + /* + * _readColumnDef + */ + static ColumnDef * + _readColumnDef(void) + { + READ_LOCALS(ColumnDef); + + READ_STRING_FIELD(colname); + READ_NODE_FIELD(typeName); + READ_INT_FIELD(inhcount); + READ_BOOL_FIELD(is_local); + READ_BOOL_FIELD(is_not_null); + READ_BOOL_FIELD(is_from_type); + READ_CHAR_FIELD(storage); + READ_NODE_FIELD(raw_default); + READ_NODE_FIELD(cooked_default); + READ_NODE_FIELD(collClause); + READ_OID_FIELD(collOid); + READ_NODE_FIELD(constraints); + READ_NODE_FIELD(fdwoptions); + + READ_DONE(); + } + + /* + * _readConstraint + */ + static Constraint * + _readConstraint(void) + { + READ_LOCALS(Constraint); + + READ_STRING_FIELD(conname); + READ_BOOL_FIELD(deferrable); + READ_BOOL_FIELD(initdeferred); + READ_LOCATION_FIELD(location); + + /* + * READ_ENUM_FIELD(contype,ConstrType); + * + * The contype is not written out as an enum value, but as a string. + * Depending on the value of the string some fields or some other are to be + * read in the node string. + */ + + token = pg_strtok(&length); /* skip :constraint */ + token = pg_strtok(&length); /* get field value */ + + if (strncmp(token, "NULL", 4) == 0) + local_node->contype = CONSTR_NULL; + else if (strncmp(token, "NOT_NULL", 8) == 0) + local_node->contype = CONSTR_NOTNULL; + else if (strncmp(token, "DEFAULT", 7) == 0) + { + local_node->contype = CONSTR_DEFAULT; + READ_NODE_FIELD(raw_expr); + READ_STRING_FIELD(cooked_expr); + } + else if (strncmp(token, "CHECK", 7) == 0) + { + local_node->contype = CONSTR_CHECK; + READ_NODE_FIELD(raw_expr); + READ_STRING_FIELD(cooked_expr); + } + else if (strncmp(token, "PRIMARY_KEY", 11) == 0) + { + local_node->contype = CONSTR_PRIMARY; + READ_NODE_FIELD(keys); + READ_NODE_FIELD(options); + READ_STRING_FIELD(indexname); + READ_STRING_FIELD(indexspace); + } + else if (strncmp(token, "UNIQUE", 6) == 0) + { + local_node->contype = CONSTR_UNIQUE; + READ_NODE_FIELD(keys); + READ_NODE_FIELD(options); + READ_STRING_FIELD(indexname); + READ_STRING_FIELD(indexspace); + } + else if (strncmp(token, "EXCLUSION", 9) == 0) + { + local_node->contype = CONSTR_EXCLUSION; + READ_NODE_FIELD(exclusions); + READ_NODE_FIELD(keys); + READ_NODE_FIELD(options); + READ_STRING_FIELD(indexname); + READ_STRING_FIELD(indexspace); + } + else if (strncmp(token, "FOREIGN_KEY", 11) == 0) + { + local_node->contype = CONSTR_FOREIGN; + READ_NODE_FIELD(pktable); + READ_NODE_FIELD(fk_attrs); + READ_NODE_FIELD(pk_attrs); + READ_CHAR_FIELD(fk_matchtype); + READ_CHAR_FIELD(fk_upd_action); + READ_CHAR_FIELD(fk_del_action); + READ_BOOL_FIELD(skip_validation); + READ_BOOL_FIELD(initially_valid); + } + else if (strncmp(token, "ATTR_DEFERRABLE", 15) == 0) + local_node->contype = CONSTR_ATTR_DEFERRABLE; + else if (strncmp(token, "ATTR_NOT_DEFERRABLE", 19) == 0) + local_node->contype = CONSTR_ATTR_NOT_DEFERRABLE; + else if (strncmp(token, "ATTR_DEFERRED", 13) == 0) + local_node->contype = CONSTR_ATTR_DEFERRED; + else if (strncmp(token, "ATTR_IMMEDIATE", 14) == 0) + local_node->contype = CONSTR_ATTR_IMMEDIATE; + else + elog(ERROR, "unrecognized constraint type: %d", + (int) local_node->contype); + READ_DONE(); + } + + /* * _readSortGroupClause */ static SortGroupClause * *************** *** 1232,1238 **** _readRangeTblEntry(void) READ_DONE(); } - /* * parseNodeString * --- 1435,1440 ---- *************** *** 1255,1260 **** parseNodeString(void) --- 1457,1476 ---- if (MATCH("QUERY", 5)) return_value = _readQuery(); + else if (MATCH("CREATESTMT", 10)) + return_value = _readCreateStmt(); + else if (MATCH("DROPSTMT", 8)) + return_value = _readDropStmt(); + else if (MATCH("CREATEEXTENSIONSTMT", 19)) + return_value = _readCreateExtensionStmt(); + else if (MATCH("ALTERCMDTRIGSTMT", 16)) + return_value = _readAlterCmdTrigStmt(); + else if (MATCH("TYPENAME", 8)) + return_value = _readTypeName(); + else if (MATCH("COLUMNDEF", 9)) + return_value = _readColumnDef(); + else if (MATCH("CONSTRAINT", 10)) + return_value = _readConstraint(); 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" *************** *** 195,200 **** static void processCASbits(int cas_bits, int location, const char *constrType, --- 196,202 ---- } %type stmt schema_stmt + AlterCmdTrigStmt AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt *************** *** 208,219 **** 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 --- 210,221 ---- 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 *************** *** 264,273 **** 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 --- 266,277 ---- %type OptSchemaEltList %type TriggerForSpec TriggerForType ! %type TriggerActionTime CmdTriggerActionTime %type TriggerEvents TriggerOneEvent %type TriggerFuncArg %type TriggerWhen + %type trigger_command enable_trigger + %type trigger_command_list %type copy_file_name database_name access_method_clause access_method attr_name *************** *** 495,501 **** 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 --- 499,505 ---- 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 *************** *** 675,681 **** stmtmulti: stmtmulti ';' stmt ; stmt : ! AlterDatabaseStmt | AlterDatabaseSetStmt | AlterDefaultPrivilegesStmt | AlterDomainStmt --- 679,686 ---- ; stmt : ! AlterCmdTrigStmt ! | AlterDatabaseStmt | AlterDatabaseSetStmt | AlterDefaultPrivilegesStmt | AlterDomainStmt *************** *** 726,731 **** stmt : --- 731,737 ---- | CreateStmt | CreateTableSpaceStmt | CreateTrigStmt + | CreateCmdTrigStmt | CreateRoleStmt | CreateUserStmt | CreateUserMappingStmt *************** *** 749,754 **** stmt : --- 755,761 ---- | DropStmt | DropTableSpaceStmt | DropTrigStmt + | DropCmdTrigStmt | DropRoleStmt | DropUserStmt | DropUserMappingStmt *************** *** 4260,4265 **** DropTrigStmt: --- 4267,4454 ---- /***************************************************************************** * * QUERIES : + * CREATE TRIGGER ... BEFORE|INSTEAD OF|AFTER COMMAND ... + * DROP TRIGGER ... ON COMMAND ... + * + *****************************************************************************/ + + CreateCmdTrigStmt: + CREATE TRIGGER name CmdTriggerActionTime COMMAND trigger_command_list + EXECUTE PROCEDURE func_name '(' ')' + { + CreateCmdTrigStmt *n = makeNode(CreateCmdTrigStmt); + n->trigname = $3; + n->timing = $4; + n->command = $6; + n->funcname = $9; + $$ = (Node *)n; + } + | CREATE TRIGGER name CmdTriggerActionTime ANY COMMAND + EXECUTE PROCEDURE func_name '(' ')' + { + CreateCmdTrigStmt *n = makeNode(CreateCmdTrigStmt); + n->trigname = $3; + n->timing = $4; + n->command = list_make1(makeStringConst("ANY", @5)); + n->funcname = $9; + $$ = (Node *)n; + } + ; + + CmdTriggerActionTime: + BEFORE { $$ = CMD_TRIGGER_FIRED_BEFORE; } + | AFTER { $$ = CMD_TRIGGER_FIRED_AFTER; } + ; + + trigger_command_list: + trigger_command + { + $$ = list_make1(makeStringConst($1, @1)); + } + | trigger_command_list ',' trigger_command + { + $$ = lappend($1, makeStringConst($3, @1)); + } + ; + + + /* + * 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 FUNCTION { $$ = "CREATE FUNCTION"; } + | CREATE TABLE { $$ = "CREATE TABLE"; } + | CREATE FOREIGN TABLE { $$ = "CREATE FOREIGN TABLE"; } + | CREATE INDEX { $$ = "CREATE INDEX"; } + | CREATE SEQUENCE { $$ = "CREATE SEQUENCE"; } + | CREATE VIEW { $$ = "CREATE VIEW"; } + | 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"; } + | ALTER SCHEMA { $$ = "ALTER SCHEMA"; } + | ALTER EXTENSION { $$ = "ALTER EXTENSION"; } + | ALTER FUNCTION { $$ = "ALTER FUNCTION"; } + | ALTER TABLE { $$ = "ALTER TABLE"; } + | ALTER AGGREGATE { $$ = "ALTER AGGREGATE"; } + | ALTER OPERATOR { $$ = "ALTER OPERATOR"; } + | 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"; } + | DROP TABLE { $$ = "DROP TABLE"; } + | DROP SEQUENCE { $$ = "DROP SEQUENCE"; } + | DROP VIEW { $$ = "DROP VIEW"; } + | DROP INDEX { $$ = "DROP INDEX"; } + | DROP FOREIGN TABLE { $$ = "DROP FOREIGN TABLE"; } + | DROP TYPE_P { $$ = "DROP TYPE"; } + | DROP DOMAIN_P { $$ = "DROP DOMAIN"; } + | DROP COLLATION { $$ = "DROP COLLATION"; } + | DROP CONVERSION_P { $$ = "DROP CONVERSION_P"; } + | 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 FOREIGN DATA_P WRAPPER { $$ = "DROP FOREIGN DATA WRAPPER"; } + | DROP SERVER { $$ = "DROP SERVER"; } + | DROP TRIGGER { $$ = "DROP TRIGGER"; } + | DROP ASSERTION { $$ = "DROP ASSERTION"; } + | 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"; } + | VACUUM { $$ = "VACUUM"; } + ; + + DropCmdTrigStmt: + DROP TRIGGER name ON COMMAND trigger_command_list opt_drop_behavior + { + DropCmdTrigStmt *n = makeNode(DropCmdTrigStmt); + n->trigname = $3; + n->command = $6; + n->behavior = $7; + n->missing_ok = false; + $$ = (Node *) n; + } + | DROP TRIGGER IF_P EXISTS name ON COMMAND trigger_command_list opt_drop_behavior + { + DropCmdTrigStmt *n = makeNode(DropCmdTrigStmt); + n->trigname = $5; + n->command = $8; + n->behavior = $9; + n->missing_ok = true; + $$ = (Node *) n; + } + | DROP TRIGGER name ON ANY COMMAND opt_drop_behavior + { + DropCmdTrigStmt *n = makeNode(DropCmdTrigStmt); + n->trigname = $3; + n->command = list_make1(makeStringConst("ANY", @4)); + n->behavior = $7; + n->missing_ok = false; + $$ = (Node *) n; + } + | DROP TRIGGER IF_P EXISTS name ON ANY COMMAND opt_drop_behavior + { + DropCmdTrigStmt *n = makeNode(DropCmdTrigStmt); + n->trigname = $5; + n->command = list_make1(makeStringConst("ANY", @6)); + n->behavior = $9; + n->missing_ok = true; + $$ = (Node *) n; + } + ; + + AlterCmdTrigStmt: + ALTER TRIGGER name ON COMMAND trigger_command SET enable_trigger + { + AlterCmdTrigStmt *n = makeNode(AlterCmdTrigStmt); + n->trigname = $3; + n->command = $6; + n->tgenabled = $8; + $$ = (Node *) n; + } + | ALTER TRIGGER name ON ANY COMMAND SET enable_trigger + { + AlterCmdTrigStmt *n = makeNode(AlterCmdTrigStmt); + n->trigname = $3; + n->command = "ANY"; + n->tgenabled = $8; + $$ = (Node *) n; + } + ; + + enable_trigger: + ENABLE_P { $$ = "O"; } + | ENABLE_P REPLICA { $$ = "R"; } + | ENABLE_P ALWAYS { $$ = "A"; } + | DISABLE_P { $$ = "D"; } + ; + + /***************************************************************************** + * + * QUERIES : * CREATE ASSERTION ... * DROP ASSERTION ... * *************** *** 6763,6768 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name --- 6952,6966 ---- n->missing_ok = false; $$ = (Node *)n; } + | ALTER TRIGGER name ON COMMAND trigger_command RENAME TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_CMDTRIGGER; + n->object = list_make1(makeString($6)); + n->subname = $3; + n->newname = $9; + $$ = (Node *)n; + } | ALTER ROLE RoleId RENAME TO RoleId { RenameStmt *n = makeNode(RenameStmt); *** 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" *************** *** 58,66 **** #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; --- 59,67 ---- #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; *************** *** 150,155 **** CommandIsReadOnly(Node *parsetree) --- 151,305 ---- } /* + * Support function for calling the command triggers. + */ + static void + call_before_cmdtriggers(CommandContext cmd) + { + switch (nodeTag(cmd->parsetree)) + { + case T_AlterDatabaseStmt: + case T_AlterDatabaseSetStmt: + case T_AlterDomainStmt: + case T_AlterFunctionStmt: + case T_AlterRoleStmt: + case T_AlterRoleSetStmt: + case T_AlterObjectSchemaStmt: + case T_AlterOwnerStmt: + case T_AlterSeqStmt: + case T_AlterTableStmt: + case T_RenameStmt: + case T_CommentStmt: + case T_DefineStmt: + case T_CreateCastStmt: + case T_CreateConversionStmt: + case T_CreatedbStmt: + case T_CreateDomainStmt: + case T_CreateFunctionStmt: + case T_CreateRoleStmt: + case T_IndexStmt: + case T_CreatePLangStmt: + case T_CreateOpClassStmt: + case T_CreateOpFamilyStmt: + case T_AlterOpFamilyStmt: + case T_RuleStmt: + case T_CreateSchemaStmt: + case T_CreateSeqStmt: + case T_CreateStmt: + case T_CreateTableSpaceStmt: + case T_CreateTrigStmt: + case T_CompositeTypeStmt: + case T_CreateEnumStmt: + case T_CreateRangeStmt: + case T_AlterEnumStmt: + case T_ViewStmt: + case T_DropStmt: + case T_DropdbStmt: + case T_DropTableSpaceStmt: + case T_DropRoleStmt: + case T_GrantStmt: + case T_GrantRoleStmt: + case T_AlterDefaultPrivilegesStmt: + case T_TruncateStmt: + case T_DropOwnedStmt: + case T_ReassignOwnedStmt: + case T_AlterTSDictionaryStmt: + case T_AlterTSConfigurationStmt: + case T_CreateExtensionStmt: + case T_AlterExtensionStmt: + case T_AlterExtensionContentsStmt: + case T_CreateFdwStmt: + case T_AlterFdwStmt: + case T_CreateForeignServerStmt: + case T_AlterForeignServerStmt: + case T_CreateUserMappingStmt: + case T_AlterUserMappingStmt: + case T_DropUserMappingStmt: + case T_AlterTableSpaceOptionsStmt: + case T_CreateForeignTableStmt: + case T_SecLabelStmt: + ExecBeforeCommandTriggers(cmd); + + default: + /* commands that don't support triggers */ + return; + } + } + + static void + call_after_cmdtriggers(CommandContext cmd) + { + switch (nodeTag(cmd->parsetree)) + { + case T_AlterDatabaseStmt: + case T_AlterDatabaseSetStmt: + case T_AlterDomainStmt: + case T_AlterFunctionStmt: + case T_AlterRoleStmt: + case T_AlterRoleSetStmt: + case T_AlterObjectSchemaStmt: + case T_AlterOwnerStmt: + case T_AlterSeqStmt: + case T_AlterTableStmt: + case T_RenameStmt: + case T_CommentStmt: + case T_DefineStmt: + case T_CreateCastStmt: + case T_CreateConversionStmt: + case T_CreatedbStmt: + case T_CreateDomainStmt: + case T_CreateFunctionStmt: + case T_CreateRoleStmt: + case T_IndexStmt: + case T_CreatePLangStmt: + case T_CreateOpClassStmt: + case T_CreateOpFamilyStmt: + case T_AlterOpFamilyStmt: + case T_RuleStmt: + case T_CreateSchemaStmt: + case T_CreateSeqStmt: + case T_CreateStmt: + case T_CreateTableSpaceStmt: + case T_CreateTrigStmt: + case T_CompositeTypeStmt: + case T_CreateEnumStmt: + case T_CreateRangeStmt: + case T_AlterEnumStmt: + case T_ViewStmt: + case T_DropStmt: + case T_DropdbStmt: + case T_DropTableSpaceStmt: + case T_DropRoleStmt: + case T_GrantStmt: + case T_GrantRoleStmt: + case T_AlterDefaultPrivilegesStmt: + case T_TruncateStmt: + case T_DropOwnedStmt: + case T_ReassignOwnedStmt: + case T_AlterTSDictionaryStmt: + case T_AlterTSConfigurationStmt: + case T_CreateExtensionStmt: + case T_AlterExtensionStmt: + case T_AlterExtensionContentsStmt: + case T_CreateFdwStmt: + case T_AlterFdwStmt: + case T_CreateForeignServerStmt: + case T_AlterForeignServerStmt: + case T_CreateUserMappingStmt: + case T_AlterUserMappingStmt: + case T_DropUserMappingStmt: + case T_AlterTableSpaceOptionsStmt: + case T_CreateForeignTableStmt: + case T_SecLabelStmt: + ExecAfterCommandTriggers(cmd); + + default: + /* commands that don't support triggers */ + return; + } + } + + /* * check_xact_readonly: is a utility command read-only? * * Here we use the loose rules of XactReadOnly mode: no permanent effects *************** *** 184,189 **** check_xact_readonly(Node *parsetree) --- 334,341 ---- case T_CommentStmt: case T_DefineStmt: case T_CreateCastStmt: + case T_CreateCmdTrigStmt: + case T_AlterCmdTrigStmt: case T_CreateConversionStmt: case T_CreatedbStmt: case T_CreateDomainStmt: *************** *** 205,210 **** check_xact_readonly(Node *parsetree) --- 357,363 ---- case T_CreateRangeStmt: case T_AlterEnumStmt: case T_ViewStmt: + case T_DropCmdTrigStmt: case T_DropStmt: case T_DropdbStmt: case T_DropTableSpaceStmt: *************** *** 344,354 **** standard_ProcessUtility(Node *parsetree, --- 497,512 ---- DestReceiver *dest, char *completionTag) { + CommandContextData cmd; check_xact_readonly(parsetree); if (completionTag) completionTag[0] = '\0'; + /* call the BEFORE ANY COMMAND triggers first */ + InitCommandContext(&cmd, parsetree, true); + call_before_cmdtriggers(&cmd); + switch (nodeTag(parsetree)) { /* *************** *** 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) --- 667,693 ---- { List *stmts; ListCell *l; ! Oid relOid = InvalidOid; ! CreateStmt *stmt = (CreateStmt *) parsetree; ! CommandContextData cmd; ! ! /* ! * Call BEFORE CREATE TABLE triggers ! */ ! InitCommandContext(&cmd, parsetree, false); ! ! if (CommandFiresTriggers(&cmd)) ! { ! cmd.objectId = InvalidOid; ! cmd.objectname = stmt->relation->relname; ! cmd.schemaname = get_namespace_name( ! RangeVarGetCreationNamespace(stmt->relation)); ! ! ExecBeforeCommandTriggers(&cmd); ! } /* Run parse analysis ... */ ! stmts = transformCreateStmt(stmt, queryString); /* ... and do it */ foreach(l, stmts) *************** *** 571,576 **** standard_ProcessUtility(Node *parsetree, --- 745,757 ---- if (lnext(l) != NULL) CommandCounterIncrement(); } + + /* Call AFTER CREATE TABLE triggers */ + if (CommandFiresAfterTriggers(&cmd)) + { + cmd.objectId = relOid; + ExecAfterCommandTriggers(&cmd); + } } break; *************** *** 704,709 **** standard_ProcessUtility(Node *parsetree, --- 885,891 ---- List *stmts; ListCell *l; LOCKMODE lockmode; + CommandContextData cmd; /* * Figure out lock mode, and acquire lock. This also does *************** *** 716,721 **** standard_ProcessUtility(Node *parsetree, --- 898,918 ---- if (OidIsValid(relid)) { + /* + * Call BEFORE|INSTEAD OF ALTER TABLE triggers + */ + InitCommandContext(&cmd, parsetree, false); + + if (ListCommandTriggers(&cmd)) + { + cmd.objectId = relid; + cmd.objectname = atstmt->relation->relname; + cmd.schemaname = get_namespace_name( + RangeVarGetCreationNamespace(atstmt->relation)); + + ExecBeforeCommandTriggers(&cmd); + } + /* Run parse analysis ... */ stmts = transformAlterTableStmt(atstmt, queryString); *************** *** 744,749 **** standard_ProcessUtility(Node *parsetree, --- 941,949 ---- if (lnext(l) != NULL) CommandCounterIncrement(); } + /* Call AFTER ALTER TABLE triggers */ + if (cmd.after != NIL) + ExecAfterCommandTriggers(&cmd); } else ereport(NOTICE, *************** *** 819,858 **** 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", --- 1019,1061 ---- case T_DefineStmt: { DefineStmt *stmt = (DefineStmt *) parsetree; + CommandContextData cmd; + + InitCommandContext(&cmd, parsetree, false); 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", *************** *** 865,872 **** standard_ProcessUtility(Node *parsetree, case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ { CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; ! DefineCompositeType(stmt->typevar, stmt->coldeflist); } break; --- 1068,1077 ---- case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ { CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; + CommandContextData cmd; ! InitCommandContext(&cmd, parsetree, false); ! DefineCompositeType(stmt->typevar, stmt->coldeflist, &cmd); } break; *************** *** 904,909 **** standard_ProcessUtility(Node *parsetree, --- 1109,1117 ---- case T_IndexStmt: /* CREATE INDEX */ { IndexStmt *stmt = (IndexStmt *) parsetree; + CommandContextData cmd; + + InitCommandContext(&cmd, parsetree, false); if (stmt->concurrent) PreventTransactionChain(isTopLevel, *************** *** 934,940 **** standard_ProcessUtility(Node *parsetree, true, /* check_rights */ false, /* skip_build */ false, /* quiet */ ! stmt->concurrent); /* concurrent */ } break; --- 1142,1149 ---- true, /* check_rights */ false, /* skip_build */ false, /* quiet */ ! stmt->concurrent, /* concurrent */ ! &cmd); } break; *************** *** 972,978 **** standard_ProcessUtility(Node *parsetree, DropdbStmt *stmt = (DropdbStmt *) parsetree; PreventTransactionChain(isTopLevel, "DROP DATABASE"); ! dropdb(stmt->dbname, stmt->missing_ok); } break; --- 1181,1187 ---- DropdbStmt *stmt = (DropdbStmt *) parsetree; PreventTransactionChain(isTopLevel, "DROP DATABASE"); ! dropdb(stmt); } break; *************** *** 1059,1064 **** standard_ProcessUtility(Node *parsetree, --- 1268,1285 ---- InvalidOid, InvalidOid, false); break; + case T_CreateCmdTrigStmt: + CreateCmdTrigger((CreateCmdTrigStmt *) parsetree, queryString); + break; + + case T_DropCmdTrigStmt: + DropCmdTrigger((DropCmdTrigStmt *) parsetree); + break; + + case T_AlterCmdTrigStmt: + (void) AlterCmdTrigger((AlterCmdTrigStmt *) parsetree); + break; + case T_CreatePLangStmt: CreateProceduralLanguage((CreatePLangStmt *) parsetree); break; *************** *** 1197,1202 **** standard_ProcessUtility(Node *parsetree, --- 1418,1425 ---- (int) nodeTag(parsetree)); break; } + /* call the AFTER ANY COMMAND triggers */ + call_after_cmdtriggers(&cmd); } /* *************** *** 1950,1955 **** CreateCommandTag(Node *parsetree) --- 2173,2190 ---- tag = "CREATE TRIGGER"; break; + case T_CreateCmdTrigStmt: + tag = "CREATE COMMAND TRIGGER"; + break; + + case T_DropCmdTrigStmt: + tag = "DROP COMMAND TRIGGER"; + break; + + case T_AlterCmdTrigStmt: + tag = "ALTER COMMAND TRIGGER"; + break; + case T_CreatePLangStmt: tag = "CREATE LANGUAGE"; break; *************** *** 2151,2156 **** CreateCommandTag(Node *parsetree) --- 2386,2398 ---- 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; } *************** *** 2445,2450 **** GetCommandLogLevel(Node *parsetree) --- 2687,2708 ---- lev = LOGSTMT_DDL; break; + case T_DropPropertyStmt: + lev = LOGSTMT_DDL; + break; + + case T_CreateCmdTrigStmt: + lev = LOGSTMT_DDL; + break; + + case T_DropCmdTrigStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterCmdTrigStmt: + lev = LOGSTMT_DDL; + break; + case T_CreatePLangStmt: lev = LOGSTMT_DDL; break; *** 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" *************** *** 256,262 **** 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 --- 258,263 ---- *** 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,5359 ---- } /* + * 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, proname as ctgfname, ctgenabled " + "FROM pg_cmdtrigger c JOIN pg_proc p on c.ctgfoid = p.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 * *************** *** 7173,7178 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj) --- 7244,7252 ---- case DO_TRIGGER: dumpTrigger(fout, (TriggerInfo *) dobj); break; + case DO_CMDTRIGGER: + dumpCmdTrigger(fout, (CmdTriggerInfo *) dobj); + break; case DO_CONSTRAINT: dumpConstraint(fout, (ConstraintInfo *) dobj); break; *************** *** 13645,13650 **** dumpTrigger(Archive *fout, TriggerInfo *tginfo) --- 13719,13777 ---- destroyPQExpBuffer(labelq); } + static void + dumpCmdTrigger(Archive *fout, CmdTriggerInfo *ctginfo) + { + PQExpBuffer query; + PQExpBuffer labelq; + + query = createPQExpBuffer(); + labelq = createPQExpBuffer(); + + appendPQExpBuffer(query, "CREATE 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, " COMMAND "); + appendPQExpBufferStr(query, fmtId(ctginfo->ctgcommand)); + } + + appendPQExpBuffer(query, " EXECUTE PROCEDURE "); + appendPQExpBufferStr(query, fmtId(ctginfo->ctgfname)); + appendPQExpBuffer(query, " ();\n"); + + appendPQExpBuffer(labelq, "TRIGGER %s ", + fmtId(ctginfo->dobj.name)); + appendPQExpBuffer(labelq, "ON COMMAND %s", + fmtId(ctginfo->ctgcommand)); + + 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 *************** *** 2941,2946 **** listConversions(const char *pattern, bool verbose, bool showSystem) --- 2941,3003 ---- } /* + * \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/indexing.h --- b/src/include/catalog/indexing.h *************** *** 234,239 **** DECLARE_UNIQUE_INDEX(pg_trigger_tgrelid_tgname_index, 2701, on pg_trigger using --- 234,244 ---- DECLARE_UNIQUE_INDEX(pg_trigger_oid_index, 2702, on pg_trigger using btree(oid oid_ops)); #define TriggerOidIndexId 2702 + DECLARE_UNIQUE_INDEX(pg_cmdtrigger_ctgcommand_ctgname_index, 3467, on pg_cmdtrigger using btree(ctgcommand name_ops, ctgname name_ops)); + #define CmdTriggerCommandNameIndexId 3467 + DECLARE_UNIQUE_INDEX(pg_cmdtrigger_oid_index, 3468, on pg_cmdtrigger using btree(oid oid_ops)); + #define CmdTriggerOidIndexId 3468 + 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 ctgcommand; /* trigger's command */ + NameData ctgname; /* trigger's name */ + 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_ctgcommand 1 + #define Anum_pg_cmdtrigger_ctgname 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" /* ---------------- *************** *** 1722,1727 **** extern void OperatorCreate(const char *operatorName, Oid restrictionId, Oid joinId, bool canMerge, ! bool canHash); #endif /* PG_OPERATOR_H */ --- 1723,1729 ---- Oid restrictionId, Oid joinId, bool canMerge, ! bool canHash, ! CommandContext cmd); #endif /* PG_OPERATOR_H */ *** 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 */ *** /dev/null --- b/src/include/commands/cmdtrigger.h *************** *** 0 **** --- 1,55 ---- + /*------------------------------------------------------------------------- + * + * 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 */ + } CommandContextData; + + typedef struct CommandContextData *CommandContext; + + extern CommandContext command_context; + + extern void CreateCmdTrigger(CreateCmdTrigStmt *stmt, const char *queryString); + extern void DropCmdTrigger(DropCmdTrigStmt *stmt); + extern void RemoveCmdTriggerById(Oid ctrigOid); + extern Oid get_cmdtrigger_oid(const char *command, const char *trigname, bool missing_ok); + extern void AlterCmdTrigger(AlterCmdTrigStmt *stmt); + extern void RenameCmdTrigger(List *command, const char *trigname, const char *newname); + + extern void InitCommandContext(CommandContext cmd, const Node *stmt, bool list_triggers); + extern bool ListCommandTriggers(CommandContext cmd); + 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/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,45 **** 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, --- 40,47 ---- bool check_rights, bool skip_build, bool quiet, ! bool concurrent, ! CommandContext cmd); extern void ReindexIndex(RangeVar *indexRelation); extern void ReindexTable(RangeVar *relation); extern void ReindexDatabase(const char *databaseName, *************** *** 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); *************** *** 103,145 **** extern void RenameOpClass(List *name, const char *access_method, const char *new 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); --- 107,151 ---- 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, CommandContext cmd); 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, 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); *** 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/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,20 **** --- 15,21 ---- #define TABLECMDS_H #include "access/htup.h" + #include "commands/cmdtrigger.h" #include "nodes/parsenodes.h" #include "storage/lock.h" #include "utils/relcache.h" *************** *** 48,57 **** extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass); extern void renameatt(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, --- 49,58 ---- extern void renameatt(RenameStmt *stmt); ! extern void RenameRelation(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,31 **** #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(const RangeVar *typevar, List *coldeflist); extern Oid AssignTypeArrayOid(void); extern void AlterDomainDefault(List *names, Node *defaultRaw); --- 14,33 ---- #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(const RangeVar *typevar, List *coldeflist, CommandContext cmd); extern Oid AssignTypeArrayOid(void); extern void AlterDomainDefault(List *names, Node *defaultRaw); *************** *** 37,50 **** extern void AlterDomainDropConstraint(List *names, const char *constrName, 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 */ --- 39,55 ---- 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 *************** *** 290,295 **** typedef enum NodeTag --- 290,296 ---- T_IndexStmt, T_CreateFunctionStmt, T_AlterFunctionStmt, + T_RemoveFuncStmt, T_DoStmt, T_RenameStmt, T_RuleStmt, *************** *** 310,316 **** typedef enum NodeTag --- 311,319 ---- T_VariableShowStmt, T_DiscardStmt, T_CreateTrigStmt, + T_DropPropertyStmt, T_CreatePLangStmt, + T_DropPLangStmt, T_CreateRoleStmt, T_AlterRoleStmt, T_DropRoleStmt, *************** *** 324,332 **** typedef enum NodeTag --- 327,338 ---- 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 --- 351,360 ---- 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 --- 364,372 ---- T_CreateExtensionStmt, T_AlterExtensionStmt, T_AlterExtensionContentsStmt, + T_CreateCmdTrigStmt, + T_DropCmdTrigStmt, + T_AlterCmdTrigStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 1107,1112 **** typedef enum ObjectType --- 1107,1113 ---- OBJECT_AGGREGATE, OBJECT_ATTRIBUTE, /* type's attribute, when distinct from column */ OBJECT_CAST, + OBJECT_CMDTRIGGER, OBJECT_COLUMN, OBJECT_CONSTRAINT, OBJECT_COLLATION, *************** *** 1729,1734 **** typedef struct CreateTrigStmt --- 1730,1763 ---- } CreateTrigStmt; /* ---------------------- + * Create COMMAND TRIGGER Statement + * ---------------------- + */ + typedef struct CreateCmdTrigStmt + { + NodeTag type; + List *command; /* commands 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 *command; /* command's name */ + 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 * ---------------------- */ *************** *** 1911,1916 **** typedef struct DropStmt --- 1940,1976 ---- } DropStmt; /* ---------------------- + * Drop Rule|Trigger Statement + * + * In general this may be used for dropping any property of a relation; + * for example, someday soon we may have DROP ATTRIBUTE. + * ---------------------- + */ + + typedef struct DropPropertyStmt + { + NodeTag type; + RangeVar *relation; /* owning relation */ + char *property; /* name of rule, trigger, etc */ + ObjectType removeType; /* OBJECT_RULE or OBJECT_TRIGGER */ + DropBehavior behavior; /* RESTRICT or CASCADE behavior */ + bool missing_ok; /* skip error if missing? */ + } DropPropertyStmt; + + /* ---------------------- + * Drop Command Trigger Statement + * ---------------------- + */ + typedef struct DropCmdTrigStmt + { + NodeTag type; + List *command; /* command's name */ + char *trigname; /* TRIGGER's name */ + DropBehavior behavior; /* RESTRICT or CASCADE behavior */ + bool missing_ok; /* skip error if missing? */ + } DropCmdTrigStmt; + + /* ---------------------- * Truncate Table Statement * ---------------------- */ *** 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, UNRESERVED_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) *** /dev/null --- b/src/test/regress/expected/cmdtriggers.out *************** *** 0 **** --- 1,183 ---- + -- + -- COMMAND TRIGGERS + -- + create or replace function snitch + (in tg_when text, in cmd_tag text, in objectid oid, in schemaname text, in objectname text) + returns void language plpgsql + as $$ + begin + -- can't output the objectid here that would break pg_regress + raise notice 'snitch: % % % %', tg_when, cmd_tag, schemaname, objectname; + end; + $$; + create trigger snitch_before before any command execute procedure snitch(); + create trigger snitch_after after any command execute procedure snitch(); + alter trigger snitch_before on any command set disable; + alter trigger snitch_before on any command set enable; + create trigger snitch_some_more + after command create table, alter table, drop table, + create function, create collation, + alter operator, create domain, alter schema + execute procedure snitch(); + create trigger snitch_some_even_more + before command create trigger, alter trigger, drop trigger, + create schema, drop schema, + create aggregate, alter collation, create operator, + alter domain, create type, alter type + execute procedure snitch(); + create schema cmd; + NOTICE: snitch: BEFORE CREATE SCHEMA + NOTICE: snitch: BEFORE CREATE SCHEMA cmd + NOTICE: snitch: AFTER CREATE SCHEMA + create table cmd.foo(id bigserial primary key); + NOTICE: snitch: BEFORE CREATE TABLE + NOTICE: CREATE TABLE will create implicit sequence "foo_id_seq" for serial column "foo.id" + NOTICE: snitch: BEFORE CREATE SEQUENCE + NOTICE: snitch: AFTER CREATE SEQUENCE + NOTICE: snitch: BEFORE CREATE INDEX + NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo" + NOTICE: snitch: AFTER CREATE INDEX + NOTICE: snitch: BEFORE ALTER SEQUENCE + NOTICE: snitch: AFTER ALTER SEQUENCE + NOTICE: snitch: AFTER CREATE TABLE cmd foo + NOTICE: snitch: AFTER CREATE TABLE + create view cmd.v as select * from cmd.foo; + NOTICE: snitch: BEFORE CREATE VIEW + NOTICE: snitch: AFTER CREATE VIEW + alter table cmd.foo add column t text; + NOTICE: snitch: BEFORE ALTER TABLE + NOTICE: snitch: AFTER ALTER TABLE cmd foo + NOTICE: snitch: AFTER ALTER TABLE + 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 CREATE INDEX + NOTICE: snitch: AFTER CREATE INDEX + drop index cmd.idx_foo; + NOTICE: snitch: BEFORE DROP INDEX + NOTICE: snitch: AFTER DROP INDEX + create function cmd.fun(int) returns text language sql + as $$ select t from cmd.foo where id = $1; $$; + NOTICE: snitch: BEFORE CREATE FUNCTION + NOTICE: snitch: AFTER CREATE FUNCTION cmd fun + NOTICE: snitch: AFTER CREATE FUNCTION + alter function cmd.fun(int) strict; + NOTICE: snitch: BEFORE ALTER FUNCTION + NOTICE: snitch: AFTER ALTER FUNCTION + alter function cmd.fun(int) rename to notfun; + NOTICE: snitch: BEFORE ALTER FUNCTION + NOTICE: snitch: AFTER ALTER FUNCTION + drop function cmd.notfun(int); + NOTICE: snitch: BEFORE DROP FUNCTION + NOTICE: snitch: AFTER DROP FUNCTION + create function cmd.plus1(int) returns bigint language sql + as $$ select $1::bigint + 1; $$; + NOTICE: snitch: BEFORE CREATE FUNCTION + NOTICE: snitch: AFTER CREATE FUNCTION cmd plus1 + NOTICE: snitch: AFTER CREATE FUNCTION + create operator cmd.+!(procedure = cmd.plus1, leftarg = int); + NOTICE: snitch: BEFORE CREATE OPERATOR + NOTICE: snitch: BEFORE CREATE OPERATOR cmd +! + NOTICE: snitch: AFTER CREATE OPERATOR + alter operator cmd.+!(int, NONE) set schema public; + NOTICE: snitch: BEFORE ALTER OPERATOR + NOTICE: snitch: AFTER ALTER OPERATOR public +! + NOTICE: snitch: AFTER ALTER OPERATOR + drop operator public.+!(int, NONE); + NOTICE: snitch: BEFORE DROP OPERATOR + NOTICE: snitch: AFTER DROP OPERATOR + create aggregate cmd.avg (float8) + ( + sfunc = float8_accum, + stype = float8[], + finalfunc = float8_avg, + initcond = '{0,0,0}' + ); + NOTICE: snitch: BEFORE CREATE AGGREGATE + NOTICE: snitch: BEFORE CREATE AGGREGATE cmd avg + NOTICE: snitch: AFTER CREATE AGGREGATE + alter aggregate cmd.avg(float8) set schema public; + NOTICE: snitch: BEFORE ALTER AGGREGATE + NOTICE: snitch: AFTER ALTER AGGREGATE + drop aggregate public.avg(float8); + NOTICE: snitch: BEFORE DROP AGGREGATE + NOTICE: snitch: AFTER DROP AGGREGATE + create collation cmd.french (LOCALE = 'fr_FR'); + NOTICE: snitch: BEFORE CREATE COLLATION + NOTICE: snitch: AFTER CREATE COLLATION cmd french + NOTICE: snitch: AFTER CREATE COLLATION + alter collation cmd.french rename to francais; + NOTICE: snitch: BEFORE ALTER COLLATION + NOTICE: snitch: BEFORE ALTER COLLATION cmd french + NOTICE: snitch: AFTER ALTER COLLATION + create type cmd.compfoo AS (f1 int, f2 text); + NOTICE: snitch: BEFORE CREATE TYPE + NOTICE: snitch: BEFORE CREATE TYPE cmd compfoo + NOTICE: snitch: AFTER CREATE TYPE + alter type cmd.compfoo add attribute f3 text; + NOTICE: snitch: BEFORE ALTER TYPE + NOTICE: snitch: BEFORE ALTER TYPE cmd compfoo + NOTICE: snitch: AFTER ALTER TYPE + drop type cmd.compfoo; + NOTICE: snitch: BEFORE DROP TYPE + NOTICE: snitch: AFTER DROP TYPE + create type cmd.bug_status as enum ('new', 'open', 'closed'); + NOTICE: snitch: BEFORE CREATE TYPE + NOTICE: snitch: BEFORE CREATE TYPE cmd bug_status + NOTICE: snitch: AFTER CREATE TYPE + alter type cmd.bug_status add value 'wontfix'; + NOTICE: snitch: BEFORE ALTER TYPE + NOTICE: snitch: AFTER ALTER TYPE + create domain cmd.us_postal_code as text check(value ~ '^\d{5}$' or value ~ '^\d{5}-\d{4}$'); + NOTICE: snitch: BEFORE CREATE DOMAIN + NOTICE: snitch: AFTER CREATE DOMAIN cmd us_postal_code + NOTICE: snitch: AFTER CREATE DOMAIN + alter domain cmd.us_postal_code set not null; + NOTICE: snitch: BEFORE ALTER DOMAIN + NOTICE: snitch: AFTER ALTER DOMAIN + create function cmd.trigfunc() returns trigger language plpgsql as + $$ begin raise notice 'trigfunc'; end;$$; + NOTICE: snitch: BEFORE CREATE FUNCTION + NOTICE: snitch: AFTER CREATE FUNCTION cmd trigfunc + NOTICE: snitch: AFTER CREATE FUNCTION + create trigger footg before update on cmd.foo for each row execute procedure cmd.trigfunc(); + NOTICE: snitch: BEFORE CREATE TRIGGER + NOTICE: snitch: BEFORE CREATE TRIGGER cmd footg + NOTICE: snitch: AFTER CREATE TRIGGER + alter trigger footg on cmd.foo rename to foo_trigger; + NOTICE: snitch: BEFORE ALTER TRIGGER + NOTICE: snitch: BEFORE ALTER TRIGGER cmd footg + NOTICE: snitch: AFTER ALTER TRIGGER + drop trigger foo_trigger on cmd.foo; + NOTICE: snitch: BEFORE DROP TRIGGER + NOTICE: snitch: BEFORE DROP TRIGGER foo_trigger + NOTICE: snitch: AFTER DROP TRIGGER + alter schema cmd rename to cmd1; + NOTICE: snitch: BEFORE ALTER SCHEMA + NOTICE: snitch: AFTER ALTER SCHEMA cmd1 + NOTICE: snitch: AFTER ALTER SCHEMA + drop schema cmd1 cascade; + NOTICE: snitch: BEFORE DROP SCHEMA + NOTICE: snitch: BEFORE DROP SCHEMA cmd1 + NOTICE: drop cascades to 8 other objects + DETAIL: drop cascades to table cmd1.foo + drop cascades to view cmd1.v + 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 type cmd1.us_postal_code + drop cascades to function cmd1.trigfunc() + NOTICE: snitch: AFTER DROP SCHEMA + drop trigger snitch_before on any command; + drop trigger snitch_after on any command; + drop trigger snitch_some_more + on command create table, alter table, drop table, + create function, create collation, + alter operator, create domain, alter schema; + drop trigger snitch_some_even_more + on command create trigger, alter trigger, drop trigger, + create schema, drop schema, + create aggregate, alter collation, create operator, + alter domain, create type, alter type; *** 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/serial_schedule --- b/src/test/regress/serial_schedule *************** *** 61,66 **** test: create_aggregate --- 61,67 ---- 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,105 ---- + -- + -- COMMAND TRIGGERS + -- + create or replace function snitch + (in tg_when text, in cmd_tag text, in objectid oid, in schemaname text, in objectname text) + returns void language plpgsql + as $$ + begin + -- can't output the objectid here that would break pg_regress + raise notice 'snitch: % % % %', tg_when, cmd_tag, schemaname, objectname; + end; + $$; + + create trigger snitch_before before any command execute procedure snitch(); + create trigger snitch_after after any command execute procedure snitch(); + + alter trigger snitch_before on any command set disable; + alter trigger snitch_before on any command set enable; + + create trigger snitch_some_more + after command create table, alter table, drop table, + create function, create collation, + alter operator, create domain, alter schema + execute procedure snitch(); + + create trigger snitch_some_even_more + before command create trigger, alter trigger, drop trigger, + create schema, drop schema, + create aggregate, alter collation, create operator, + alter domain, create type, alter type + execute procedure snitch(); + + create schema cmd; + 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; + + set session_replication_role to replica; + create table cmd.bar(); + reset session_replication_role; + + create index idx_foo on cmd.foo(t); + drop index cmd.idx_foo; + + create function cmd.fun(int) returns text language sql + as $$ select t from cmd.foo where id = $1; $$; + + alter function cmd.fun(int) strict; + alter function cmd.fun(int) rename to notfun; + 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; + drop type cmd.compfoo; + + 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; + + 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; + + alter schema cmd rename to cmd1; + + drop schema cmd1 cascade; + + drop trigger snitch_before on any command; + drop trigger snitch_after on any command; + drop trigger snitch_some_more + on command create table, alter table, drop table, + create function, create collation, + alter operator, create domain, alter schema; + + drop trigger snitch_some_even_more + on command create trigger, alter trigger, drop trigger, + create schema, drop schema, + create aggregate, alter collation, create operator, + alter domain, create type, alter type; + *** 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 ----