*** 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
--- 275,289 ----
+ 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
--- 398,407 ----
can be accessed within the function; it might be different from
normal function arguments.
+
+ 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;