*** a/doc/src/sgml/ref/create_trigger.sgml --- b/doc/src/sgml/ref/create_trigger.sgml *************** *** 29,40 **** CREATE [ CONSTRAINT ] TRIGGER name --- 29,54 ---- [ WHEN ( condition ) ] EXECUTE PROCEDURE function_name ( arguments ) + CREATE TRIGGER name { BEFORE | AFTER | INSTEAD OF } COMMAND command + EXECUTE PROCEDURE function_name ( arguments ) + where event can be one of: INSERT UPDATE [ OF column_name [, ... ] ] DELETE TRUNCATE + + and where command can be one of: + + CREATE TABLE + ALTER TABLE + DROP TABLE + CREATE VIEW + DROP VIEW + CREATE EXTENSION + DROP EXTENSION + *************** *** 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. --- 56,75 ---- 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 before trigger's ! function must return a boolean, returning False allows ! the procedure to prevent the command execution. AFTER and INSTEAD OF ! triggers return value is not used, such trigger's function must then ! 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. --- 367,386 ---- 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 BEFORE COMMAND trigger, the user-supplied function ! must be declared as taking 4 text arguments and returning a boolean. ! In the case of an AFTER COMMAND trigger or an INSTEAD OF trigger, the ! user-supplied function must be declare as taking 4 text arguments and ! returning void. ! ! ! The command trigger function is called with the ! parameters command string (normalized command ! string rewritten by PostgreSQL), schemaname (can be ! null) and object name. *************** *** 352,357 **** UPDATE OF column_name1 [, column_name2 + + Command triggers pay no attention to + the arguments. + *************** *** 469,474 **** CREATE TRIGGER view_insert --- 519,553 ---- FOR EACH ROW EXECUTE PROCEDURE view_insert_row(); + + Execute the function enforce_local_style each time + a CREATE TABLE command is run: + + + CREATE OR REPLACE FUNCTION enforce_local_style + ( + IN cmd_tag text, + IN cmd_string text, + IN schemaname text, + IN relname text + ) + RETURNS bool + LANGUAGE plpgsql + AS $$ + BEGIN + IF substring(relname, 0, 4) NOT IN ('ab_', 'cz_', 'fr_') + THEN + RAISE WARNING 'invalid relation name: %', relname; + RETURN FALSE; + END IF; + RETURN TRUE; + END; + $$; + + CREATE TRIGGER check_style + BEFORE COMMAND CREATE TABLE + EXECUTE PROCEDURE enforce_local_style(); + *************** *** 531,536 **** CREATE TRIGGER view_insert --- 610,620 ---- + 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. *** a/doc/src/sgml/ref/drop_trigger.sgml --- b/doc/src/sgml/ref/drop_trigger.sgml *************** *** 22,27 **** PostgreSQL documentation --- 22,39 ---- DROP TRIGGER [ IF EXISTS ] name ON table [ CASCADE | RESTRICT ] + DROP TRIGGER [ IF EXISTS ] name ON COMMAND command [ CASCADE | RESTRICT ] + + where command can be one of: + + CREATE TABLE + ALTER TABLE + DROP TABLE + CREATE VIEW + DROP VIEW + CREATE EXTENSION + DROP EXTENSION + *************** *** 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. --- 41,50 ---- 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/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 */ }; *************** *** 1052,1057 **** doDeletion(const ObjectAddress *object) --- 1055,1064 ---- break; } + case OCLASS_CMDTRIGGER: + RemoveCmdTriggerById(object->objectId); + break; + case OCLASS_PROC: RemoveFunctionById(object->objectId); break; *************** *** 2173,2178 **** getObjectClass(const ObjectAddress *object) --- 2180,2188 ---- case ExtensionRelationId: return OCLASS_EXTENSION; + + case CmdTriggerRelationId: + return OCLASS_CMDTRIGGER; } /* shouldn't get here */ *************** *** 2807,2812 **** getObjectDescription(const ObjectAddress *object) --- 2817,2861 ---- 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/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/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" *************** *** 61,66 **** ExecRenameStmt(RenameStmt *stmt) --- 62,71 ---- RenameConversion(stmt->object, stmt->newname); break; + case OBJECT_CMDTRIGGER: + RenameCmdTrigger(stmt->object, stmt->subname, stmt->newname); + break; + case OBJECT_DATABASE: RenameDatabase(stmt->subname, stmt->newname); break; *** /dev/null --- b/src/backend/commands/cmdtrigger.c *************** *** 0 **** --- 1,792 ---- + /*------------------------------------------------------------------------- + * + * 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_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 "tcop/utility.h" + + static void check_cmdtrigger_name(const char *command, const char *trigname, Relation tgrel); + static RegProcedure *list_triggers_for_command(const char *command, char type); + static RegProcedure *list_all_triggers_for_command(const char *command, char type); + static bool ExecBeforeCommandTriggers(Node *parsetree, CommandContext cmd, + MemoryContext per_command_context); + static int ExecInsteadOfCommandTriggers(Node *parsetree, CommandContext cmd, + MemoryContext per_command_context); + + + /* + * Check permission: command triggers are only available for superusers and + * database owner. Raise an exception when requirements are not fullfilled. + */ + static void + CheckCmdTriggerPrivileges() + { + if (!superuser()) + if (!pg_database_ownercheck(MyDatabaseId, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE, + get_database_name(MyDatabaseId)); + } + + /* + * 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: cmd_string, schemaname, objectname */ + Oid fargtypes[4] = {TEXTOID, TEXTOID, TEXTOID, TEXTOID}; + Oid funcoid; + Oid funcrettype; + char ctgtype; + + CheckCmdTriggerPrivileges(); + + /* + * Find and validate the trigger function. + */ + funcoid = LookupFuncName(stmt->funcname, 4, fargtypes, false); + 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); + + /* + * 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); + + switch (stmt->timing) + { + case TRIGGER_TYPE_BEFORE: + { + RegProcedure *procs = list_all_triggers_for_command(command, CMD_TRIGGER_FIRED_INSTEAD); + ctgtype = CMD_TRIGGER_FIRED_BEFORE; + if (procs[0] != InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("\"%s\" already has INSTEAD OF triggers", command), + errdetail("Commands cannot have both BEFORE and INSTEAD OF triggers."))); + break; + } + + case TRIGGER_TYPE_INSTEAD: + { + RegProcedure *before = list_all_triggers_for_command(command, CMD_TRIGGER_FIRED_BEFORE); + RegProcedure *after; + ctgtype = CMD_TRIGGER_FIRED_INSTEAD; + if (before[0] != InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("\"%s\" already has BEFORE triggers", command), + errdetail("Commands cannot have both BEFORE and INSTEAD OF triggers."))); + + after = list_all_triggers_for_command(command, CMD_TRIGGER_FIRED_AFTER); + if (after[0] != InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("\"%s\" already has AFTER triggers", command), + errdetail("Commands cannot have both AFTER and INSTEAD OF triggers."))); + break; + } + + case TRIGGER_TYPE_AFTER: + { + RegProcedure *procs = list_all_triggers_for_command(command, CMD_TRIGGER_FIRED_INSTEAD); + ctgtype = CMD_TRIGGER_FIRED_AFTER; + if (procs[0] != InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("\"%s\" already has INSTEAD OF triggers", command), + errdetail("Commands cannot have both AFTER and INSTEAD OF triggers."))); + break; + } + + default: + { + elog(ERROR, "unknown trigger type for COMMAND TRIGGER"); + return; /* make compiler happy */ + } + } + + if (ctgtype == CMD_TRIGGER_FIRED_BEFORE && funcrettype != BOOLOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("function \"%s\" must return type \"boolean\"", + NameListToString(stmt->funcname)))); + + if (ctgtype != CMD_TRIGGER_FIRED_BEFORE && 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, ctgtype); + } + 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); + } + } + + /* + * 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 + * command string, text + * schemaname, text + * objectname, text + * + * we rebuild the DDL command we're about to execute from the parsetree. + * + * The queryString comes from untrusted places: it could be a multiple + * queries string that has been passed through psql -c or otherwise in the + * protocol, or something that comes from an EXECUTE evaluation in plpgsql. + * + * Also we need to be able to spit out a normalized (canonical?) SQL + * command to ease DDL trigger code. + * + */ + static RegProcedure * + list_all_triggers_for_command(const char *command, char type) + { + RegProcedure *procs = list_triggers_for_command(command, type); + RegProcedure *anyp = list_triggers_for_command("ANY", type); + + /* add the ANY trigger at the last position */ + if (anyp != InvalidOid) + { + /* list_triggers_for_command ensure procs has at least one free slot */ + int i; + for (i=0; procs[i] != InvalidOid; i++); + procs[i++] = anyp[0]; + procs[i] = InvalidOid; + } + return procs; + } + + static RegProcedure * + list_triggers_for_command(const char *command, char type) + { + int count = 0, size = 10; + RegProcedure *procs = (RegProcedure *) palloc(size*sizeof(RegProcedure)); + + Relation rel, irel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + /* init the first entry of the procs array */ + procs[0] = InvalidOid; + + rel = heap_open(CmdTriggerRelationId, AccessShareLock); + irel = index_open(CmdTriggerCommandNameIndexId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_cmdtrigger_ctgcommand, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(command)); + + scandesc = systable_beginscan_ordered(rel, irel, SnapshotNow, 1, entry); + + while (HeapTupleIsValid(tuple = systable_getnext_ordered(scandesc, ForwardScanDirection))) + { + Form_pg_cmdtrigger cmd = (Form_pg_cmdtrigger) GETSTRUCT(tuple); + + /* + * Replica support for command triggers is still on the TODO + */ + if (cmd->ctgenabled != 'D' && cmd->ctgtype == type) + { + /* ensure at least a free slot at the end of the array */ + if ((count+1) == size) + { + size += 10; + procs = (Oid *)repalloc(procs, size); + } + procs[count++] = cmd->ctgfoid; + procs[count] = InvalidOid; + } + } + systable_endscan_ordered(scandesc); + + index_close(irel, AccessShareLock); + heap_close(rel, AccessShareLock); + + return procs; + } + + static bool + call_cmdtrigger_procedure(RegProcedure proc, CommandContext cmd, + MemoryContext per_command_context) + { + FmgrInfo flinfo; + FunctionCallInfoData fcinfo; + PgStat_FunctionCallUsage fcusage; + Datum result; + + fmgr_info_cxt(proc, &flinfo, per_command_context); + + /* Can't use OidFunctionCallN because we might get a NULL result */ + InitFunctionCallInfoData(fcinfo, &flinfo, 5, InvalidOid, NULL, NULL); + + /* We support triggers ON ANY COMMAND so all fields here are nullable. */ + if (cmd->tag != NULL) + fcinfo.arg[0] = PointerGetDatum(cstring_to_text(pstrdup(cmd->tag))); + + if (cmd->cmdstr != NULL) + fcinfo.arg[1] = PointerGetDatum(cstring_to_text(pstrdup(cmd->cmdstr))); + + if (cmd->schemaname != NULL) + fcinfo.arg[2] = PointerGetDatum(cstring_to_text(pstrdup(cmd->schemaname))); + + if (cmd->objectname != NULL) + fcinfo.arg[3] = PointerGetDatum(cstring_to_text(pstrdup(cmd->objectname))); + + fcinfo.argnull[0] = cmd->tag == NULL; + fcinfo.argnull[1] = cmd->cmdstr == NULL; + fcinfo.argnull[2] = cmd->schemaname == NULL; + fcinfo.argnull[3] = cmd->objectname == NULL; + + 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; + } + + /* + * For any given command tag, you can have either Before and After triggers, or + * Instead Of triggers, not both. + * + * Instead Of triggers have to run before the command and to cancel its + * execution, hence this API where we return the number of InsteadOf trigger + * procedures we fired. + */ + int + ExecBeforeOrInsteadOfCommandTriggers(Node *parsetree, CommandContext cmd) + { + MemoryContext per_command_context; + int nb = 0; + + per_command_context = + AllocSetContextCreate(CurrentMemoryContext, + "BeforeOrInsteadOfTriggerCommandContext", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + /* + * You can't have both BEFORE and INSTEAD OF triggers registered on the + * same command, so this function is not checking about that and just going + * through an empty list in at least one of those cases. The cost of doing + * it this lazy way is an index scan on pg_catalog.pg_cmdtrigger. + */ + if (!ExecBeforeCommandTriggers(parsetree, cmd, per_command_context)) + nb++; + nb += ExecInsteadOfCommandTriggers(parsetree, cmd, per_command_context); + + /* Release working resources */ + MemoryContextDelete(per_command_context); + return nb; + } + + /* + * 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 bool + ExecBeforeCommandTriggers(Node *parsetree, CommandContext cmd, + MemoryContext per_command_context) + { + MemoryContext oldContext; + RegProcedure *procs = + list_all_triggers_for_command(cmd->tag, CMD_TRIGGER_FIRED_BEFORE); + RegProcedure proc; + int cur= 0; + bool cont = true; + + /* + * Do the functions evaluation in a per-command memory context, so that + * leaked memory will be reclaimed once per command. + * + * Only back parse the command tree if we have at least one trigger + * function to fire. + */ + if (procs[0] != InvalidOid && cmd->cmdstr == NULL) + pg_get_cmddef(cmd, parsetree); + + oldContext = MemoryContextSwitchTo(per_command_context); + MemoryContextReset(per_command_context); + + while (cont && InvalidOid != (proc = procs[cur++])) + { + cont = call_cmdtrigger_procedure(proc, cmd, per_command_context); + + if (cont == false) + elog(WARNING, + "command \"%s %s...\" was cancelled by procedure \"%s\"", + cmd->tag, cmd->objectname, get_func_name(proc)); + } + MemoryContextSwitchTo(oldContext); + return cont; + } + + /* + * An INSTEAD OF command trigger will always cancel execution of the command, + * we only need to know that at least one of them got fired. This function is + * called by ExecBeforeOrInsteadOfCommandTriggers() so is not exposed to other + * modules. + */ + static int + ExecInsteadOfCommandTriggers(Node *parsetree, CommandContext cmd, + MemoryContext per_command_context) + { + MemoryContext oldContext; + RegProcedure *procs = + list_all_triggers_for_command(cmd->tag, CMD_TRIGGER_FIRED_INSTEAD); + RegProcedure proc; + int cur = 0; + + /* + * Do the functions evaluation in a per-command memory context, so that + * leaked memory will be reclaimed once per command. + * + * Only back parse the command tree if we have at least one trigger + * function to fire. + */ + if (procs[0] != InvalidOid && cmd->cmdstr == NULL) + pg_get_cmddef(cmd, parsetree); + + oldContext = MemoryContextSwitchTo(per_command_context); + MemoryContextReset(per_command_context); + + while (InvalidOid != (proc = procs[cur++])) + call_cmdtrigger_procedure(proc, cmd, per_command_context); + + MemoryContextSwitchTo(oldContext); + return cur-1; + } + + /* + * An AFTER trigger will have no impact on the command, which already was + * executed. + */ + void + ExecAfterCommandTriggers(Node *parsetree, CommandContext cmd) + { + MemoryContext oldContext, per_command_context; + RegProcedure *procs = + list_all_triggers_for_command(cmd->tag, CMD_TRIGGER_FIRED_AFTER); + RegProcedure proc; + int cur = 0; + + /* + * 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, + "AfterTriggerCommandContext", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + /* + * Only back parse the command tree if we have at least one trigger + * function to fire. + */ + if (procs[0] != InvalidOid && cmd->cmdstr == NULL) + pg_get_cmddef(cmd, parsetree); + + oldContext = MemoryContextSwitchTo(per_command_context); + + while (InvalidOid != (proc = procs[cur++])) + call_cmdtrigger_procedure(proc, cmd, per_command_context); + + /* Release working resources */ + MemoryContextSwitchTo(oldContext); + MemoryContextDelete(per_command_context); + + return; + } *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 3453,3458 **** _copyCreateTrigStmt(const CreateTrigStmt *from) --- 3453,3510 ---- 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) { *************** *** 3682,3688 **** _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)); \ --- 3734,3740 ---- /* * 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)); \ *************** *** 4305,4310 **** copyObject(const void *from) --- 4357,4374 ---- 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 *************** *** 1774,1779 **** _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b) --- 1774,1823 ---- } 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); *************** *** 2848,2853 **** equal(const void *a, const void *b) --- 2892,2909 ---- 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 *************** *** 1946,1951 **** _outPlannerParamItem(StringInfo str, const PlannerParamItem *node) --- 1946,2082 ---- *****************************************************************************/ 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"); *************** *** 1962,1967 **** _outCreateStmt(StringInfo str, const CreateStmt *node) --- 2093,2123 ---- } 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"); *************** *** 1973,1978 **** _outCreateForeignTableStmt(StringInfo str, const CreateForeignTableStmt *node) --- 2129,2156 ---- } 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"); *************** *** 1996,2001 **** _outIndexStmt(StringInfo str, const IndexStmt *node) --- 2174,2206 ---- } 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"); *************** *** 2005,2010 **** _outNotifyStmt(StringInfo str, const NotifyStmt *node) --- 2210,2304 ---- } 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"); *************** *** 2096,2101 **** _outXmlSerialize(StringInfo str, const XmlSerialize *node) --- 2390,2405 ---- } 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"); *************** *** 2253,2258 **** _outWindowClause(StringInfo str, const WindowClause *node) --- 2557,2573 ---- } 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"); *************** *** 2361,2366 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) --- 2676,2691 ---- } 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"); *************** *** 3037,3060 **** _outNode(StringInfo str, const void *obj) --- 3362,3463 ---- _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; *************** *** 3079,3084 **** _outNode(StringInfo str, const void *obj) --- 3482,3490 ---- case T_WindowClause: _outWindowClause(str, obj); break; + case T_FunctionParameter: + _outFunctionParameter(str, obj); + break; case T_RowMarkClause: _outRowMarkClause(str, obj); break; *************** *** 3094,3099 **** _outNode(StringInfo str, const void *obj) --- 3500,3508 ---- 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 *************** *** 195,200 **** static void processCASbits(int cas_bits, int location, const char *constrType, --- 195,201 ---- } %type stmt schema_stmt + AlterCmdTrigStmt AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt *************** *** 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 --- 209,220 ---- CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt ! CreateAssertStmt CreateTrigStmt CreateCmdTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt ! DropAssertStmt DropTrigStmt DropCmdTrigStmt DropRuleStmt DropCastStmt ! DropRoleStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt *************** *** 268,273 **** static void processCASbits(int cas_bits, int location, const char *constrType, --- 269,276 ---- %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 --- 498,504 ---- 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 --- 678,685 ---- ; stmt : ! AlterCmdTrigStmt ! | AlterDatabaseStmt | AlterDatabaseSetStmt | AlterDefaultPrivilegesStmt | AlterDomainStmt *************** *** 726,731 **** stmt : --- 730,736 ---- | CreateStmt | CreateTableSpaceStmt | CreateTrigStmt + | CreateCmdTrigStmt | CreateRoleStmt | CreateUserStmt | CreateUserMappingStmt *************** *** 749,754 **** stmt : --- 754,760 ---- | DropStmt | DropTableSpaceStmt | DropTrigStmt + | DropCmdTrigStmt | DropRoleStmt | DropUserStmt | DropUserMappingStmt *************** *** 4190,4195 **** DropTrigStmt: --- 4196,4326 ---- /***************************************************************************** * * QUERIES : + * CREATE TRIGGER ... BEFORE|INSTEAD OF|AFTER COMMAND ... + * DROP TRIGGER ... ON COMMAND ... + * + *****************************************************************************/ + + CreateCmdTrigStmt: + CREATE TRIGGER name TriggerActionTime 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 TriggerActionTime 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; + } + ; + + 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 of rewriting + * utility statements in pg_get_cmddef() in src/backend/utils/adt/ruleutils.c + */ + trigger_command: + CREATE TABLE { $$ = "CREATE TABLE"; } + | ALTER TABLE { $$ = "ALTER TABLE"; } + | DROP TABLE { $$ = "DROP TABLE"; } + | CREATE VIEW { $$ = "CREATE VIEW"; } + | DROP VIEW { $$ = "DROP VIEW"; } + | CREATE EXTENSION { $$ = "CREATE EXTENSION"; } + | DROP EXTENSION { $$ = "DROP EXTENSION"; } + ; + + 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 = makeStringConst("ANY", @6); + 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 ... * *************** *** 6592,6597 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name --- 6723,6737 ---- n->newname = $8; $$ = (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" *************** *** 150,155 **** CommandIsReadOnly(Node *parsetree) --- 151,311 ---- } /* + * Support function for calling the command triggers. + */ + static int + call_before_or_insteadof_cmdtriggers(Node *parsetree, CommandContext cmd) + { + switch (nodeTag(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_CreateCmdTrigStmt: + case T_AlterCmdTrigStmt: + 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_DropCmdTrigStmt: + 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: + return ExecBeforeOrInsteadOfCommandTriggers(parsetree, cmd); + + default: + /* commands that don't support triggers */ + return 0; + } + } + + static void + call_after_cmdtriggers(Node *parsetree, CommandContext cmd) + { + switch (nodeTag(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_CreateCmdTrigStmt: + case T_AlterCmdTrigStmt: + 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_DropCmdTrigStmt: + 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(parsetree, 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) --- 340,347 ---- 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) --- 363,369 ---- 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, --- 503,526 ---- DestReceiver *dest, char *completionTag) { + CommandContextData cmd; + check_xact_readonly(parsetree); if (completionTag) completionTag[0] = '\0'; + /* + * we want a completion tag to identify which triggers to run, and that's + * true whatever is given as completionTag here, so just call + * CreateCommandTag() for our own business. + */ + cmd.tag = (char *) CreateCommandTag(parsetree); + cmd.cmdstr = NULL; + + if (call_before_or_insteadof_cmdtriggers(parsetree, &cmd) > 0) + return; + switch (nodeTag(parsetree)) { /* *************** *** 1052,1057 **** standard_ProcessUtility(Node *parsetree, --- 1224,1241 ---- 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; *************** *** 1190,1195 **** standard_ProcessUtility(Node *parsetree, --- 1374,1380 ---- (int) nodeTag(parsetree)); break; } + call_after_cmdtriggers(parsetree, &cmd); } /* *************** *** 1943,1948 **** CreateCommandTag(Node *parsetree) --- 2128,2145 ---- 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; *************** *** 2144,2149 **** CreateCommandTag(Node *parsetree) --- 2341,2353 ---- 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; } *************** *** 2438,2443 **** GetCommandLogLevel(Node *parsetree) --- 2642,2663 ---- 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 ---- *************** *** 7326,7328 **** flatten_reloptions(Oid relid) --- 7327,7972 ---- return result; } + + /* + * Functions that ouputs a COMMAND given a Utility parsetree + * + * And some utilities. + */ + + /* + * Given a RangeVar, return the namespace name to use as schemaname. When + * r->schemaname is NULL, returns the first schema name of the current + * search_path. + */ + static char * + RangeVarGetNamespace(RangeVar *r) + { + char *schemaname; + List *search_path = fetch_search_path(false); + + if (search_path == NIL) /* probably can't happen */ + schemaname = NULL; + else + schemaname = get_namespace_name(linitial_oid(search_path)); + + list_free(search_path); + + return schemaname; + } + + static char * + RangeVarToString(RangeVar *r) + { + char *schemaname = RangeVarGetNamespace(r); + StringInfoData string; + initStringInfo(&string); + + if (r->catalogname != NULL) + { + appendStringInfoString(&string, quote_identifier(r->catalogname)); + appendStringInfoChar(&string, '.'); + } + if (schemaname != NULL) + { + appendStringInfoString(&string, quote_identifier(schemaname)); + appendStringInfoChar(&string, '.'); + } + appendStringInfoString(&string, quote_identifier(r->relname)); + + return string.data; + } + + static const char * + relkindToString(ObjectType relkind) + { + const char *kind; + + switch (relkind) + { + case OBJECT_FOREIGN_TABLE: + kind = "FOREIGN TABLE"; + break; + + case OBJECT_INDEX: + kind = "INDEX"; + break; + + case OBJECT_SEQUENCE: + kind = "SEQUENCE"; + break; + + case OBJECT_TABLE: + kind = "TABLE"; + break; + + case OBJECT_VIEW: + kind = "VIEW"; + break; + + default: + elog(ERROR, "unrecognized relkind: %d", relkind); + return NULL; /* make compiler happy */ + } + return kind; + } + + static void + _maybeAddSeparator(StringInfo buf, const char *sep, bool *first) + { + if (*first) *first = false; + else appendStringInfoString(buf, sep); + } + + /* + * The DROP statement is "generic" as in supporting multiple object types. The + * specialized part is only finding the names of the objects dropped. + * + * Also the easiest way to get the command prefix is to use the command tag. + */ + static void + _rwDropStmt(CommandContext cmd, DropStmt *node) + { + StringInfoData buf; + ListCell *obj; + bool first = true; + + initStringInfo(&buf); + appendStringInfo(&buf, "%s ", cmd->tag); + + foreach(obj, node->objects) + { + switch (node->removeType) + { + case OBJECT_TABLE: + case OBJECT_SEQUENCE: + case OBJECT_VIEW: + case OBJECT_INDEX: + case OBJECT_FOREIGN_TABLE: + { + RangeVar *rel = makeRangeVarFromNameList((List *) lfirst(obj)); + _maybeAddSeparator(&buf, ", ", &first); + appendStringInfoString(&buf, RangeVarToString(rel)); + + cmd->schemaname = RangeVarGetNamespace(rel); + cmd->objectname = rel->relname; + break; + } + + case OBJECT_TYPE: + case OBJECT_DOMAIN: + { + TypeName *typename = makeTypeNameFromNameList((List *) obj); + _maybeAddSeparator(&buf, ", ", &first); + appendStringInfoString(&buf, TypeNameToString(typename)); + + if (list_nth((List *) obj, 1) == NIL) + { + cmd->schemaname = NULL; + cmd->objectname = strVal(linitial((List *) obj)); + } + else + { + cmd->schemaname = strVal(list_nth((List *) obj, 0)); + cmd->objectname = strVal(list_nth((List *) obj, 1)); + } + break; + } + + /* case OBJECT_COLLATION: */ + /* case OBJECT_CONVERSION: */ + /* case OBJECT_SCHEMA: */ + /* case OBJECT_EXTENSION: */ + default: + { + char *name = strVal(linitial((List *) obj)); + _maybeAddSeparator(&buf, ", ", &first); + appendStringInfoString(&buf, name); + + cmd->schemaname = NULL; + cmd->objectname = name; + break; + } + } + } + appendStringInfo(&buf, "%s %s;", + node->missing_ok ? " IF EXISTS":"", + node->behavior == DROP_CASCADE ? "CASCADE" : "RESTRICT"); + + cmd->cmdstr = buf.data; + } + + static void + _rwCreateExtensionStmt(CommandContext cmd, CreateExtensionStmt *node) + { + StringInfoData buf; + ListCell *lc; + + initStringInfo(&buf); + appendStringInfo(&buf, "CREATE EXTENSION%s %s", + node->if_not_exists ? " IF NOT EXISTS" : "", + node->extname); + + foreach(lc, node->options) + { + DefElem *defel = (DefElem *) lfirst(lc); + + if (strcmp(defel->defname, "schema") == 0) + appendStringInfo(&buf, " SCHEMA %s", strVal(defel->arg)); + + else if (strcmp(defel->defname, "new_version") == 0) + appendStringInfo(&buf, " VERSION %s", strVal(defel->arg)); + + else if (strcmp(defel->defname, "old_version") == 0) + appendStringInfo(&buf, " FROM %s", strVal(defel->arg)); + } + appendStringInfoChar(&buf, ';'); + + cmd->cmdstr = buf.data; + cmd->schemaname = NULL; + cmd->objectname = node->extname; + } + + static void + _rwViewStmt(CommandContext cmd, ViewStmt *node) + { + StringInfoData buf; + Query *viewParse; + + initStringInfo(&buf); + viewParse = parse_analyze((Node *) copyObject(node->query), + "(unavailable source text)", NULL, 0); + + appendStringInfo(&buf, "CREATE %sVIEW %s AS ", + node->replace? "OR REPLACE": "", + RangeVarToString(node->view)); + + get_query_def(viewParse, &buf, NIL, NULL, 0, 1); + appendStringInfoChar(&buf, ';'); + + cmd->cmdstr = buf.data; + cmd->schemaname = RangeVarGetNamespace(node->view); + cmd->objectname = node->view->relname; + } + + static void + _rwColQualList(StringInfo buf, List *constraints, const char *relname) + { + ListCell *lc; + + foreach(lc, constraints) + { + Constraint *c = (Constraint *) lfirst(lc); + Assert(IsA(c, Constraint)); + + if (c->conname != NULL) + appendStringInfo(buf, " CONSTRAINT %s", c->conname); + + switch (c->contype) + { + case CONSTR_NOTNULL: + appendStringInfo(buf, " NOT NULL"); + break; + + case CONSTR_NULL: + appendStringInfo(buf, " NULL"); + break; + + case CONSTR_UNIQUE: + appendStringInfo(buf, " UNIQUE"); + if (c->indexspace != NULL) + appendStringInfo(buf, " USING INDEX TABLESPACE %s", c->indexspace); + break; + + case CONSTR_PRIMARY: + appendStringInfo(buf, " PRIMARY KEY"); + if (c->keys != NULL) + { + ListCell *k; + bool first = true; + + appendStringInfoChar(buf, '('); + foreach(k, c->keys) + { + _maybeAddSeparator(buf, ",", &first); + appendStringInfo(buf, "%s", strVal(lfirst(k))); + } + appendStringInfoChar(buf, ')'); + } + if (c->indexspace != NULL) + appendStringInfo(buf, " USING INDEX TABLESPACE %s", c->indexspace); + break; + + case CONSTR_CHECK: + { + char *consrc; + List *context; + + context = deparse_context_for(relname, InvalidOid); + consrc = deparse_expression(c->raw_expr, context, false, false); + appendStringInfo(buf, " CHECK (%s)", consrc); + break; + } + + case CONSTR_DEFAULT: + { + char *consrc; + List *context; + + context = deparse_context_for(relname, InvalidOid); + consrc = deparse_expression(c->raw_expr, context, false, false); + appendStringInfo(buf, " DEFAUT %s", consrc); + break; + } + + case CONSTR_EXCLUSION: + appendStringInfo(buf, " EXCLUDE %s () ", c->access_method); + if (c->indexspace != NULL) + appendStringInfo(buf, " USING INDEX TABLESPACE %s", c->indexspace); + break; + + case CONSTR_FOREIGN: + appendStringInfo(buf, " REFERENCES %s()", RangeVarToString(c->pktable)); + break; + + case CONSTR_ATTR_DEFERRABLE: + appendStringInfo(buf, " DEFERRABLE"); + break; + + case CONSTR_ATTR_NOT_DEFERRABLE: + appendStringInfo(buf, " NOT DEFERRABLE"); + break; + + case CONSTR_ATTR_DEFERRED: + appendStringInfo(buf, " INITIALLY DEFERRED"); + break; + + case CONSTR_ATTR_IMMEDIATE: + appendStringInfo(buf, " INITIALLY IMMEDIATE"); + break; + } + } + } + + static void + _rwCreateStmt(CommandContext cmd, CreateStmt *node) + { + ListCell *lcmd; + StringInfoData buf; + + initStringInfo(&buf); + appendStringInfo(&buf, "CREATE TABLE %s %s", + RangeVarToString(node->relation), + node->if_not_exists ? " IF NOT EXISTS" : ""); + + appendStringInfoChar(&buf, '('); + + foreach(lcmd, node->tableElts) + { + Node *elmt = (Node *) lfirst(lcmd); + + switch (nodeTag(elmt)) + { + case T_ColumnDef: + { + ColumnDef *c = (ColumnDef *) elmt; + appendStringInfo(&buf, "%s %s", + c->colname, + TypeNameToString(c->typeName)); + _rwColQualList(&buf, c->constraints, node->relation->relname); + break; + } + case T_TableLikeClause: + { + TableLikeClause *r = (TableLikeClause *) elmt; + appendStringInfo(&buf, "%s", RangeVarToString(r->relation)); + break; + } + case T_Constraint: + { + Constraint *c = (Constraint *) elmt; + _rwColQualList(&buf, list_make1(c), node->relation->relname); + break; + } + default: + /* Many nodeTags are not interesting as an + * OptTableElementList + */ + break; + } + if (lnext(lcmd) != NULL) + appendStringInfoChar(&buf, ','); + } + appendStringInfoChar(&buf, ')'); + appendStringInfoChar(&buf, ';'); + + cmd->cmdstr = buf.data; + cmd->schemaname = RangeVarGetNamespace(node->relation); + cmd->objectname = node->relation->relname; + } + + static void + _rwAlterTableStmt(CommandContext cmd, AlterTableStmt *node) + { + StringInfoData buf; + ListCell *lcmd; + bool first = true; + + initStringInfo(&buf); + appendStringInfo(&buf, "ALTER %s %s", + relkindToString(node->relkind), + RangeVarToString(node->relation)); + + foreach(lcmd, node->cmds) + { + AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); + ColumnDef *def = (ColumnDef *) cmd->def; + + _maybeAddSeparator(&buf, ", ", &first); + + switch (cmd->subtype) + { + case AT_AddColumn: /* add column */ + appendStringInfo(&buf, " ADD COLUMN %s %s", + def->colname, + TypeNameToString(def->typeName)); + + if (def->is_not_null) + appendStringInfoString(&buf, " NOT NULL"); + break; + + case AT_ColumnDefault: /* alter column default */ + if (def == NULL) + appendStringInfo(&buf, " ALTER %s DROP DEFAULT", + cmd->name); + else + { + char *str = + deparse_expression_pretty(cmd->def, NIL, false, false, 0, 0); + + appendStringInfo(&buf, " ALTER %s SET DEFAULT %s", + cmd->name, str); + } + break; + + case AT_DropNotNull: /* alter column drop not null */ + appendStringInfo(&buf, " ALTER %s DROP NOT NULL", cmd->name); + break; + + case AT_SetNotNull: /* alter column set not null */ + appendStringInfo(&buf, " ALTER %s SET NOT NULL", cmd->name); + break; + + case AT_SetStatistics: /* alter column set statistics */ + appendStringInfo(&buf, " ALTER %s SET STATISTICS %ld", + cmd->name, + (long) intVal((Value *)(cmd->def))); + break; + + case AT_SetOptions: /* alter column set ( options ) */ + break; + + case AT_ResetOptions: /* alter column reset ( options ) */ + break; + + case AT_SetStorage: /* alter column set storage */ + appendStringInfo(&buf, " ALTER %s SET STORAGE %s", + cmd->name, + strVal((Value *)(cmd->def))); + break; + + case AT_DropColumn: /* drop column */ + appendStringInfo(&buf, " %s %s%s", + cmd->missing_ok? "DROP IF EXISTS": "DROP", + cmd->name, + cmd->behavior == DROP_CASCADE? " CASCADE": ""); + break; + + case AT_AddIndex: /* add index */ + break; + + case AT_AddConstraint: /* add constraint */ + break; + + case AT_ValidateConstraint: /* validate constraint */ + appendStringInfo(&buf, " VALIDATE CONSTRAINT %s", cmd->name); + break; + + case AT_AddIndexConstraint: /* add constraint using existing index */ + break; + + case AT_DropConstraint: /* drop constraint */ + appendStringInfo(&buf, " DROP CONSTRAINT%s %s %s", + cmd->missing_ok? " IF EXISTS": "", + cmd->name, + cmd->behavior == DROP_CASCADE? " CASCADE": ""); + break; + + case AT_AlterColumnType: /* alter column type */ + appendStringInfo(&buf, " ALTER %s TYPE %s", + cmd->name, + TypeNameToString(def->typeName)); + if (def->raw_default != NULL) + { + char *str = + deparse_expression_pretty(def->raw_default, + NIL, false, false, 0, 0); + appendStringInfo(&buf, " USING %s", str); + } + break; + + case AT_AlterColumnGenericOptions: /* alter column OPTIONS (...) */ + break; + + case AT_ChangeOwner: /* change owner */ + appendStringInfo(&buf, " OWNER TO %s", cmd->name); + break; + + case AT_ClusterOn: /* CLUSTER ON */ + appendStringInfo(&buf, " CLUSTER ON %s", cmd->name); + break; + + case AT_DropCluster: /* SET WITHOUT CLUSTER */ + appendStringInfo(&buf, " SET WITHOUT CLUSTER"); + break; + + case AT_SetTableSpace: /* SET TABLESPACE */ + appendStringInfo(&buf, " SET TABLESPACE %s", cmd->name); + break; + + case AT_SetRelOptions: /* SET (...) -- AM specific parameters */ + break; + + case AT_ResetRelOptions: /* RESET (...) -- AM specific parameters */ + break; + + case AT_EnableTrig: /* ENABLE TRIGGER name */ + appendStringInfo(&buf, " ENABLE TRIGGER %s", cmd->name); + break; + + case AT_EnableAlwaysTrig: /* ENABLE ALWAYS TRIGGER name */ + appendStringInfo(&buf, " ENABLE ALWAYS TRIGGER %s", cmd->name); + break; + + case AT_EnableReplicaTrig: /* ENABLE REPLICA TRIGGER name */ + appendStringInfo(&buf, " ENABLE REPLICA TRIGGER %s", cmd->name); + break; + + case AT_DisableTrig: /* DISABLE TRIGGER name */ + appendStringInfo(&buf, " DISABLE TRIGGER %s", cmd->name); + break; + + case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */ + appendStringInfo(&buf, " ENABLE TRIGGER ALL"); + break; + + case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */ + appendStringInfo(&buf, " DISABLE TRIGGER ALL"); + break; + + case AT_EnableTrigUser: /* ENABLE TRIGGER USER */ + appendStringInfo(&buf, " ENABLE TRIGGER USER"); + break; + + case AT_DisableTrigUser: /* DISABLE TRIGGER USER */ + appendStringInfo(&buf, " DISABLE TRIGGER USER"); + break; + + case AT_EnableRule: /* ENABLE RULE name */ + appendStringInfo(&buf, " ENABLE RULE %s", cmd->name); + break; + + case AT_EnableAlwaysRule: /* ENABLE ALWAYS RULE name */ + appendStringInfo(&buf, " ENABLE ALWAYS RULE %s", cmd->name); + break; + + case AT_EnableReplicaRule: /* ENABLE REPLICA RULE name */ + appendStringInfo(&buf, " ENABLE REPLICA RULE %s", cmd->name); + break; + + case AT_DisableRule: /* DISABLE RULE name */ + appendStringInfo(&buf, " DISABLE RULE %s", cmd->name); + break; + + case AT_AddInherit: /* INHERIT parent */ + appendStringInfo(&buf, " INHERIT %s", + RangeVarToString((RangeVar *) cmd->def)); + break; + + case AT_DropInherit: /* NO INHERIT parent */ + appendStringInfo(&buf, " NO INHERIT %s", + RangeVarToString((RangeVar *) cmd->def)); + break; + + case AT_AddOf: /* OF */ + appendStringInfo(&buf, " OF %s", TypeNameToString(def->typeName)); + break; + + case AT_DropOf: /* NOT OF */ + appendStringInfo(&buf, " NOT OF"); + break; + + case AT_GenericOptions: /* OPTIONS (...) */ + break; + + default: + break; + } + } + appendStringInfoChar(&buf, ';'); + + cmd->cmdstr = buf.data; + cmd->schemaname = RangeVarGetNamespace(node->relation); + cmd->objectname = node->relation->relname; + } + + /* + * get_cmddef + * + * internal use only, used for DDL Triggers + * + * Utility statements are not planned thus won't get into a Query *, we get to + * work from the parsetree directly, that would be query->utilityStmt which is + * of type Node *. We declare that a void * to avoid incompatible pointer type + * warnings. + * + */ + void + pg_get_cmddef(CommandContext cmd, void *parsetree) + { + /* + * we need the big'o'switch here, and calling a specialized function per + * utility statement nodetag. Also, we could have a trigger on ANY command + * firing, in that case we need to avoid trying to fill the CommandContext + * for command we don't know how to back parse. + */ + cmd->objectname = NULL; + cmd->schemaname = NULL; + + switch (nodeTag(parsetree)) + { + case T_DropStmt: + _rwDropStmt(cmd, parsetree); + break; + + case T_CreateStmt: + _rwCreateStmt(cmd, parsetree); + break; + + case T_AlterTableStmt: + _rwAlterTableStmt(cmd, parsetree); + break; + + case T_ViewStmt: + _rwViewStmt(cmd, parsetree); + break; + + case T_CreateExtensionStmt: + _rwCreateExtensionStmt(cmd, parsetree); + break; + + default: + elog(DEBUG1, "unrecognized node type: %d", + (int) nodeTag(parsetree)); + } + } *** /dev/null --- b/src/bin/pg_dump/dumpcatalog.c *************** *** 0 **** --- 1,983 ---- + /*------------------------------------------------------------------------- + * + * common.c + * catalog routines used by pg_dump + * + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/bin/pg_dump/dumpcatalog.c + * + *------------------------------------------------------------------------- + */ + #include "postgres_fe.h" + + #include + + #include "catalog/pg_class.h" + + #include "pg_backup_archiver.h" + #include "common.h" + + + /* + * Variables for mapping DumpId to DumpableObject + */ + static DumpableObject **dumpIdMap = NULL; + static int allocedDumpIds = 0; + static DumpId lastDumpId = 0; + + /* + * Variables for mapping CatalogId to DumpableObject + */ + static bool catalogIdMapValid = false; + static DumpableObject **catalogIdMap = NULL; + static int numCatalogIds = 0; + + /* + * These variables are static to avoid the notational cruft of having to pass + * them into findTableByOid() and friends. For each of these arrays, we + * build a sorted-by-OID index array immediately after it's built, and then + * we use binary search in findTableByOid() and friends. (qsort'ing the base + * arrays themselves would be simpler, but it doesn't work because pg_dump.c + * may have already established pointers between items.) + */ + static TableInfo *tblinfo; + static TypeInfo *typinfo; + static FuncInfo *funinfo; + static OprInfo *oprinfo; + static int numTables; + static int numTypes; + static int numFuncs; + static int numOperators; + static int numCollations; + static DumpableObject **tblinfoindex; + static DumpableObject **typinfoindex; + static DumpableObject **funinfoindex; + static DumpableObject **oprinfoindex; + static DumpableObject **collinfoindex; + + + static void flagInhTables(TableInfo *tbinfo, int numTables, + InhInfo *inhinfo, int numInherits); + static void flagInhAttrs(TableInfo *tblinfo, int numTables); + static DumpableObject **buildIndexArray(void *objArray, int numObjs, + Size objSize); + static int DOCatalogIdCompare(const void *p1, const void *p2); + static void findParentsByOid(TableInfo *self, + InhInfo *inhinfo, int numInherits); + static int strInArray(const char *pattern, char **arr, int arr_size); + + + /* + * getSchemaData + * Collect information about all potentially dumpable objects + */ + TableInfo * + getSchemaData(int *numTablesPtr) + { + ExtensionInfo *extinfo; + InhInfo *inhinfo; + CollInfo *collinfo; + int numNamespaces; + int numExtensions; + int numAggregates; + int numInherits; + int numRules; + int numProcLangs; + int numCasts; + int numOpclasses; + int numOpfamilies; + int numConversions; + int numTSParsers; + int numTSTemplates; + int numTSDicts; + int numTSConfigs; + int numForeignDataWrappers; + int numForeignServers; + int numDefaultACLs; + int numCmdTriggers; + + if (g_verbose) + write_msg(NULL, "reading schemas\n"); + getNamespaces(&numNamespaces); + + /* + * getTables should be done as soon as possible, so as to minimize the + * window between starting our transaction and acquiring per-table locks. + * However, we have to do getNamespaces first because the tables get + * linked to their containing namespaces during getTables. + */ + if (g_verbose) + write_msg(NULL, "reading user-defined tables\n"); + tblinfo = getTables(&numTables); + tblinfoindex = buildIndexArray(tblinfo, numTables, sizeof(TableInfo)); + + if (g_verbose) + write_msg(NULL, "reading extensions\n"); + extinfo = getExtensions(&numExtensions); + + if (g_verbose) + write_msg(NULL, "reading user-defined functions\n"); + funinfo = getFuncs(&numFuncs); + funinfoindex = buildIndexArray(funinfo, numFuncs, sizeof(FuncInfo)); + + /* this must be after getTables and getFuncs */ + if (g_verbose) + write_msg(NULL, "reading user-defined types\n"); + typinfo = getTypes(&numTypes); + typinfoindex = buildIndexArray(typinfo, numTypes, sizeof(TypeInfo)); + + /* this must be after getFuncs, too */ + if (g_verbose) + write_msg(NULL, "reading procedural languages\n"); + getProcLangs(&numProcLangs); + + if (g_verbose) + write_msg(NULL, "reading user-defined aggregate functions\n"); + getAggregates(&numAggregates); + + if (g_verbose) + write_msg(NULL, "reading user-defined operators\n"); + oprinfo = getOperators(&numOperators); + oprinfoindex = buildIndexArray(oprinfo, numOperators, sizeof(OprInfo)); + + if (g_verbose) + write_msg(NULL, "reading user-defined operator classes\n"); + getOpclasses(&numOpclasses); + + if (g_verbose) + write_msg(NULL, "reading user-defined operator families\n"); + getOpfamilies(&numOpfamilies); + + if (g_verbose) + write_msg(NULL, "reading user-defined text search parsers\n"); + getTSParsers(&numTSParsers); + + if (g_verbose) + write_msg(NULL, "reading user-defined text search templates\n"); + getTSTemplates(&numTSTemplates); + + if (g_verbose) + write_msg(NULL, "reading user-defined text search dictionaries\n"); + getTSDictionaries(&numTSDicts); + + if (g_verbose) + write_msg(NULL, "reading user-defined text search configurations\n"); + getTSConfigurations(&numTSConfigs); + + if (g_verbose) + write_msg(NULL, "reading user-defined foreign-data wrappers\n"); + getForeignDataWrappers(&numForeignDataWrappers); + + if (g_verbose) + write_msg(NULL, "reading user-defined foreign servers\n"); + getForeignServers(&numForeignServers); + + if (g_verbose) + write_msg(NULL, "reading default privileges\n"); + getDefaultACLs(&numDefaultACLs); + + if (g_verbose) + write_msg(NULL, "reading user-defined collations\n"); + collinfo = getCollations(&numCollations); + collinfoindex = buildIndexArray(collinfo, numCollations, sizeof(CollInfo)); + + if (g_verbose) + write_msg(NULL, "reading user-defined conversions\n"); + getConversions(&numConversions); + + if (g_verbose) + write_msg(NULL, "reading type casts\n"); + getCasts(&numCasts); + + if (g_verbose) + write_msg(NULL, "reading table inheritance information\n"); + inhinfo = getInherits(&numInherits); + + if (g_verbose) + write_msg(NULL, "reading rewrite rules\n"); + getRules(&numRules); + + /* + * Identify extension member objects and mark them as not to be dumped. + * This must happen after reading all objects that can be direct members + * of extensions, but before we begin to process table subsidiary objects. + */ + if (g_verbose) + write_msg(NULL, "finding extension members\n"); + getExtensionMembership(extinfo, numExtensions); + + /* Link tables to parents, mark parents of target tables interesting */ + if (g_verbose) + write_msg(NULL, "finding inheritance relationships\n"); + flagInhTables(tblinfo, numTables, inhinfo, numInherits); + + if (g_verbose) + write_msg(NULL, "reading column info for interesting tables\n"); + getTableAttrs(tblinfo, numTables); + + if (g_verbose) + write_msg(NULL, "flagging inherited columns in subtables\n"); + flagInhAttrs(tblinfo, numTables); + + if (g_verbose) + write_msg(NULL, "reading indexes\n"); + getIndexes(tblinfo, numTables); + + if (g_verbose) + write_msg(NULL, "reading constraints\n"); + getConstraints(tblinfo, numTables); + + if (g_verbose) + write_msg(NULL, "reading triggers\n"); + getTriggers(tblinfo, numTables); + + if (g_verbose) + write_msg(NULL, "reading command triggers\n"); + getCmdTriggers(&numCmdTriggers); + + *numTablesPtr = numTables; + return tblinfo; + } + + /* flagInhTables - + * Fill in parent link fields of every target table, and mark + * parents of target tables as interesting + * + * Note that only direct ancestors of targets are marked interesting. + * This is sufficient; we don't much care whether they inherited their + * attributes or not. + * + * modifies tblinfo + */ + static void + flagInhTables(TableInfo *tblinfo, int numTables, + InhInfo *inhinfo, int numInherits) + { + int i, + j; + int numParents; + TableInfo **parents; + + for (i = 0; i < numTables; i++) + { + /* Sequences and views never have parents */ + if (tblinfo[i].relkind == RELKIND_SEQUENCE || + tblinfo[i].relkind == RELKIND_VIEW) + continue; + + /* Don't bother computing anything for non-target tables, either */ + if (!tblinfo[i].dobj.dump) + continue; + + /* Find all the immediate parent tables */ + findParentsByOid(&tblinfo[i], inhinfo, numInherits); + + /* Mark the parents as interesting for getTableAttrs */ + numParents = tblinfo[i].numParents; + parents = tblinfo[i].parents; + for (j = 0; j < numParents; j++) + parents[j]->interesting = true; + } + } + + /* flagInhAttrs - + * for each dumpable table in tblinfo, flag its inherited attributes + * so when we dump the table out, we don't dump out the inherited attributes + * + * modifies tblinfo + */ + static void + flagInhAttrs(TableInfo *tblinfo, int numTables) + { + int i, + j, + k; + + for (i = 0; i < numTables; i++) + { + TableInfo *tbinfo = &(tblinfo[i]); + int numParents; + TableInfo **parents; + TableInfo *parent; + + /* Sequences and views never have parents */ + if (tbinfo->relkind == RELKIND_SEQUENCE || + tbinfo->relkind == RELKIND_VIEW) + continue; + + /* Don't bother computing anything for non-target tables, either */ + if (!tbinfo->dobj.dump) + continue; + + numParents = tbinfo->numParents; + parents = tbinfo->parents; + + if (numParents == 0) + continue; /* nothing to see here, move along */ + + /*---------------------------------------------------------------- + * For each attr, check the parent info: if no parent has an attr + * with the same name, then it's not inherited. If there *is* an + * attr with the same name, then only dump it if: + * + * - it is NOT NULL and zero parents are NOT NULL + * OR + * - it has a default value AND the default value does not match + * all parent default values, or no parents specify a default. + * + * See discussion on -hackers around 2-Apr-2001. + *---------------------------------------------------------------- + */ + for (j = 0; j < tbinfo->numatts; j++) + { + bool foundAttr; /* Attr was found in a parent */ + bool foundNotNull; /* Attr was NOT NULL in a parent */ + bool defaultsMatch; /* All non-empty defaults match */ + bool defaultsFound; /* Found a default in a parent */ + AttrDefInfo *attrDef; + + foundAttr = false; + foundNotNull = false; + defaultsMatch = true; + defaultsFound = false; + + attrDef = tbinfo->attrdefs[j]; + + for (k = 0; k < numParents; k++) + { + int inhAttrInd; + + parent = parents[k]; + inhAttrInd = strInArray(tbinfo->attnames[j], + parent->attnames, + parent->numatts); + + if (inhAttrInd != -1) + { + AttrDefInfo *inhDef = parent->attrdefs[inhAttrInd]; + + foundAttr = true; + foundNotNull |= parent->notnull[inhAttrInd]; + if (inhDef != NULL) + { + defaultsFound = true; + + /* + * If any parent has a default and the child doesn't, + * we have to emit an explicit DEFAULT NULL clause for + * the child, else the parent's default will win. + */ + if (attrDef == NULL) + { + attrDef = (AttrDefInfo *) pg_malloc(sizeof(AttrDefInfo)); + attrDef->dobj.objType = DO_ATTRDEF; + attrDef->dobj.catId.tableoid = 0; + attrDef->dobj.catId.oid = 0; + AssignDumpId(&attrDef->dobj); + attrDef->adtable = tbinfo; + attrDef->adnum = j + 1; + attrDef->adef_expr = pg_strdup("NULL"); + + attrDef->dobj.name = pg_strdup(tbinfo->dobj.name); + attrDef->dobj.namespace = tbinfo->dobj.namespace; + + attrDef->dobj.dump = tbinfo->dobj.dump; + + attrDef->separate = false; + addObjectDependency(&tbinfo->dobj, + attrDef->dobj.dumpId); + + tbinfo->attrdefs[j] = attrDef; + } + if (strcmp(attrDef->adef_expr, inhDef->adef_expr) != 0) + { + defaultsMatch = false; + + /* + * Whenever there is a non-matching parent + * default, add a dependency to force the parent + * default to be dumped first, in case the + * defaults end up being dumped as separate + * commands. Otherwise the parent default will + * override the child's when it is applied. + */ + addObjectDependency(&attrDef->dobj, + inhDef->dobj.dumpId); + } + } + } + } + + /* + * Based on the scan of the parents, decide if we can rely on the + * inherited attr + */ + if (foundAttr) /* Attr was inherited */ + { + /* Set inherited flag by default */ + tbinfo->inhAttrs[j] = true; + tbinfo->inhAttrDef[j] = true; + tbinfo->inhNotNull[j] = true; + + /* + * Clear it if attr had a default, but parents did not, or + * mismatch + */ + if ((attrDef != NULL) && (!defaultsFound || !defaultsMatch)) + { + tbinfo->inhAttrs[j] = false; + tbinfo->inhAttrDef[j] = false; + } + + /* + * Clear it if NOT NULL and none of the parents were NOT NULL + */ + if (tbinfo->notnull[j] && !foundNotNull) + { + tbinfo->inhAttrs[j] = false; + tbinfo->inhNotNull[j] = false; + } + + /* Clear it if attr has local definition */ + if (tbinfo->attislocal[j]) + tbinfo->inhAttrs[j] = false; + } + } + } + } + + /* + * AssignDumpId + * Given a newly-created dumpable object, assign a dump ID, + * and enter the object into the lookup table. + * + * The caller is expected to have filled in objType and catId, + * but not any of the other standard fields of a DumpableObject. + */ + void + AssignDumpId(DumpableObject *dobj) + { + dobj->dumpId = ++lastDumpId; + dobj->name = NULL; /* must be set later */ + dobj->namespace = NULL; /* may be set later */ + dobj->dump = true; /* default assumption */ + dobj->ext_member = false; /* default assumption */ + dobj->dependencies = NULL; + dobj->nDeps = 0; + dobj->allocDeps = 0; + + while (dobj->dumpId >= allocedDumpIds) + { + int newAlloc; + + if (allocedDumpIds <= 0) + { + newAlloc = 256; + dumpIdMap = (DumpableObject **) + pg_malloc(newAlloc * sizeof(DumpableObject *)); + } + else + { + newAlloc = allocedDumpIds * 2; + dumpIdMap = (DumpableObject **) + pg_realloc(dumpIdMap, newAlloc * sizeof(DumpableObject *)); + } + memset(dumpIdMap + allocedDumpIds, 0, + (newAlloc - allocedDumpIds) * sizeof(DumpableObject *)); + allocedDumpIds = newAlloc; + } + dumpIdMap[dobj->dumpId] = dobj; + + /* mark catalogIdMap invalid, but don't rebuild it yet */ + catalogIdMapValid = false; + } + + /* + * Assign a DumpId that's not tied to a DumpableObject. + * + * This is used when creating a "fixed" ArchiveEntry that doesn't need to + * participate in the sorting logic. + */ + DumpId + createDumpId(void) + { + return ++lastDumpId; + } + + /* + * Return the largest DumpId so far assigned + */ + DumpId + getMaxDumpId(void) + { + return lastDumpId; + } + + /* + * Find a DumpableObject by dump ID + * + * Returns NULL for invalid ID + */ + DumpableObject * + findObjectByDumpId(DumpId dumpId) + { + if (dumpId <= 0 || dumpId >= allocedDumpIds) + return NULL; /* out of range? */ + return dumpIdMap[dumpId]; + } + + /* + * Find a DumpableObject by catalog ID + * + * Returns NULL for unknown ID + * + * We use binary search in a sorted list that is built on first call. + * If AssignDumpId() and findObjectByCatalogId() calls were freely intermixed, + * the code would work, but possibly be very slow. In the current usage + * pattern that does not happen, indeed we build the list at most twice. + */ + DumpableObject * + findObjectByCatalogId(CatalogId catalogId) + { + DumpableObject **low; + DumpableObject **high; + + if (!catalogIdMapValid) + { + if (catalogIdMap) + free(catalogIdMap); + getDumpableObjects(&catalogIdMap, &numCatalogIds); + if (numCatalogIds > 1) + qsort((void *) catalogIdMap, numCatalogIds, + sizeof(DumpableObject *), DOCatalogIdCompare); + catalogIdMapValid = true; + } + + /* + * We could use bsearch() here, but the notational cruft of calling + * bsearch is nearly as bad as doing it ourselves; and the generalized + * bsearch function is noticeably slower as well. + */ + if (numCatalogIds <= 0) + return NULL; + low = catalogIdMap; + high = catalogIdMap + (numCatalogIds - 1); + while (low <= high) + { + DumpableObject **middle; + int difference; + + middle = low + (high - low) / 2; + /* comparison must match DOCatalogIdCompare, below */ + difference = oidcmp((*middle)->catId.oid, catalogId.oid); + if (difference == 0) + difference = oidcmp((*middle)->catId.tableoid, catalogId.tableoid); + if (difference == 0) + return *middle; + else if (difference < 0) + low = middle + 1; + else + high = middle - 1; + } + return NULL; + } + + /* + * Find a DumpableObject by OID, in a pre-sorted array of one type of object + * + * Returns NULL for unknown OID + */ + static DumpableObject * + findObjectByOid(Oid oid, DumpableObject **indexArray, int numObjs) + { + DumpableObject **low; + DumpableObject **high; + + /* + * This is the same as findObjectByCatalogId except we assume we need not + * look at table OID because the objects are all the same type. + * + * We could use bsearch() here, but the notational cruft of calling + * bsearch is nearly as bad as doing it ourselves; and the generalized + * bsearch function is noticeably slower as well. + */ + if (numObjs <= 0) + return NULL; + low = indexArray; + high = indexArray + (numObjs - 1); + while (low <= high) + { + DumpableObject **middle; + int difference; + + middle = low + (high - low) / 2; + difference = oidcmp((*middle)->catId.oid, oid); + if (difference == 0) + return *middle; + else if (difference < 0) + low = middle + 1; + else + high = middle - 1; + } + return NULL; + } + + /* + * Build an index array of DumpableObject pointers, sorted by OID + */ + static DumpableObject ** + buildIndexArray(void *objArray, int numObjs, Size objSize) + { + DumpableObject **ptrs; + int i; + + ptrs = (DumpableObject **) pg_malloc(numObjs * sizeof(DumpableObject *)); + for (i = 0; i < numObjs; i++) + ptrs[i] = (DumpableObject *) ((char *) objArray + i * objSize); + + /* We can use DOCatalogIdCompare to sort since its first key is OID */ + if (numObjs > 1) + qsort((void *) ptrs, numObjs, sizeof(DumpableObject *), + DOCatalogIdCompare); + + return ptrs; + } + + /* + * qsort comparator for pointers to DumpableObjects + */ + static int + DOCatalogIdCompare(const void *p1, const void *p2) + { + const DumpableObject *obj1 = *(DumpableObject * const *) p1; + const DumpableObject *obj2 = *(DumpableObject * const *) p2; + int cmpval; + + /* + * Compare OID first since it's usually unique, whereas there will only be + * a few distinct values of tableoid. + */ + cmpval = oidcmp(obj1->catId.oid, obj2->catId.oid); + if (cmpval == 0) + cmpval = oidcmp(obj1->catId.tableoid, obj2->catId.tableoid); + return cmpval; + } + + /* + * Build an array of pointers to all known dumpable objects + * + * This simply creates a modifiable copy of the internal map. + */ + void + getDumpableObjects(DumpableObject ***objs, int *numObjs) + { + int i, + j; + + *objs = (DumpableObject **) + pg_malloc(allocedDumpIds * sizeof(DumpableObject *)); + j = 0; + for (i = 1; i < allocedDumpIds; i++) + { + if (dumpIdMap[i]) + (*objs)[j++] = dumpIdMap[i]; + } + *numObjs = j; + } + + /* + * Add a dependency link to a DumpableObject + * + * Note: duplicate dependencies are currently not eliminated + */ + void + addObjectDependency(DumpableObject *dobj, DumpId refId) + { + if (dobj->nDeps >= dobj->allocDeps) + { + if (dobj->allocDeps <= 0) + { + dobj->allocDeps = 16; + dobj->dependencies = (DumpId *) + pg_malloc(dobj->allocDeps * sizeof(DumpId)); + } + else + { + dobj->allocDeps *= 2; + dobj->dependencies = (DumpId *) + pg_realloc(dobj->dependencies, + dobj->allocDeps * sizeof(DumpId)); + } + } + dobj->dependencies[dobj->nDeps++] = refId; + } + + /* + * Remove a dependency link from a DumpableObject + * + * If there are multiple links, all are removed + */ + void + removeObjectDependency(DumpableObject *dobj, DumpId refId) + { + int i; + int j = 0; + + for (i = 0; i < dobj->nDeps; i++) + { + if (dobj->dependencies[i] != refId) + dobj->dependencies[j++] = dobj->dependencies[i]; + } + dobj->nDeps = j; + } + + + /* + * findTableByOid + * finds the entry (in tblinfo) of the table with the given oid + * returns NULL if not found + */ + TableInfo * + findTableByOid(Oid oid) + { + return (TableInfo *) findObjectByOid(oid, tblinfoindex, numTables); + } + + /* + * findTypeByOid + * finds the entry (in typinfo) of the type with the given oid + * returns NULL if not found + */ + TypeInfo * + findTypeByOid(Oid oid) + { + return (TypeInfo *) findObjectByOid(oid, typinfoindex, numTypes); + } + + /* + * findFuncByOid + * finds the entry (in funinfo) of the function with the given oid + * returns NULL if not found + */ + FuncInfo * + findFuncByOid(Oid oid) + { + return (FuncInfo *) findObjectByOid(oid, funinfoindex, numFuncs); + } + + /* + * findOprByOid + * finds the entry (in oprinfo) of the operator with the given oid + * returns NULL if not found + */ + OprInfo * + findOprByOid(Oid oid) + { + return (OprInfo *) findObjectByOid(oid, oprinfoindex, numOperators); + } + + /* + * findCollationByOid + * finds the entry (in collinfo) of the collation with the given oid + * returns NULL if not found + */ + CollInfo * + findCollationByOid(Oid oid) + { + return (CollInfo *) findObjectByOid(oid, collinfoindex, numCollations); + } + + + /* + * findParentsByOid + * find a table's parents in tblinfo[] + */ + static void + findParentsByOid(TableInfo *self, + InhInfo *inhinfo, int numInherits) + { + Oid oid = self->dobj.catId.oid; + int i, + j; + int numParents; + + numParents = 0; + for (i = 0; i < numInherits; i++) + { + if (inhinfo[i].inhrelid == oid) + numParents++; + } + + self->numParents = numParents; + + if (numParents > 0) + { + self->parents = (TableInfo **) + pg_malloc(sizeof(TableInfo *) * numParents); + j = 0; + for (i = 0; i < numInherits; i++) + { + if (inhinfo[i].inhrelid == oid) + { + TableInfo *parent; + + parent = findTableByOid(inhinfo[i].inhparent); + if (parent == NULL) + { + write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n", + inhinfo[i].inhparent, + self->dobj.name, + oid); + exit_nicely(); + } + self->parents[j++] = parent; + } + } + } + else + self->parents = NULL; + } + + /* + * parseOidArray + * parse a string of numbers delimited by spaces into a character array + * + * Note: actually this is used for both Oids and potentially-signed + * attribute numbers. This should cause no trouble, but we could split + * the function into two functions with different argument types if it does. + */ + + void + parseOidArray(const char *str, Oid *array, int arraysize) + { + int j, + argNum; + char temp[100]; + char s; + + argNum = 0; + j = 0; + for (;;) + { + s = *str++; + if (s == ' ' || s == '\0') + { + if (j > 0) + { + if (argNum >= arraysize) + { + write_msg(NULL, "could not parse numeric array \"%s\": too many numbers\n", str); + exit_nicely(); + } + temp[j] = '\0'; + array[argNum++] = atooid(temp); + j = 0; + } + if (s == '\0') + break; + } + else + { + if (!(isdigit((unsigned char) s) || s == '-') || + j >= sizeof(temp) - 1) + { + write_msg(NULL, "could not parse numeric array \"%s\": invalid character in number\n", str); + exit_nicely(); + } + temp[j++] = s; + } + } + + while (argNum < arraysize) + array[argNum++] = InvalidOid; + } + + + /* + * strInArray: + * takes in a string and a string array and the number of elements in the + * string array. + * returns the index if the string is somewhere in the array, -1 otherwise + */ + + static int + strInArray(const char *pattern, char **arr, int arr_size) + { + int i; + + for (i = 0; i < arr_size; i++) + { + if (strcmp(pattern, arr[i]) == 0) + return i; + } + return -1; + } + + + /* + * Support for simple list operations + */ + + void + simple_oid_list_append(SimpleOidList *list, Oid val) + { + SimpleOidListCell *cell; + + cell = (SimpleOidListCell *) pg_malloc(sizeof(SimpleOidListCell)); + cell->next = NULL; + cell->val = val; + + if (list->tail) + list->tail->next = cell; + else + list->head = cell; + list->tail = cell; + } + + void + simple_string_list_append(SimpleStringList *list, const char *val) + { + SimpleStringListCell *cell; + + /* this calculation correctly accounts for the null trailing byte */ + cell = (SimpleStringListCell *) + pg_malloc(sizeof(SimpleStringListCell) + strlen(val)); + cell->next = NULL; + strcpy(cell->val, val); + + if (list->tail) + list->tail->next = cell; + else + list->head = cell; + list->tail = cell; + } + + bool + simple_oid_list_member(SimpleOidList *list, Oid val) + { + SimpleOidListCell *cell; + + for (cell = list->head; cell; cell = cell->next) + { + if (cell->val == val) + return true; + } + return false; + } + + bool + simple_string_list_member(SimpleStringList *list, const char *val) + { + SimpleStringListCell *cell; + + for (cell = list->head; cell; cell = cell->next) + { + if (strcmp(cell->val, val) == 0) + return true; + } + return false; + } *** 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" *************** *** 187,192 **** static void dumpConversion(Archive *fout, ConvInfo *convinfo); --- 188,194 ---- 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); *************** *** 5366,5371 **** getTriggers(TableInfo tblinfo[], int numTables) --- 5368,5443 ---- } /* + * getCmdTriggers + * get information about every command trigger on a dumpable table + */ + CmdTriggerInfo * + getCmdTriggers(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("pg_catalog"); + + if (g_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 = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, 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 * *************** *** 7221,7226 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj) --- 7293,7301 ---- case DO_TRIGGER: dumpTrigger(fout, (TriggerInfo *) dobj); break; + case DO_CMDTRIGGER: + dumpCmdTrigger(fout, (CmdTriggerInfo *) dobj); + break; case DO_CONSTRAINT: dumpConstraint(fout, (ConstraintInfo *) dobj); break; *************** *** 13860,13865 **** dumpTrigger(Archive *fout, TriggerInfo *tginfo) --- 13935,14000 ---- 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 if (ctginfo->ctgtype == CMD_TRIGGER_FIRED_INSTEAD) + appendPQExpBuffer(query, " INSTEAD OF"); + else + { + write_msg(NULL, "unexpected ctgtype value: %d\n", ctginfo->ctgtype); + exit_nicely(); + } + write_msg(NULL, "PHOQUE\n"); + + 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)); + + write_msg(NULL, "BLURPS\n"); + + 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); + + write_msg(NULL, "PLOP\n"); + + 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 *************** *** 359,364 **** typedef struct _triggerInfo --- 360,375 ---- 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 *************** *** 552,557 **** extern void getIndexes(TableInfo tblinfo[], int numTables); --- 563,569 ---- extern void getConstraints(TableInfo tblinfo[], int numTables); extern RuleInfo *getRules(int *numRules); extern void getTriggers(TableInfo tblinfo[], int numTables); + extern CmdTriggerInfo *getCmdTriggers(int *numCmdTriggers); extern ProcLangInfo *getProcLangs(int *numProcLangs); extern CastInfo *getCasts(int *numCasts); extern void getTableAttrs(TableInfo *tbinfo, int numTables); *** 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 */ }; *************** *** 1105,1110 **** describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) --- 1107,1117 ---- "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 *************** *** 378,384 **** 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); --- 378,395 ---- 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 *************** *** 195,200 **** slashUsage(unsigned short int pager) --- 195,201 ---- 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)); *** /dev/null --- b/src/include/catalog/pg_cmdtrigger.h *************** *** 0 **** --- 1,71 ---- + /*------------------------------------------------------------------------- + * + * 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/INSTEAD */ + 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' + #define CMD_TRIGGER_FIRED_INSTEAD 'I' + + #endif /* PG_CMDTRIGGER_H */ *** /dev/null --- b/src/include/commands/cmdtrigger.h *************** *** 0 **** --- 1,30 ---- + /*------------------------------------------------------------------------- + * + * 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 "commands/defrem.h" + #include "nodes/execnodes.h" + #include "nodes/parsenodes.h" + + 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); + + int ExecBeforeOrInsteadOfCommandTriggers(Node *parsetree, CommandContext cmd); + void ExecAfterCommandTriggers(Node *parsetree, CommandContext cmd); + + #endif /* TRIGGER_H */ *** a/src/include/commands/defrem.h --- b/src/include/commands/defrem.h *************** *** 177,180 **** extern TypeName *defGetTypeName(DefElem *def); --- 177,193 ---- extern int defGetTypeLength(DefElem *def); extern DefElem *defWithOids(bool value); + /* utils/adt/ruleutils.c -- FIXME, find a better place */ + typedef struct CommandContextData + { + char *tag; /* Command Tag */ + char *cmdstr; /* Command String, rewritten by ruleutils */ + char *schemaname; /* schemaname or NULL if not relevant */ + char *objectname; /* objectname */ + } CommandContextData; + + typedef struct CommandContextData *CommandContext; + + extern void pg_get_cmddef(CommandContext cmd, void *parsetree); + #endif /* DEFREM_H */ *** a/src/include/nodes/nodes.h --- b/src/include/nodes/nodes.h *************** *** 291,296 **** typedef enum NodeTag --- 291,297 ---- T_IndexStmt, T_CreateFunctionStmt, T_AlterFunctionStmt, + T_RemoveFuncStmt, T_DoStmt, T_RenameStmt, T_RuleStmt, *************** *** 311,317 **** typedef enum NodeTag --- 312,320 ---- T_VariableShowStmt, T_DiscardStmt, T_CreateTrigStmt, + T_DropPropertyStmt, T_CreatePLangStmt, + T_DropPLangStmt, T_CreateRoleStmt, T_AlterRoleStmt, T_DropRoleStmt, *************** *** 325,333 **** typedef enum NodeTag --- 328,339 ---- T_AlterRoleSetStmt, T_CreateConversionStmt, T_CreateCastStmt, + T_DropCastStmt, T_CreateOpClassStmt, T_CreateOpFamilyStmt, T_AlterOpFamilyStmt, + T_RemoveOpClassStmt, + T_RemoveOpFamilyStmt, T_PrepareStmt, T_ExecuteStmt, T_DeallocateStmt, *************** *** 346,353 **** typedef enum NodeTag --- 352,361 ---- T_AlterTSConfigurationStmt, T_CreateFdwStmt, T_AlterFdwStmt, + T_DropFdwStmt, T_CreateForeignServerStmt, T_AlterForeignServerStmt, + T_DropForeignServerStmt, T_CreateUserMappingStmt, T_AlterUserMappingStmt, T_DropUserMappingStmt, *************** *** 357,362 **** typedef enum NodeTag --- 365,373 ---- 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, *************** *** 1728,1733 **** typedef struct CreateTrigStmt --- 1729,1762 ---- } 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 */ + int16 timing; /* BEFORE, AFTER, or INSTEAD */ + 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 * ---------------------- */ *************** *** 1909,1914 **** typedef struct DropStmt --- 1938,1974 ---- } 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) *** a/src/test/regress/expected/sanity_check.out --- b/src/test/regress/expected/sanity_check.out *************** *** 93,98 **** SELECT relname, relhasindex --- 93,99 ---- pg_authid | t pg_cast | t pg_class | t + pg_cmdtrigger | t pg_collation | t pg_constraint | t pg_conversion | t *************** *** 164,170 **** SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (153 rows) -- -- another sanity check: every system catalog that has OIDs should have --- 165,171 ---- timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (154 rows) -- -- another sanity check: every system catalog that has OIDs should have *** a/src/test/regress/expected/triggers.out --- b/src/test/regress/expected/triggers.out *************** *** 1443,1445 **** NOTICE: drop cascades to 2 other objects --- 1443,1480 ---- DETAIL: drop cascades to view city_view drop cascades to view european_city_view DROP TABLE country_table; + CREATE FUNCTION cmdtrigger_notice + ( + IN cmd_tag text, + IN cmd_string text, + IN schemaname text, + IN relname text + ) + RETURNS void + LANGUAGE plpgsql + AS $$ + BEGIN + RAISE NOTICE 'cmd_string: %', cmd_string; + END; + $$; + CREATE TRIGGER cmdtrigger_notice + AFTER COMMAND CREATE TABLE + EXECUTE PROCEDURE cmdtrigger_notice(); + CREATE TRIGGER cmdtrigger_notice + AFTER COMMAND DROP TABLE + EXECUTE PROCEDURE cmdtrigger_notice(); + -- that should error out as you can't have both INSTEAD OF command triggers + -- and BEFORE|AFTER triggers defined on the same command + CREATE TRIGGER cmdtrigger_notice_error + INSTEAD OF COMMAND DROP TABLE + EXECUTE PROCEDURE cmdtrigger_notice(); + ERROR: "DROP TABLE" already has AFTER triggers + DETAIL: Commands cannot have both AFTER and INSTEAD OF triggers. + CREATE TABLE foo(a serial, b text, primary key (a, b)); + NOTICE: CREATE TABLE will create implicit sequence "foo_a_seq" for serial column "foo.a" + NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo" + NOTICE: cmd_string: CREATE TABLE public.foo (a serial,b text, PRIMARY KEY(a,b)); + DROP TABLE foo; + NOTICE: cmd_string: DROP TABLE public.foo RESTRICT; + DROP TRIGGER cmdtrigger_notice ON COMMAND CREATE TABLE; + DROP TRIGGER cmdtrigger_notice ON COMMAND DROP TABLE; *** a/src/test/regress/sql/triggers.sql --- b/src/test/regress/sql/triggers.sql *************** *** 961,963 **** SELECT * FROM city_view; --- 961,998 ---- DROP TABLE city_table CASCADE; DROP TABLE country_table; + + CREATE FUNCTION cmdtrigger_notice + ( + IN cmd_tag text, + IN cmd_string text, + IN schemaname text, + IN relname text + ) + RETURNS void + LANGUAGE plpgsql + AS $$ + BEGIN + RAISE NOTICE 'cmd_string: %', cmd_string; + END; + $$; + + CREATE TRIGGER cmdtrigger_notice + AFTER COMMAND CREATE TABLE + EXECUTE PROCEDURE cmdtrigger_notice(); + + CREATE TRIGGER cmdtrigger_notice + AFTER COMMAND DROP TABLE + EXECUTE PROCEDURE cmdtrigger_notice(); + + -- that should error out as you can't have both INSTEAD OF command triggers + -- and BEFORE|AFTER triggers defined on the same command + CREATE TRIGGER cmdtrigger_notice_error + INSTEAD OF COMMAND DROP TABLE + EXECUTE PROCEDURE cmdtrigger_notice(); + + CREATE TABLE foo(a serial, b text, primary key (a, b)); + DROP TABLE foo; + + DROP TRIGGER cmdtrigger_notice ON COMMAND CREATE TABLE; + DROP TRIGGER cmdtrigger_notice ON COMMAND DROP TABLE;