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;
