command-trigger.v2.patch
text/x-patch
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v2
| File | + | − |
|---|---|---|
| doc/src/sgml/ref/create_trigger.sgml | 92 | 5 |
| doc/src/sgml/ref/drop_trigger.sgml | 16 | 3 |
| src/backend/catalog/dependency.c | 46 | 1 |
| src/backend/catalog/Makefile | 1 | 1 |
| src/backend/catalog/objectaddress.c | 8 | 0 |
| src/backend/commands/alter.c | 4 | 0 |
| src/backend/commands/cmdtrigger.c | 708 | 0 |
| src/backend/commands/Makefile | 2 | 2 |
| src/backend/nodes/copyfuncs.c | 64 | 0 |
| src/backend/nodes/equalfuncs.c | 56 | 0 |
| src/backend/nodes/outfuncs.c | 432 | 0 |
| src/backend/nodes/readfuncs.c | 211 | 0 |
| src/backend/parser/gram.y | 96 | 5 |
| src/backend/tcop/utility.c | 62 | 0 |
| src/backend/utils/adt/ruleutils.c | 613 | 1 |
| src/include/catalog/dependency.h | 1 | 0 |
| src/include/catalog/indexing.h | 5 | 0 |
| src/include/catalog/pg_cmdtrigger.h | 59 | 0 |
| src/include/commands/cmdtrigger.h | 45 | 0 |
| src/include/commands/defrem.h | 14 | 0 |
| src/include/nodes/nodes.h | 11 | 0 |
| src/include/nodes/parsenodes.h | 60 | 0 |
| src/include/parser/kwlist.h | 1 | 0 |
| src/test/regress/expected/sanity_check.out | 1 | 0 |
| src/test/regress/expected/triggers.out | 35 | 0 |
| src/test/regress/sql/triggers.sql | 35 | 0 |
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index a7915cf..059e6bb 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -29,12 +29,26 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="PARAMETER">name</replaceable>
[ WHEN ( <replaceable class="parameter">condition</replaceable> ) ]
EXECUTE PROCEDURE <replaceable class="PARAMETER">function_name</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
+CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTER | INSTEAD OF } COMMAND <replaceable class="PARAMETER">command</replaceable>
+ EXECUTE PROCEDURE <replaceable class="PARAMETER">function_name</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
+
<phrase>where <replaceable class="parameter">event</replaceable> can be one of:</phrase>
INSERT
UPDATE [ OF <replaceable class="parameter">column_name</replaceable> [, ... ] ]
DELETE
TRUNCATE
+
+<phrase>and where <replaceable class="parameter">command</replaceable> can be one of:</phrase>
+
+ CREATE TABLE
+ ALTER TABLE
+ DROP TABLE
+ CREATE VIEW
+ DROP VIEW
+ CREATE EXTENSION
+ DROP EXTENSION
+
</synopsis>
</refsynopsisdiv>
@@ -42,10 +56,20 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="PARAMETER">name</replaceable>
<title>Description</title>
<para>
- <command>CREATE TRIGGER</command> creates a new trigger. The
- trigger will be associated with the specified table or view and will
- execute the specified function <replaceable
- class="parameter">function_name</replaceable> when certain events occur.
+ <command>CREATE TRIGGER</command> creates a new trigger. The trigger will
+ be associated with the specified table, view or command and will execute
+ the specified
+ function <replaceable class="parameter">function_name</replaceable> when
+ certain events occur.
+ </para>
+
+ <para>
+ 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 <literal>False</literal> 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 <literal>void</literal>.
</para>
<para>
@@ -251,6 +275,15 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
</varlistentry>
<varlistentry>
+ <term><replaceable class="parameter">command</replaceable></term>
+ <listitem>
+ <para>
+ The tag of the command the trigger is for.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">referenced_table_name</replaceable></term>
<listitem>
<para>
@@ -334,7 +367,23 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
<para>
A user-supplied function that is declared as taking no arguments
and returning type <literal>trigger</>, which is executed when
- the trigger fires.
+ the trigger fires, for table and view triggers.
+ </para>
+ <para>
+ 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.
+ </para>
+ <para>
+ The command trigger function is called with the
+ parameters <literal>command string</literal> (normalized command
+ string rewritten by PostgreSQL), <literal>command node
+ string</literal> (the command syntax tree, in its text form, is easier
+ to deal with programmaticaly for more complex inquiries about the
+ command), <literal>schemaname</literal> (can be null)
+ and <literal>object name</literal>.
</para>
</listitem>
</varlistentry>
@@ -352,6 +401,10 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
can be accessed within the function; it might be different from
normal function arguments.
</para>
+ <para>
+ Command triggers pay no attention to
+ the <replaceable class="parameter">arguments</replaceable>.
+ </para>
</listitem>
</varlistentry>
</variablelist>
@@ -469,6 +522,35 @@ CREATE TRIGGER view_insert
FOR EACH ROW
EXECUTE PROCEDURE view_insert_row();
</programlisting>
+
+ Execute the function <function>enforce_local_style</> each time
+ a <literal>CREATE TABLE</literal> command is run:
+
+<programlisting>
+CREATE OR REPLACE FUNCTION enforce_local_style
+ (
+ IN cmd_string text,
+ IN cmd_nodestring 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();
+</programlisting>
</para>
<para>
@@ -531,6 +613,11 @@ CREATE TRIGGER view_insert
</para>
<para>
+ The ability to run triggers on commands is <productname>PostgreSQL</>
+ extension of the SQL standard.
+ </para>
+
+ <para>
The ability to specify multiple actions for a single trigger using
<literal>OR</literal> is a <productname>PostgreSQL</> extension of
the SQL standard.
diff --git a/doc/src/sgml/ref/drop_trigger.sgml b/doc/src/sgml/ref/drop_trigger.sgml
index 2fcc690..3e37aa2 100644
--- a/doc/src/sgml/ref/drop_trigger.sgml
+++ b/doc/src/sgml/ref/drop_trigger.sgml
@@ -22,6 +22,18 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
DROP TRIGGER [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ON <replaceable class="PARAMETER">table</replaceable> [ CASCADE | RESTRICT ]
+DROP TRIGGER [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ON COMMAND <replaceable class="PARAMETER">command</replaceable> [ CASCADE | RESTRICT ]
+
+<phrase>where <replaceable class="parameter">command</replaceable> can be one of:</phrase>
+
+ CREATE TABLE
+ ALTER TABLE
+ DROP TABLE
+ CREATE VIEW
+ DROP VIEW
+ CREATE EXTENSION
+ DROP EXTENSION
+
</synopsis>
</refsynopsisdiv>
@@ -29,9 +41,10 @@ DROP TRIGGER [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ON
<title>Description</title>
<para>
- <command>DROP TRIGGER</command> 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.
+ <command>DROP TRIGGER</command> 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.
</para>
</refsect1>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 5a4419d..7e7d4c6 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -31,7 +31,7 @@ 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_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 \
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 223c097..42701f3 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -25,6 +25,7 @@
#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,6 +53,7 @@
#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,7 +159,8 @@ static const Oid object_classes[MAX_OCLASS] = {
ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */
UserMappingRelationId, /* OCLASS_USER_MAPPING */
DefaultAclRelationId, /* OCLASS_DEFACL */
- ExtensionRelationId /* OCLASS_EXTENSION */
+ ExtensionRelationId, /* OCLASS_EXTENSION */
+ CmdTriggerRelationId /* OCLASS_CMDTRIGGER */
};
@@ -1052,6 +1055,10 @@ doDeletion(const ObjectAddress *object)
break;
}
+ case OCLASS_CMDTRIGGER:
+ RemoveCmdTriggerById(object->objectId);
+ break;
+
case OCLASS_PROC:
RemoveFunctionById(object->objectId);
break;
@@ -2173,6 +2180,9 @@ getObjectClass(const ObjectAddress *object)
case ExtensionRelationId:
return OCLASS_EXTENSION;
+
+ case CmdTriggerRelationId:
+ return OCLASS_CMDTRIGGER;
}
/* shouldn't get here */
@@ -2807,6 +2817,41 @@ getObjectDescription(const ObjectAddress *object)
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);
+
+ appendStringInfo(&buffer, _("trigger %s on %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,
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 30873aa..67f6fab 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -21,6 +21,7 @@
#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,6 +45,7 @@
#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,6 +206,12 @@ static ObjectPropertyType ObjectProperty[] =
InvalidAttrNumber
},
{
+ CmdTriggerRelationId,
+ CmdTriggerOidIndexId,
+ -1,
+ InvalidAttrNumber
+ },
+ {
TSConfigRelationId,
TSConfigOidIndexId,
TSCONFIGOID,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 4af7aad..a00a358 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -12,8 +12,8 @@ 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 \
+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 \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index c321224..505d672 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -61,6 +61,10 @@ ExecRenameStmt(RenameStmt *stmt)
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;
diff --git a/src/backend/commands/cmdtrigger.c b/src/backend/commands/cmdtrigger.c
new file mode 100644
index 0000000..5a4d370
--- /dev/null
+++ b/src/backend/commands/cmdtrigger.c
@@ -0,0 +1,708 @@
+/*-------------------------------------------------------------------------
+ *
+ * trigger.c
+ * PostgreSQL TRIGGERs support code.
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/commands/trigger.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/defrem.h"
+#include "commands/cmdtrigger.h"
+#include "commands/trigger.h"
+#include "parser/parse_func.h"
+#include "pgstat.h"
+#include "tcop/utility.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"
+
+static void check_cmdtrigger_name(const char *command, const char *trigname, Relation tgrel);
+static RegProcedure * list_triggers_for_command(const char *command, char type);
+
+/*
+ * Create a trigger. Returns the OID of the created trigger.
+ */
+Oid
+CreateCmdTrigger(CreateCmdTrigStmt *stmt, const char *queryString)
+{
+ Relation tgrel;
+ HeapTuple tuple;
+ Datum values[Natts_pg_trigger];
+ bool nulls[Natts_pg_trigger];
+ /* cmd trigger args: cmd_string, cmd_nodestring, schemaname, objectname */
+ Oid fargtypes[4] = {TEXTOID, TEXTOID, TEXTOID, TEXTOID};
+ Oid funcoid;
+ Oid funcrettype;
+ Oid trigoid;
+ char ctgtype;
+ ObjectAddress myself,
+ referenced;
+
+ /*
+ * 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);
+
+ /*
+ * Scan pg_cmdtrigger for existing triggers on command. We do this only to
+ * give a nice error message if there's already a trigger of the same name.
+ * (The unique index on ctgcommand/ctgname would complain anyway.)
+ *
+ * NOTE that this is cool only because we have AccessExclusiveLock on
+ * the relation, so the trigger set won't be changing underneath us.
+ */
+ check_cmdtrigger_name(stmt->command, stmt->trigname, tgrel);
+
+ switch (stmt->timing)
+ {
+ case TRIGGER_TYPE_BEFORE:
+ {
+ RegProcedure *procs = list_triggers_for_command(stmt->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",
+ stmt->command),
+ errdetail("Commands cannot have both BEFORE and INSTEAD OF triggers.")));
+ break;
+ }
+
+ case TRIGGER_TYPE_INSTEAD:
+ {
+ RegProcedure *before = list_triggers_for_command(stmt->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",
+ stmt->command),
+ errdetail("Commands cannot have both BEFORE and INSTEAD OF triggers.")));
+
+ after = list_triggers_for_command(stmt->command, CMD_TRIGGER_FIRED_AFTER);
+ if (after[0] != InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("\"%s\" already has AFTER triggers",
+ stmt->command),
+ errdetail("Commands cannot have both AFTER and INSTEAD OF triggers.")));
+ break;
+ }
+
+ case TRIGGER_TYPE_AFTER:
+ {
+ RegProcedure *procs = list_triggers_for_command(stmt->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",
+ stmt->command),
+ errdetail("Commands cannot have both AFTER and INSTEAD OF triggers.")));
+ break;
+ }
+ }
+
+ 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))));
+
+ /*
+ * Build the new pg_trigger tuple.
+ */
+ memset(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_cmdtrigger_ctgcommand - 1] = NameGetDatum(stmt->command);
+ values[Anum_pg_cmdtrigger_ctgname - 1] = NameGetDatum(stmt->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);
+
+ /* force tuple to have the desired OID */
+ trigoid = HeapTupleGetOid(tuple);
+
+ /*
+ * Insert tuple into pg_trigger.
+ */
+ simple_heap_insert(tgrel, tuple);
+
+ CatalogUpdateIndexes(tgrel, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(tgrel, RowExclusiveLock);
+
+ /*
+ * Record dependencies for trigger. Always place a normal dependency on
+ * the function.
+ */
+ myself.classId = TriggerRelationId;
+ myself.objectId = trigoid;
+ myself.objectSubId = 0;
+
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = funcoid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ return trigoid;
+}
+
+/*
+ * DropTrigger - drop an individual trigger by name
+ */
+void
+DropCmdTrigger(DropCmdTrigStmt *stmt)
+{
+ ObjectAddress object;
+
+ object.classId = CmdTriggerRelationId;
+ object.objectId = get_cmdtrigger_oid(stmt->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, stmt->command)));
+ return;
+ }
+
+ /*
+ * 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 */
+
+ tgrel = heap_open(CmdTriggerRelationId, AccessShareLock);
+
+ 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, AccessShareLock);
+ heap_freetuple(tup);
+}
+
+
+/*
+ * Rename conversion
+ */
+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;
+
+ Assert(list_length(name) == 1);
+ command = strVal((Value *)linitial(name));
+
+ rel = heap_open(CmdTriggerRelationId, RowExclusiveLock);
+
+ /* 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(NOTICE, "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 string, text
+ * command node string, text
+ * schemaname, text, can be null
+ * 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, and we even provide them with a
+ * nodeToString() output.
+ *
+ */
+
+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)
+ {
+ if (count == 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, 4, InvalidOid, NULL, NULL);
+
+ fcinfo.arg[0] = PointerGetDatum(cstring_to_text(pstrdup(cmd->cmdstr)));
+
+ if (cmd->nodestr != NULL)
+ fcinfo.arg[1] = PointerGetDatum(cstring_to_text(pstrdup(cmd->nodestr)));
+
+ if (cmd->schemaname != NULL)
+ fcinfo.arg[2] = PointerGetDatum(cstring_to_text(pstrdup(cmd->schemaname)));
+
+ fcinfo.arg[3] = PointerGetDatum(cstring_to_text(pstrdup(cmd->objectname)));
+
+ fcinfo.argnull[0] = false;
+ fcinfo.argnull[1] = cmd->nodestr == NULL;
+ fcinfo.argnull[2] = cmd->schemaname == NULL;
+ fcinfo.argnull[3] = false;
+
+ pgstat_init_function_usage(&fcinfo, &fcusage);
+
+ result = FunctionCallInvoke(&fcinfo);
+
+ pgstat_end_function_usage(&fcusage, true);
+
+ if (!fcinfo.isnull && DatumGetBool(result) == false)
+ return false;
+ return true;
+}
+
+/*
+ * 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, const char *cmdtag)
+{
+ 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, cmdtag, per_command_context))
+ nb++;
+ nb += ExecInsteadOfCommandTriggers(parsetree, cmdtag, per_command_context);
+
+ /* Release working resources */
+ MemoryContextDelete(per_command_context);
+ return nb;
+}
+
+bool
+ExecBeforeCommandTriggers(Node *parsetree, const char *cmdtag,
+ MemoryContext per_command_context)
+{
+ MemoryContext oldContext;
+ CommandContextData cmd;
+ RegProcedure *procs = list_triggers_for_command(cmdtag,
+ 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.
+ */
+ oldContext = MemoryContextSwitchTo(per_command_context);
+ MemoryContextReset(per_command_context);
+
+ while (cont && InvalidOid != (proc = procs[cur++]))
+ {
+ if (cur==1)
+ {
+ cmd.tag = (char *)cmdtag;
+ pg_get_cmddef(&cmd, parsetree);
+ }
+ cont = call_cmdtrigger_procedure(proc, &cmd, per_command_context);
+
+ if (cont == false)
+ elog(WARNING,
+ "command \"%s %s...\" was cancelled by procedure \"%s\"",
+ cmdtag, cmd.objectname, get_func_name(proc));
+ }
+ MemoryContextSwitchTo(oldContext);
+ return cont;
+}
+
+/*
+ * return the count of triggers we fired
+ */
+int
+ExecInsteadOfCommandTriggers(Node *parsetree, const char *cmdtag,
+ MemoryContext per_command_context)
+{
+ MemoryContext oldContext;
+ CommandContextData cmd;
+ RegProcedure *procs = list_triggers_for_command(cmdtag,
+ 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.
+ */
+ oldContext = MemoryContextSwitchTo(per_command_context);
+ MemoryContextReset(per_command_context);
+
+ while (InvalidOid != (proc = procs[cur++]))
+ {
+ if (cur==1)
+ {
+ cmd.tag = (char *)cmdtag;
+ pg_get_cmddef(&cmd, parsetree);
+ }
+ call_cmdtrigger_procedure(proc, &cmd, per_command_context);
+ }
+
+ MemoryContextSwitchTo(oldContext);
+ return cur-1;
+}
+
+void
+ExecAfterCommandTriggers(Node *parsetree, const char *cmdtag)
+{
+ MemoryContext oldContext, per_command_context;
+ CommandContextData cmd;
+ RegProcedure *procs = list_triggers_for_command(cmdtag,
+ 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);
+
+ oldContext = MemoryContextSwitchTo(per_command_context);
+
+ while (InvalidOid != (proc = procs[cur++]))
+ {
+ if (cur==1)
+ {
+ cmd.tag = (char *)cmdtag;
+ pg_get_cmddef(&cmd, parsetree);
+ }
+ call_cmdtrigger_procedure(proc, &cmd, per_command_context);
+ }
+
+ /* Release working resources */
+ MemoryContextSwitchTo(oldContext);
+ MemoryContextDelete(per_command_context);
+
+ return;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c70a5bd..68034f6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3451,6 +3451,58 @@ _copyCreateTrigStmt(CreateTrigStmt *from)
return newnode;
}
+static DropPropertyStmt *
+_copyDropPropertyStmt(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(CreateCmdTrigStmt *from)
+{
+ CreateCmdTrigStmt *newnode = makeNode(CreateCmdTrigStmt);
+
+ COPY_STRING_FIELD(command);
+ COPY_STRING_FIELD(trigname);
+ COPY_SCALAR_FIELD(timing);
+ COPY_NODE_FIELD(funcname);
+
+ return newnode;
+}
+
+static DropCmdTrigStmt *
+_copyDropCmdTrigStmt(DropCmdTrigStmt *from)
+{
+ DropCmdTrigStmt *newnode = makeNode(DropCmdTrigStmt);
+
+ COPY_STRING_FIELD(command);
+ COPY_STRING_FIELD(trigname);
+ COPY_SCALAR_FIELD(behavior);
+ COPY_SCALAR_FIELD(missing_ok);
+
+ return newnode;
+}
+
+static AlterCmdTrigStmt *
+_copyAlterCmdTrigStmt(AlterCmdTrigStmt *from)
+{
+ AlterCmdTrigStmt *newnode = makeNode(AlterCmdTrigStmt);
+
+ COPY_STRING_FIELD(command);
+ COPY_STRING_FIELD(trigname);
+ COPY_STRING_FIELD(tgenabled);
+
+ return newnode;
+}
+
static CreatePLangStmt *
_copyCreatePLangStmt(CreatePLangStmt *from)
{
@@ -4303,6 +4355,18 @@ copyObject(void *from)
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;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f490a7a..65535f9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1773,6 +1773,50 @@ _equalCreateTrigStmt(CreateTrigStmt *a, CreateTrigStmt *b)
}
static bool
+_equalDropPropertyStmt(DropPropertyStmt *a, 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(CreateCmdTrigStmt *a, CreateCmdTrigStmt *b)
+{
+ COMPARE_STRING_FIELD(command);
+ COMPARE_STRING_FIELD(trigname);
+ COMPARE_SCALAR_FIELD(timing);
+ COMPARE_NODE_FIELD(funcname);
+
+ return true;
+}
+
+static bool
+_equalDropCmdTrigStmt(DropCmdTrigStmt *a, DropCmdTrigStmt *b)
+{
+ COMPARE_STRING_FIELD(command);
+ COMPARE_STRING_FIELD(trigname);
+ COMPARE_SCALAR_FIELD(behavior);
+ COMPARE_SCALAR_FIELD(missing_ok);
+
+ return true;
+}
+
+static bool
+_equalAlterCmdTrigStmt(AlterCmdTrigStmt *a, AlterCmdTrigStmt *b)
+{
+ COMPARE_STRING_FIELD(command);
+ COMPARE_STRING_FIELD(trigname);
+ COMPARE_STRING_FIELD(tgenabled);
+
+ return true;
+}
+
+static bool
_equalCreatePLangStmt(CreatePLangStmt *a, CreatePLangStmt *b)
{
COMPARE_SCALAR_FIELD(replace);
@@ -2846,6 +2890,18 @@ equal(void *a, void *b)
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;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 31af47f..387043d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1944,6 +1944,137 @@ _outPlannerParamItem(StringInfo str, PlannerParamItem *node)
*****************************************************************************/
static void
+_outInsertStmt(StringInfo str, 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, 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, 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, AlterTableStmt *node)
+{
+ WRITE_NODE_TYPE("ALTERTABLESTMT");
+
+ WRITE_NODE_FIELD(relation);
+ WRITE_NODE_FIELD(cmds);
+ WRITE_CHAR_FIELD(relkind);
+}
+
+static void
+_outAlterTableCmd(StringInfo str, 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, 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, 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, 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, AlterDefaultPrivilegesStmt *node)
+{
+ WRITE_NODE_TYPE("ALTERDEFAULTPRIVILEGESSTMT");
+
+ WRITE_NODE_FIELD(options);
+ WRITE_NODE_FIELD(action);
+}
+
+static void
+_outClusterStmt(StringInfo str, ClusterStmt *node)
+{
+ WRITE_NODE_TYPE("CLUSTERSTMT");
+
+ WRITE_NODE_FIELD(relation);
+ WRITE_STRING_FIELD(indexname);
+ WRITE_BOOL_FIELD(verbose);
+}
+
+static void
+_outCopyStmt(StringInfo str, 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, CreateStmt *node)
{
WRITE_NODE_TYPE("CREATESTMT");
@@ -1960,6 +2091,31 @@ _outCreateStmt(StringInfo str, CreateStmt *node)
}
static void
+_outDefineStmt(StringInfo str, 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, 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, CreateForeignTableStmt *node)
{
WRITE_NODE_TYPE("CREATEFOREIGNTABLESTMT");
@@ -1971,6 +2127,28 @@ _outCreateForeignTableStmt(StringInfo str, CreateForeignTableStmt *node)
}
static void
+_outDropStmt(StringInfo str, 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, 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, IndexStmt *node)
{
WRITE_NODE_TYPE("INDEXSTMT");
@@ -1994,6 +2172,33 @@ _outIndexStmt(StringInfo str, IndexStmt *node)
}
static void
+_outCreateFunctionStmt(StringInfo str, 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, 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, NotifyStmt *node)
{
WRITE_NODE_TYPE("NOTIFY");
@@ -2003,6 +2208,95 @@ _outNotifyStmt(StringInfo str, NotifyStmt *node)
}
static void
+_outViewStmt(StringInfo str, 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, 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, CreatedbStmt *node)
+{
+ WRITE_NODE_TYPE("CREATEDBSTMT");
+
+ WRITE_STRING_FIELD(dbname);
+ WRITE_NODE_FIELD(options);
+}
+
+static void
+_outVacuumStmt(StringInfo str, 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, 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, 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, CreateSchemaStmt *node)
+{
+ WRITE_NODE_TYPE("CREATESCHEMASTMT");
+
+ WRITE_STRING_FIELD(schemaname);
+ WRITE_STRING_FIELD(authid);
+ WRITE_NODE_FIELD(schemaElts);
+}
+
+static void
+_outCreateConversionStmt(StringInfo str, 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, DeclareCursorStmt *node)
{
WRITE_NODE_TYPE("DECLARECURSOR");
@@ -2064,6 +2358,23 @@ _outDefElem(StringInfo str, DefElem *node)
}
static void
+_outPrivGrantee(StringInfo str, PrivGrantee *node)
+{
+ WRITE_NODE_TYPE("PRIVGRANTEE");
+
+ WRITE_STRING_FIELD(rolname);
+}
+
+static void
+_outAccessPriv(StringInfo str, AccessPriv *node)
+{
+ WRITE_NODE_TYPE("ACCESSPRIV");
+
+ WRITE_STRING_FIELD(priv_name);
+ WRITE_NODE_FIELD(cols);
+}
+
+static void
_outInhRelation(StringInfo str, InhRelation *node)
{
WRITE_NODE_TYPE("INHRELATION");
@@ -2094,6 +2405,16 @@ _outXmlSerialize(StringInfo str, XmlSerialize *node)
}
static void
+_outCreateExtensionStmt(StringInfo str, 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, ColumnDef *node)
{
WRITE_NODE_TYPE("COLUMNDEF");
@@ -2251,6 +2572,17 @@ _outWindowClause(StringInfo str, WindowClause *node)
}
static void
+_outFunctionParameter(StringInfo str, 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, RowMarkClause *node)
{
WRITE_NODE_TYPE("ROWMARKCLAUSE");
@@ -2358,6 +2690,16 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
}
static void
+_outAlterCmdTrigStmt(StringInfo str, AlterCmdTrigStmt *node)
+{
+ WRITE_NODE_TYPE("ALTERCMDTRIGSTMT");
+
+ WRITE_STRING_FIELD(command);
+ WRITE_STRING_FIELD(trigname);
+ WRITE_STRING_FIELD(tgenabled);
+}
+
+static void
_outAExpr(StringInfo str, A_Expr *node)
{
WRITE_NODE_TYPE("AEXPR");
@@ -3034,24 +3376,102 @@ _outNode(StringInfo str, void *obj)
_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;
@@ -3076,6 +3496,9 @@ _outNode(StringInfo str, void *obj)
case T_WindowClause:
_outWindowClause(str, obj);
break;
+ case T_FunctionParameter:
+ _outFunctionParameter(str, obj);
+ break;
case T_RowMarkClause:
_outRowMarkClause(str, obj);
break;
@@ -3091,6 +3514,9 @@ _outNode(StringInfo str, void *obj)
case T_RangeTblEntry:
_outRangeTblEntry(str, obj);
break;
+ case T_AlterCmdTrigStmt:
+ _outAlterCmdTrigStmt(str, obj);
+ break;
case T_A_Expr:
_outAExpr(str, obj);
break;
@@ -3139,6 +3565,12 @@ _outNode(StringInfo str, void *obj)
case T_DefElem:
_outDefElem(str, obj);
break;
+ case T_PrivGrantee:
+ _outPrivGrantee(str, obj);
+ break;
+ case T_AccessPriv:
+ _outAccessPriv(str, obj);
+ break;
case T_InhRelation:
_outInhRelation(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3de20ad..2b1a76d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -255,6 +255,203 @@ _readDeclareCursorStmt(void)
}
/*
+ * _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_CHAR_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); */
+
+ 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 *
@@ -1254,6 +1451,20 @@ parseNodeString(void)
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))
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2a497d1..1ecd0ce 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -195,6 +195,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
}
%type <node> stmt schema_stmt
+ AlterCmdTrigStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
@@ -208,12 +209,12 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
- CreateAssertStmt CreateTrigStmt
+ CreateAssertStmt CreateTrigStmt CreateCmdTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
- DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
- DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
+ 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,6 +269,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <list> TriggerEvents TriggerOneEvent
%type <value> TriggerFuncArg
%type <node> TriggerWhen
+%type <str> trigger_command enable_trigger
%type <str> copy_file_name
database_name access_method_clause access_method attr_name
@@ -495,7 +497,7 @@ 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
+ 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,7 +677,8 @@ stmtmulti: stmtmulti ';' stmt
;
stmt :
- AlterDatabaseStmt
+ AlterCmdTrigStmt
+ | AlterDatabaseStmt
| AlterDatabaseSetStmt
| AlterDefaultPrivilegesStmt
| AlterDomainStmt
@@ -726,6 +729,7 @@ stmt :
| CreateStmt
| CreateTableSpaceStmt
| CreateTrigStmt
+ | CreateCmdTrigStmt
| CreateRoleStmt
| CreateUserStmt
| CreateUserMappingStmt
@@ -749,6 +753,7 @@ stmt :
| DropStmt
| DropTableSpaceStmt
| DropTrigStmt
+ | DropCmdTrigStmt
| DropRoleStmt
| DropUserStmt
| DropUserMappingStmt
@@ -4198,6 +4203,83 @@ DropTrigStmt:
/*****************************************************************************
*
* QUERIES :
+ * CREATE TRIGGER ... BEFORE|INSTEAD OF|AFTER COMMAND ...
+ * DROP TRIGGER ... ON COMMAND ...
+ *
+ *****************************************************************************/
+
+CreateCmdTrigStmt:
+ CREATE TRIGGER name TriggerActionTime COMMAND trigger_command
+ EXECUTE PROCEDURE func_name '(' TriggerFuncArgs ')'
+ {
+ CreateCmdTrigStmt *n = makeNode(CreateCmdTrigStmt);
+ n->trigname = $3;
+ n->timing = $4;
+ n->command = $6;
+ n->funcname = $9;
+ $$ = (Node *)n;
+ }
+ ;
+
+/*
+ * 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 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 opt_drop_behavior
+ {
+ DropCmdTrigStmt *n = makeNode(DropCmdTrigStmt);
+ n->trigname = $5;
+ n->command = $8;
+ 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;
+ }
+ ;
+
+enable_trigger:
+ ENABLE_P { $$ = "O"; }
+ | ENABLE_P REPLICA { $$ = "R"; }
+ | ENABLE_P ALWAYS { $$ = "A"; }
+ | DISABLE_P { $$ = "D"; }
+ ;
+
+/*****************************************************************************
+ *
+ * QUERIES :
* CREATE ASSERTION ...
* DROP ASSERTION ...
*
@@ -6559,6 +6641,15 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
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);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6f88c47..ef80550 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -25,6 +25,7 @@
#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"
@@ -184,6 +185,8 @@ check_xact_readonly(Node *parsetree)
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,6 +208,7 @@ check_xact_readonly(Node *parsetree)
case T_CreateRangeStmt:
case T_AlterEnumStmt:
case T_ViewStmt:
+ case T_DropCmdTrigStmt:
case T_DropStmt:
case T_DropdbStmt:
case T_DropTableSpaceStmt:
@@ -321,8 +325,17 @@ ProcessUtility(Node *parsetree,
DestReceiver *dest,
char *completionTag)
{
+ /* 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.
+ */
+ const char *commandTag = CreateCommandTag(parsetree);
+
Assert(queryString != NULL); /* required as of 8.4 */
+ if (ExecBeforeOrInsteadOfCommandTriggers(parsetree, commandTag) > 0)
+ return;
+
/*
* We provide a function hook variable that lets loadable plugins get
* control when ProcessUtility is called. Such a plugin would normally
@@ -334,6 +347,8 @@ ProcessUtility(Node *parsetree,
else
standard_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag);
+
+ ExecAfterCommandTriggers(parsetree, commandTag);
}
void
@@ -1040,6 +1055,18 @@ standard_ProcessUtility(Node *parsetree,
InvalidOid, InvalidOid, false);
break;
+ case T_CreateCmdTrigStmt:
+ (void) 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;
@@ -1931,6 +1958,18 @@ CreateCommandTag(Node *parsetree)
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;
@@ -2132,6 +2171,13 @@ CreateCommandTag(Node *parsetree)
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;
}
@@ -2426,6 +2472,22 @@ GetCommandLogLevel(Node *parsetree)
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;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 75923a6..83775bf 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -39,9 +39,11 @@
#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,7 +258,6 @@ 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
@@ -7326,3 +7327,614 @@ flatten_reloptions(Oid relid)
return result;
}
+
+/*
+ * Functions that ouputs a COMMAND given a Utility parsetree
+ *
+ * FIXME: First some tools that I couldn't find in the sources.
+ */
+static char *
+RangeVarToString(RangeVar *r)
+{
+ char *qualified_name = (char *)palloc0(NAMEDATALEN*2+2);
+
+ sprintf(qualified_name, "%s%s%s",
+ r->schemaname == NULL? "": r->schemaname,
+ r->schemaname == NULL? "": ".",
+ r->relname);
+
+ return qualified_name;
+}
+
+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(DEBUG2, "unrecognized relkind: %d", relkind);
+ break;
+ }
+ 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 = rel->schemaname;
+ 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 %s %s AS ",
+ node->replace? "OR REPLACE VIEW": "VIEW",
+ RangeVarToString(node->view));
+
+ get_query_def(viewParse, &buf, NIL, NULL, 0, 1);
+ appendStringInfoChar(&buf, ';');
+
+ cmd->cmdstr = buf.data;
+ cmd->schemaname = node->view->schemaname;
+ 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);
+
+ 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_InhRelation:
+ {
+ InhRelation *r = (InhRelation *) 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 = node->relation->schemaname;
+ 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 <type_name> */
+ 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 = node->relation->schemaname;
+ 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)
+{
+ cmd->nodestr = nodeToString(parsetree);
+ /* elog(NOTICE, "nodeToString: %s", cmd->nodestr); */
+ stringToNode(cmd->nodestr);
+
+ /*
+ * we need the big'o'switch here, and calling a specialized function per
+ * utility statement nodetag.
+ */
+
+ 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:
+ /* is it best to elog(ERROR)? Not while in development :) */
+ elog(DEBUG2, "unrecognized node type: %d",
+ (int) nodeTag(parsetree));
+ }
+}
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 5302e50..a7220a0 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -146,6 +146,7 @@ typedef enum ObjectClass
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;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 2e5d224..cc5b454 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -234,6 +234,11 @@ DECLARE_UNIQUE_INDEX(pg_trigger_tgrelid_tgname_index, 2701, on pg_trigger using
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));
diff --git a/src/include/catalog/pg_cmdtrigger.h b/src/include/catalog/pg_cmdtrigger.h
new file mode 100644
index 0000000..dc12dad
--- /dev/null
+++ b/src/include/catalog/pg_cmdtrigger.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+#endif /* PG_CMDTRIGGER_H */
diff --git a/src/include/commands/cmdtrigger.h b/src/include/commands/cmdtrigger.h
new file mode 100644
index 0000000..f7d0980
--- /dev/null
+++ b/src/include/commands/cmdtrigger.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * cmdtrigger.h
+ * Declarations for command trigger handling.
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/cmdtrigger.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef CMDTRIGGER_H
+#define CMDTRIGGER_H
+
+#include "nodes/execnodes.h"
+#include "nodes/parsenodes.h"
+
+/*
+ * 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'
+
+extern Oid 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, const char *command);
+int ExecInsteadOfCommandTriggers(Node *parsetree, const char *command,
+ MemoryContext per_command_context);
+bool ExecBeforeCommandTriggers(Node *parsetree, const char *command,
+ MemoryContext per_command_context);
+void ExecAfterCommandTriggers(Node *parsetree, const char *command);
+
+#endif /* TRIGGER_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 70d3a8f..e158fc5 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -175,4 +175,18 @@ extern TypeName *defGetTypeName(DefElem *def);
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 *nodestr; /* nodeToString(parsetree) */
+ 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 */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 3a24089..e1336bd 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -291,6 +291,7 @@ typedef enum NodeTag
T_IndexStmt,
T_CreateFunctionStmt,
T_AlterFunctionStmt,
+ T_RemoveFuncStmt,
T_DoStmt,
T_RenameStmt,
T_RuleStmt,
@@ -311,7 +312,9 @@ typedef enum NodeTag
T_VariableShowStmt,
T_DiscardStmt,
T_CreateTrigStmt,
+ T_DropPropertyStmt,
T_CreatePLangStmt,
+ T_DropPLangStmt,
T_CreateRoleStmt,
T_AlterRoleStmt,
T_DropRoleStmt,
@@ -325,9 +328,12 @@ typedef enum NodeTag
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,8 +352,10 @@ typedef enum NodeTag
T_AlterTSConfigurationStmt,
T_CreateFdwStmt,
T_AlterFdwStmt,
+ T_DropFdwStmt,
T_CreateForeignServerStmt,
T_AlterForeignServerStmt,
+ T_DropForeignServerStmt,
T_CreateUserMappingStmt,
T_AlterUserMappingStmt,
T_DropUserMappingStmt,
@@ -357,6 +365,9 @@ typedef enum NodeTag
T_CreateExtensionStmt,
T_AlterExtensionStmt,
T_AlterExtensionContentsStmt,
+ T_CreateCmdTrigStmt,
+ T_DropCmdTrigStmt,
+ T_AlterCmdTrigStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9e277c5..4469314 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1106,6 +1106,7 @@ typedef enum ObjectType
OBJECT_AGGREGATE,
OBJECT_ATTRIBUTE, /* type's attribute, when distinct from column */
OBJECT_CAST,
+ OBJECT_CMDTRIGGER,
OBJECT_COLUMN,
OBJECT_CONSTRAINT,
OBJECT_COLLATION,
@@ -1723,6 +1724,34 @@ typedef struct CreateTrigStmt
} CreateTrigStmt;
/* ----------------------
+ * Create COMMAND TRIGGER Statement
+ * ----------------------
+ */
+typedef struct CreateCmdTrigStmt
+{
+ NodeTag type;
+ char *command; /* command's name */
+ char *trigname; /* TRIGGER's name */
+ /* timing uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
+ 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
* ----------------------
*/
@@ -1904,6 +1933,37 @@ typedef struct DropStmt
} 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;
+ char *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
* ----------------------
*/
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 3d170bc..bb949d0 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -81,6 +81,7 @@ PG_KEYWORD("coalesce", COALESCE, COL_NAME_KEYWORD)
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)
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index cb468e5..b19fcbe 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ SELECT relname, relhasindex
pg_authid | t
pg_cast | t
pg_class | t
+ pg_cmdtrigger | t
pg_collation | t
pg_constraint | t
pg_conversion | t
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index b4d3919..3892d36 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1443,3 +1443,38 @@ NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to view city_view
drop cascades to view european_city_view
DROP TABLE country_table;
+CREATE FUNCTION cmdtrigger_notice
+ (
+ IN cmd_string text,
+ IN cmd_nodestring 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 foo (a serial,b text, PRIMARY KEY(a,b));
+DROP TABLE foo;
+NOTICE: cmd_string: DROP TABLE foo RESTRICT;
+DROP TRIGGER cmdtrigger_notice ON COMMAND CREATE TABLE;
+DROP TRIGGER cmdtrigger_notice ON COMMAND DROP TABLE;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index e52131d..f944d60 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -961,3 +961,38 @@ SELECT * FROM city_view;
DROP TABLE city_table CASCADE;
DROP TABLE country_table;
+
+CREATE FUNCTION cmdtrigger_notice
+ (
+ IN cmd_string text,
+ IN cmd_nodestring 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;