Command Triggers
Hi,
As proposed by Jan Wieck on hackers and discussed in Ottawa at the
Clusters Hackers Meeting, the most (only?) workable way to ever have DDL
triggers is to have "command triggers" instead.
Rather than talking about catalogs and the like, it's all about firing a
registered user-defined function before or after processing the utility
command, or instead of processing it. This naturally involves a new
catalog (pg_cmdtrigger) and some new subcommands of CREATE and DROP
TRIGGER, and some support code for calling the function at the right
time.
The right place to hook this code in seems to be ProcessUtility(), which
is a central place for about all the commands that we handle. An
exception to that rule would be SELECT INTO and CREATE TABLE AS SELECT,
and my proposal would be to add specific call sites to the functions
I've provided in the attached patch rather than try to contort them into
being a good citizen as a utility command.
The ProcessUtility() integration currently looks like that:
const char *commandTag = CreateCommandTag(parsetree);
if (ExecBeforeOrInsteadOfCommandTriggers(parsetree, commandTag) > 0)
return;
... current code ...
ExecAfterCommandTriggers(parsetree, commandTag);
So I guess adding some call sites is manageable.
Now, the patch contains a Proof Of Concept implementation with a small
level of documentation and regression tests in order to get an agreement
on the principles that we already discussed.
The other part of the command trigger facility is what information we
should give to the user-defined functions, and in which form. I've
settled on passing always 4 text arguments: the command string, the
parse tree as a nodeToString() representation (Jan believes that this is
the easiest form we can provide for code consumption, and I tend to
agree), the schema name or NULL if the targeted object of the command is
not qualified, and the object name (or NULL if that does not apply).
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();
The v1 patch attached contains implementation for some commands only.
We need to add rewriting facilities for those commands we want to
support in the proposed model, because of multi-queries support in the
protocol and dynamic queries in EXECUTE e.g. (though I admit not having
had a look at EXECUTE implementation).
So each supported command has a cost, and the ones I claim to support in
the grammar in the patch are not seeing a complete support: first, I'm
missing some outfuncs and readfuncs (but I believe we can complete that
using a script on the source code) so that the cmd_nodestring argument
is currently always NULL. Second, I didn't complete the rewriting of
some more complex commands such as CREATE TABLE and ALTER TABLE.
Note that we can't reuse that much of ruleutils.c because it's written
to work from what we store in the catalogs rather than from a parsetree
object.
So, any comment on the approach before I complete the rewriting of the
commands, the out/read funcs, and add more commands to the trigger
support code?
https://github.com/dimitri/postgres/compare/master...command_triggers
$ git diff --stat master..
doc/src/sgml/ref/create_trigger.sgml | 97 +++++-
doc/src/sgml/ref/drop_trigger.sgml | 19 +-
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/dependency.c | 47 ++-
src/backend/catalog/objectaddress.c | 8 +
src/backend/commands/Makefile | 4 +-
src/backend/commands/cmdtrigger.c | 580 ++++++++++++++++++++++++++
src/backend/nodes/copyfuncs.c | 32 ++
src/backend/nodes/equalfuncs.c | 28 ++
src/backend/nodes/outfuncs.c | 405 ++++++++++++++++++
src/backend/parser/gram.y | 70 +++-
src/backend/tcop/utility.c | 44 ++
src/backend/utils/adt/ruleutils.c | 613 +++++++++++++++++++++++++++-
src/include/catalog/dependency.h | 1 +
src/include/catalog/indexing.h | 5 +
src/include/catalog/pg_cmdtrigger.h | 59 +++
src/include/commands/cmdtrigger.h | 43 ++
src/include/commands/defrem.h | 14 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 28 ++
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/sanity_check.out | 3 +-
src/test/regress/expected/triggers.out | 35 ++
src/test/regress/sql/triggers.sql | 35 ++
24 files changed, 2157 insertions(+), 18 deletions(-)
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
command-trigger.v1.patchtext/x-patchDownload
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 ec4c987..8c4c4f8 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"
@@ -203,6 +205,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/cmdtrigger.c b/src/backend/commands/cmdtrigger.c
new file mode 100644
index 0000000..b8fd250
--- /dev/null
+++ b/src/backend/commands/cmdtrigger.c
@@ -0,0 +1,580 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 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;
+ SysScanDesc tgscan;
+ ScanKeyData key;
+ 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.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_cmdtrigger_ctgcommand,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->command));
+ tgscan = systable_beginscan(tgrel, CmdTriggerCommandNameIndexId, true,
+ SnapshotNow, 1, &key);
+ while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+ {
+ Form_pg_cmdtrigger pg_cmdtrigger = (Form_pg_cmdtrigger) GETSTRUCT(tuple);
+
+ if (namestrcmp(&(pg_cmdtrigger->ctgname), stmt->trigname) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for command \"%s\" already exists",
+ stmt->trigname, stmt->command)));
+ }
+ systable_endscan(tgscan);
+
+ 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->trigname, stmt->command,
+ 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);
+}
+
+/*
+ * 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 *trigname, const char *command, 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;
+}
+
+
+/*
+ * 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);
+
+ if (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 63958c3..045282c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3527,6 +3527,32 @@ _copyDropPropertyStmt(DropPropertyStmt *from)
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 CreatePLangStmt *
_copyCreatePLangStmt(CreatePLangStmt *from)
{
@@ -4422,6 +4448,12 @@ copyObject(void *from)
case T_DropPropertyStmt:
retval = _copyDropPropertyStmt(from);
break;
+ case T_CreateCmdTrigStmt:
+ retval = _copyCreateCmdTrigStmt(from);
+ break;
+ case T_DropCmdTrigStmt:
+ retval = _copyDropCmdTrigStmt(from);
+ break;
case T_CreatePLangStmt:
retval = _copyCreatePLangStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f3a34a1..c85b5a7 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1837,6 +1837,28 @@ _equalDropPropertyStmt(DropPropertyStmt *a, DropPropertyStmt *b)
}
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
_equalCreatePLangStmt(CreatePLangStmt *a, CreatePLangStmt *b)
{
COMPARE_SCALAR_FIELD(replace);
@@ -2949,6 +2971,12 @@ equal(void *a, void *b)
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_CreatePLangStmt:
retval = _equalCreatePLangStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f7d39ed..7bd3498 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1943,6 +1943,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");
@@ -1959,6 +2090,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");
@@ -1970,6 +2126,17 @@ _outCreateForeignTableStmt(StringInfo str, CreateForeignTableStmt *node)
}
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");
@@ -1993,6 +2160,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");
@@ -2002,6 +2196,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");
@@ -2063,6 +2346,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");
@@ -2093,6 +2393,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");
@@ -2250,6 +2560,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");
@@ -3033,24 +3354,99 @@ _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_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;
@@ -3075,6 +3471,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;
@@ -3138,6 +3537,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/parser/gram.y b/src/backend/parser/gram.y
index c135465..052791d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -208,12 +208,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 +268,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
%type <str> copy_file_name
database_name access_method_clause access_method attr_name
@@ -496,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
@@ -727,6 +728,7 @@ stmt :
| CreateStmt
| CreateTableSpaceStmt
| CreateTrigStmt
+ | CreateCmdTrigStmt
| CreateRoleStmt
| CreateUserStmt
| CreateUserMappingStmt
@@ -750,6 +752,7 @@ stmt :
| DropStmt
| DropTableSpaceStmt
| DropTrigStmt
+ | DropCmdTrigStmt
| DropRoleStmt
| DropUserStmt
| DropUserMappingStmt
@@ -4219,6 +4222,65 @@ 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;
+ }
+ ;
+
+/*****************************************************************************
+ *
+ * QUERIES :
* CREATE ASSERTION ...
* DROP ASSERTION ...
*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5b06333..ec863c9 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,7 @@ check_xact_readonly(Node *parsetree)
case T_CommentStmt:
case T_DefineStmt:
case T_CreateCastStmt:
+ case T_CreateCmdTrigStmt:
case T_CreateConversionStmt:
case T_CreatedbStmt:
case T_CreateDomainStmt:
@@ -329,8 +331,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
@@ -342,6 +353,8 @@ ProcessUtility(Node *parsetree,
else
standard_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag);
+
+ ExecAfterCommandTriggers(parsetree, commandTag);
}
void
@@ -1103,6 +1116,14 @@ standard_ProcessUtility(Node *parsetree,
}
break;
+ case T_CreateCmdTrigStmt:
+ (void) CreateCmdTrigger((CreateCmdTrigStmt *) parsetree, queryString);
+ break;
+
+ case T_DropCmdTrigStmt:
+ DropCmdTrigger((DropCmdTrigStmt *) parsetree);
+ break;
+
case T_CreatePLangStmt:
CreateProceduralLanguage((CreatePLangStmt *) parsetree);
break;
@@ -2016,6 +2037,14 @@ CreateCommandTag(Node *parsetree)
}
break;
+ case T_CreateCmdTrigStmt:
+ tag = "CREATE COMMAND TRIGGER";
+ break;
+
+ case T_DropCmdTrigStmt:
+ tag = "DROP COMMAND TRIGGER";
+ break;
+
case T_CreatePLangStmt:
tag = "CREATE LANGUAGE";
break;
@@ -2233,6 +2262,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;
}
@@ -2537,6 +2573,14 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
+ case T_CreateCmdTrigStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
+ case T_DropCmdTrigStmt:
+ 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..5324882 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,613 @@ 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); */
+ cmd->nodestr = NULL;
+
+ /*
+ * 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 0bf2d4d..7c1a81c 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..cc21d63
--- /dev/null
+++ b/src/include/commands/cmdtrigger.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 *trigname, const char *name, bool missing_ok);
+
+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 64eeb73..f8db96a 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -185,4 +185,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 824d8b5..7764fa8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -365,6 +365,8 @@ typedef enum NodeTag
T_CreateExtensionStmt,
T_AlterExtensionStmt,
T_AlterExtensionContentsStmt,
+ T_CreateCmdTrigStmt,
+ T_DropCmdTrigStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index af6565e..dd59b27 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,
@@ -1739,6 +1740,20 @@ 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;
+
+/* ----------------------
* Create/Drop PROCEDURAL LANGUAGE Statements
* ----------------------
*/
@@ -1945,6 +1960,19 @@ typedef struct DropPropertyStmt
} 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 38c88d9..c5f1392 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
@@ -159,7 +160,7 @@ SELECT relname, relhasindex
timetz_tbl | f
tinterval_tbl | f
varchar_tbl | f
-(148 rows)
+(149 rows)
--
-- another sanity check: every system catalog that has OIDs should have
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;
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
As proposed by Jan Wieck on hackers and discussed in Ottawa at the
Clusters Hackers Meeting, the most (only?) workable way to ever have DDL
triggers is to have "command triggers" instead.
[...]
The v1 patch attached contains implementation for some commands only.
Here's a v2 which is only about cleaning up the merge from master. The
recent DROP rework conflicted badly with the command triggers patch,
quite obviously. I didn't reimplement DROP COMMAND TRIGGER in terms of
the new facilities yet, though.
So, any comment on the approach before I complete the rewriting of the
commands, the out/read funcs, and add more commands to the trigger
support code?https://github.com/dimitri/postgres/compare/master...command_triggers
FWIW (scheduling mainly), I don't think I'm going to spend more time on
this work until I get some comments, because all that remains to be done
is about building on top of what I've already been doing here.
Well, I'd like to implement command triggers on replica too, but baring
psql support and a bunch of commands not there yet, that's about it as
far as features go.
Oh, and have expressions rewriting from the parsetree (default, check)
actually work (rather than segfault) is still a TODO too.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
command-trigger.v2.patchtext/x-patchDownload
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;
Hi Dimitri, Hi all,
On Tuesday, November 08, 2011 06:47:13 PM Dimitri Fontaine wrote:
As proposed by Jan Wieck on hackers and discussed in Ottawa at the
Clusters Hackers Meeting, the most (only?) workable way to ever have DDL
triggers is to have "command triggers" instead.
Rather than talking about catalogs and the like, it's all about firing a
registered user-defined function before or after processing the utility
command, or instead of processing it. This naturally involves a new
catalog (pg_cmdtrigger) and some new subcommands of CREATE and DROP
TRIGGER, and some support code for calling the function at the right
time.
Great ;)
fwiw I think thats the way forward as well.
The patch obviously isn't thought to be commitable yet, so I am not going to
do a very detailed code level review. Attached is a patch with low level
targeted comments and such I noticed while reading it.
At this state the important things are highlevel so I will try to concentrate
on those:
== the trigger function ==
I don't like the set of options parsed to the trigger functions very much. To
cite an example of yours those currently are:
* cmd_string text
* cmd_nodestring text
* schemaname text
* relname text
I think at least a
* command_tag text
is missing. Sure, you can disambiguate it by creating a function for every
command type but that seems pointlessly complex for many applications. And
adding it should be trivial as its already tracked ;)
Currently the examples refer to relname as relname which I dislike as that
seems to be too restricted to one use case. The code is already doing it
correctly (as objectname) though ;)
Why is there explicit documentation of not checking the arguments of said
trigger function? That seems to be a bit strange to me.
schemaname currently is mightily unusable because whether its sent or not
depends wether the table was created with a schemaname specified or not. That
doesn't seem to be a good approach.
That brings me to the next point:
== normalization of statements ==
Currently the normalization is a bit confusing. E.g. for:
CREATE SCHEMA barf;
SET search_path = barf;
CREATE TYPE bart AS ENUM ('a', 'b');
CREATE TABLE bar(a int, b bigint, c serial, d text, e varchar, "F" text, g
bart, h int4);
one gets
CREATE TABLE bar (a pg_catalog.int4,b pg_catalog.int8,c serial,d text,e
pg_catalog.varchar,F text,g bart,h int4);
Which points out two problems:
* inconsistent schema prefixing
* no quoting
Imo the output should fully schema qualify anything including operators. Yes,
thats rather ugly but anything else seems to be too likely to lead to
problems.
As an alternative it would be possible to pass the current search path but
that doesn't seem to the best approach to me;
Currently CHECK() constraints are not decodable, but inside those the same
quoting/qualifying rules should apply.
Then there is nice stuff like CREATE TABLE .... (LIKE ...);
What shall that return in future? I vote for showing it as the plain CREATE
TABLE where all columns are specified.
That touches another related topic. Dim's work in adding support for utility
cmd support in nodeToString and friends possibly will make the code somewhat
command trigger specific. Is there any other usage we envision?
== grammar ==
* multiple actions:
I think it would sensible to allow multiple actions on which to trigger to be
specified just as you can for normal triggers. I also would like an ALL option
* options:
Currently the grammar allows options to be passed to command triggers. Do we
want to keep that? If yes, with the same arcane set of datatypes as in data
triggers?
If yes it needs to be wired up.
* schema qualification:
An option to add triggers only to a specific schema would be rather neat for
many scenarios.
I am not totally sure if its worth the hassle of defining what happens in the
edge cases of e.g. moving from one into another schema though.
== locking ==
Currently there is no locking strategy at all. Which e.g. means that there is
no protection against two sessions changing stuff concurrently or such.
Was that just left out - which is ok - or did you miss that?
I think it would be ok to just always grab row level locks there.
== permissions ==
Command triggers should only be allowed for the database owner.
== recursion ==
I contrast to data triggers command triggers cannot change what is done. They
can either prevent it from running or not. Which imo is fine.
The question I have is whether it would be a good idea to make it easy to
prevent recursion.... Especially if the triggers wont be per schema.
== calling the trigger ==
Imo the current callsite in ProcessUtility isn't that good. I think it would
make more sense moving it into standard_ProcessUtility. It should be *after*
the check_xact_readonly check in there in my opinion.
I am also don't think its a good idea to run the
ExecBeforeOrInsteadOfCommandTriggers stuff there because thats allso the path
that COMMIT/BEGIN and other stuff take. Those are pretty "fast path".
I suggest making two switches in standard_ProcessUtility, one for the non-
command trigger stuff which returns on success and one after that. Only after
the first triggers are checked.
I wonder whether its the right choice to run triggers on untransformed input.
I.e. CREATE TABLE ... (LIKE ...) seems to only make sense to me after
transforming.
Also youre very repeatedly transforming the parstree into a string. It would
be better doing that only once instead of every trigger...
Ok, thats the first round of high level stuff...
Cool Work!
Some lower level annotations:
* many functions should be static but are not. Especially in cmdtrigger.c
* Either tgenable's datatype or its readfunc is wrong (watch the compiler ;))
* ruleutils.c:
* generic routine for IF EXISTS, CASCADE, ...
Greetings,
Andres
Attachments:
0001-Fix-dependency-recording-for-command-triggers.patchtext/x-patch; charset=UTF-8; name=0001-Fix-dependency-recording-for-command-triggers.patchDownload
From 99a67fa63e5bcec6631c00253653717688bbdfde Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Thu, 1 Dec 2011 14:18:55 +0100
Subject: [PATCH 1/3] Fix dependency recording for command triggers
* the oid is only defined after heap_insert
* use the correct relationship as classid
---
src/backend/commands/cmdtrigger.c | 11 ++++-------
1 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/backend/commands/cmdtrigger.c b/src/backend/commands/cmdtrigger.c
index 5a4d370..c6cda29 100644
--- a/src/backend/commands/cmdtrigger.c
+++ b/src/backend/commands/cmdtrigger.c
@@ -157,16 +157,13 @@ CreateCmdTrigger(CreateCmdTrigStmt *stmt, const char *queryString)
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);
+ /* remember oid for record dependencies */
+ trigoid = HeapTupleGetOid(tuple);
+
heap_freetuple(tuple);
heap_close(tgrel, RowExclusiveLock);
@@ -174,7 +171,7 @@ CreateCmdTrigger(CreateCmdTrigStmt *stmt, const char *queryString)
* Record dependencies for trigger. Always place a normal dependency on
* the function.
*/
- myself.classId = TriggerRelationId;
+ myself.classId = CmdTriggerRelationId;
myself.objectId = trigoid;
myself.objectSubId = 0;
--
1.7.6.409.ge7a85.dirty
0002-Fix-file-headers-of-new-cmdtrigger.c-file.patchtext/x-patch; charset=UTF-8; name=0002-Fix-file-headers-of-new-cmdtrigger.c-file.patchDownload
From f48b887b5090823a621ff896b01412b044f02f64 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Thu, 1 Dec 2011 14:20:17 +0100
Subject: [PATCH 2/3] Fix file headers of new cmdtrigger.c file
---
src/backend/commands/cmdtrigger.c | 9 ++++-----
1 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/backend/commands/cmdtrigger.c b/src/backend/commands/cmdtrigger.c
index c6cda29..733c798 100644
--- a/src/backend/commands/cmdtrigger.c
+++ b/src/backend/commands/cmdtrigger.c
@@ -1,13 +1,12 @@
/*-------------------------------------------------------------------------
*
- * trigger.c
- * PostgreSQL TRIGGERs support code.
+ * cmdtrigger.c
+ * PostgreSQL COMMAND TRIGGER support code.
*
- * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
+ * Portions Copyright (c) 2011, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * src/backend/commands/trigger.c
+ * src/backend/commands/cmdtrigger.c
*
*-------------------------------------------------------------------------
*/
--
1.7.6.409.ge7a85.dirty
0003-Add-some-review-comments.patchtext/x-patch; charset=UTF-8; name=0003-Add-some-review-comments.patchDownload
From 016aedc955ccd4cb2eb6cdead6957bf7cb58cee8 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Thu, 1 Dec 2011 17:30:48 +0100
Subject: [PATCH 3/3] Add some review comments
---
src/backend/catalog/dependency.c | 2 +-
src/backend/commands/cmdtrigger.c | 25 +++++++++++++++++++------
src/backend/nodes/readfuncs.c | 2 +-
src/backend/utils/adt/ruleutils.c | 11 +++++++++--
4 files changed, 30 insertions(+), 10 deletions(-)
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 42701f3..5e53e08 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2843,7 +2843,7 @@ getObjectDescription(const ObjectAddress *object)
trig = (Form_pg_cmdtrigger) GETSTRUCT(tup);
- appendStringInfo(&buffer, _("trigger %s on %s"),
+ appendStringInfo(&buffer, _("command trigger %s on %s"),
NameStr(trig->ctgname),
NameStr(trig->ctgcommand));
diff --git a/src/backend/commands/cmdtrigger.c b/src/backend/commands/cmdtrigger.c
index 733c798..3780521 100644
--- a/src/backend/commands/cmdtrigger.c
+++ b/src/backend/commands/cmdtrigger.c
@@ -129,6 +129,8 @@ CreateCmdTrigger(CreateCmdTrigStmt *stmt, const char *queryString)
errdetail("Commands cannot have both AFTER and INSTEAD OF triggers.")));
break;
}
+ default:
+ elog(ERROR, "unknown trigger type for COMMAND TRIGGER");
}
if (ctgtype == CMD_TRIGGER_FIRED_BEFORE && funcrettype != BOOLOID)
@@ -143,6 +145,8 @@ CreateCmdTrigger(CreateCmdTrigStmt *stmt, const char *queryString)
errmsg("function \"%s\" must return type \"void\"",
NameListToString(stmt->funcname))));
+ //FIXME: check argument types?
+
/*
* Build the new pg_trigger tuple.
*/
@@ -259,8 +263,8 @@ AlterCmdTrigger(AlterCmdTrigStmt *stmt)
Form_pg_cmdtrigger cmdForm;
char tgenabled = pstrdup(stmt->tgenabled)[0]; /* works with gram.y */
- tgrel = heap_open(CmdTriggerRelationId, AccessShareLock);
-
+ tgrel = heap_open(CmdTriggerRelationId, AccessShareLock);//FIXME: wrong lock level?
+ //FIXME: need a row level lock here
ScanKeyInit(&skey[0],
Anum_pg_cmdtrigger_ctgcommand,
BTEqualStrategyNumber, F_NAMEEQ,
@@ -315,6 +319,7 @@ RenameCmdTrigger(List *name, const char *trigname, const char *newname)
rel = heap_open(CmdTriggerRelationId, RowExclusiveLock);
+ //FIXME: need a row level lock here
/* newname must be available */
check_cmdtrigger_name(command, newname, rel);
@@ -432,7 +437,7 @@ check_cmdtrigger_name(const char *command, const char *trigname, Relation tgrel)
tuple = systable_getnext(tgscan);
- elog(NOTICE, "check_cmdtrigger_name(%s, %s)", command, trigname);
+ elog(DEBUG1, "check_cmdtrigger_name(%s, %s)", command, trigname);
if (HeapTupleIsValid(tuple))
ereport(ERROR,
@@ -464,7 +469,8 @@ check_cmdtrigger_name(const char *command, const char *trigname, Relation tgrel)
* nodeToString() output.
*
*/
-
+//FIXME: Return a List here.
+//FIXME: add abort-after-first for CreateCmdTrigger?
static RegProcedure *
list_triggers_for_command(const char *command, char type)
{
@@ -500,7 +506,7 @@ list_triggers_for_command(const char *command, char type)
{
if (count == size)
{
- size += 10;
+ size += 10;//FIXME: WTF?
procs = (Oid *)repalloc(procs, size);
}
procs[count++] = cmd->ctgfoid;
@@ -560,7 +566,7 @@ call_cmdtrigger_procedure(RegProcedure proc, CommandContext cmd,
* 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
+ * execution, hence this API where we return the number of InsteadOf trigger
* procedures we fired.
*/
int
@@ -591,6 +597,8 @@ ExecBeforeOrInsteadOfCommandTriggers(Node *parsetree, const char *cmdtag)
return nb;
}
+//FIXME: This looks like it should be static
+//FIXME: why not return the number of calls here as well?
bool
ExecBeforeCommandTriggers(Node *parsetree, const char *cmdtag,
MemoryContext per_command_context)
@@ -602,6 +610,7 @@ ExecBeforeCommandTriggers(Node *parsetree, const char *cmdtag,
RegProcedure proc;
int cur= 0;
bool cont = true;
+ //FIXME: add assert for IsA(parsetree, parsetree)
/*
* Do the functions evaluation in a per-command memory context, so that
@@ -631,6 +640,7 @@ ExecBeforeCommandTriggers(Node *parsetree, const char *cmdtag,
/*
* return the count of triggers we fired
*/
+//FIXME: This looks like it should be static
int
ExecInsteadOfCommandTriggers(Node *parsetree, const char *cmdtag,
MemoryContext per_command_context)
@@ -641,6 +651,7 @@ ExecInsteadOfCommandTriggers(Node *parsetree, const char *cmdtag,
CMD_TRIGGER_FIRED_INSTEAD);
RegProcedure proc;
int cur = 0;
+ //FIXME: add assert for IsA(parsetree, parsetree)
/*
* Do the functions evaluation in a per-command memory context, so that
@@ -673,6 +684,8 @@ ExecAfterCommandTriggers(Node *parsetree, const char *cmdtag)
RegProcedure proc;
int cur = 0;
+ //FIXME: add assert for IsA(parsetree, parsetree)
+
/*
* Do the functions evaluation in a per-command memory context, so that
* leaked memory will be reclaimed once per command.
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 2b1a76d..1505683 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -384,6 +384,7 @@ _readConstraint(void)
token = pg_strtok(&length); /* skip :constraint */
token = pg_strtok(&length); /* get field value */
+ //FIXME: what is going here?
if (strncmp(token, "NULL", 4) == 0)
local_node->contype = CONSTR_NULL;
else if (strncmp(token, "NOT_NULL", 8) == 0)
@@ -1428,7 +1429,6 @@ _readRangeTblEntry(void)
READ_DONE();
}
-
/*
* parseNodeString
*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 83775bf..2a14281 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7332,6 +7332,9 @@ flatten_reloptions(Oid relid)
* Functions that ouputs a COMMAND given a Utility parsetree
*
* FIXME: First some tools that I couldn't find in the sources.
+ * FIXME: replace conversion to rangevar and back to string with
+ * NameListToQuotedString
+ * FIXME: missing quoting
*/
static char *
RangeVarToString(RangeVar *r)
@@ -7506,8 +7509,8 @@ _rwViewStmt(CommandContext cmd, ViewStmt *node)
viewParse = parse_analyze((Node *) copyObject(node->query),
"(unavailable source text)", NULL, 0);
- appendStringInfo(&buf, "CREATE %s %s AS ",
- node->replace? "OR REPLACE VIEW": "VIEW",
+ appendStringInfo(&buf, "CREATE %sVIEW %s AS ",
+ node->replace? "OR REPLACE": "",
RangeVarToString(node->view));
get_query_def(viewParse, &buf, NIL, NULL, 0, 1);
@@ -7526,6 +7529,7 @@ _rwColQualList(StringInfo buf, List *constraints, const char *relname)
foreach(lc, constraints)
{
Constraint *c = (Constraint *) lfirst(lc);
+ Assert(IsA(c, Constraint));
if (c->conname != NULL)
appendStringInfo(buf, " CONSTRAINT %s", c->conname);
@@ -7897,10 +7901,13 @@ _rwAlterTableStmt(CommandContext cmd, AlterTableStmt *node)
* 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.
+ *
+ * FIXME: just add a cast + assert at the callsite ^^^?
*/
void
pg_get_cmddef(CommandContext cmd, void *parsetree)
{
+ //FIXME: at least add an assert for type
cmd->nodestr = nodeToString(parsetree);
/* elog(NOTICE, "nodeToString: %s", cmd->nodestr); */
stringToNode(cmd->nodestr);
--
1.7.6.409.ge7a85.dirty
Hi,
On Tuesday, November 08, 2011 06:47:13 PM Dimitri Fontaine wrote:
exception to that rule would be SELECT INTO and CREATE TABLE AS SELECT,
and my proposal would be to add specific call sites to the functions
I've provided in the attached patch rather than try to contort them into
being a good citizen as a utility command.
I would very much prefer to make them utility commands and rip out the
executor support for that.
It doesn't look that complicated and would allow us to get rid of the
partially duplicated defineRelation and related stuff from the executor where
its violating my aestetic feelings ;)
Is there something making that especially hard that I overlooked? The
infrastructure for doing so seems to be there.
Andres
Andres Freund <andres@anarazel.de> writes:
On Tuesday, November 08, 2011 06:47:13 PM Dimitri Fontaine wrote:
exception to that rule would be SELECT INTO and CREATE TABLE AS SELECT,
and my proposal would be to add specific call sites to the functions
I've provided in the attached patch rather than try to contort them into
being a good citizen as a utility command.
I would very much prefer to make them utility commands and rip out the
executor support for that.
Is there something making that especially hard that I overlooked? The
infrastructure for doing so seems to be there.
Well, I think the main problem is going to be shunting the query down
the right parsing track (SELECT versus utility-statement) early enough.
Making this work cleanly would be a bigger deal than I think you're
thinking.
But yeah, it would be nicer if the executor didn't have to worry about
this. +1 if you're willing to take on the work.
regards, tom lane
On Thursday, December 01, 2011 07:21:25 PM Tom Lane wrote:
Andres Freund <andres@anarazel.de> writes:
On Tuesday, November 08, 2011 06:47:13 PM Dimitri Fontaine wrote:
exception to that rule would be SELECT INTO and CREATE TABLE AS SELECT,
and my proposal would be to add specific call sites to the functions
I've provided in the attached patch rather than try to contort them into
being a good citizen as a utility command.I would very much prefer to make them utility commands and rip out the
executor support for that.
Is there something making that especially hard that I overlooked? The
infrastructure for doing so seems to be there.Well, I think the main problem is going to be shunting the query down
the right parsing track (SELECT versus utility-statement) early enough.
Making this work cleanly would be a bigger deal than I think you're
thinking.
Yes. I forgot how ugly SELECT INTO is...
But yeah, it would be nicer if the executor didn't have to worry about
this. +1 if you're willing to take on the work.
I won't promise anything but I will play around with the grammar and see if I
find a nice enough way.
Andres
On Thursday, December 01, 2011 07:21:25 PM Tom Lane wrote:
Well, I think the main problem is going to be shunting the query down
the right parsing track (SELECT versus utility-statement) early enough.
Making this work cleanly would be a bigger deal than I think you're
thinking.
Obviously that depends on the definition of clean...
Changing the grammar to make that explicit seems to involve a bit too many
changes on a first glance. The cheap way out seems to be to make the decision
in analyze.c:transformQuery.
Would that be an acceptable way forward?
Andres
On Sat, Nov 26, 2011 at 7:20 PM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
FWIW (scheduling mainly), I don't think I'm going to spend more time on
this work until I get some comments, because all that remains to be done
is about building on top of what I've already been doing here.
+1 overall
One of the main use cases for me is the ability to
* prevent all commands
* prevent extension commands - to maintain stable execution environment
Those are especially important because in 9.2 DDL commands will cause
additional locking overheads, so preventing DDL will be essential to
keeping performance stable in high txn rate databases.
So I'd like to see a few more triggers that work out of the box for
those cases (perhaps wrapped by a function?). It would also allow a
more useful man page example of how to use this.
On the patch, header comment for cmdtrigger.c needs updating.
Comments for DropCmdTrigger etc look like you forgot to update them
after cut and paste.
Comments say "For any given command tag, you can have either Before
and After triggers, or
Instead Of triggers, not both.", which seems like a great thing to
put in the docs.
Looks good.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Andres Freund <andres@anarazel.de> writes:
On Thursday, December 01, 2011 07:21:25 PM Tom Lane wrote:
Making this work cleanly would be a bigger deal than I think you're
thinking.
Obviously that depends on the definition of clean...
Changing the grammar to make that explicit seems to involve a bit too many
changes on a first glance. The cheap way out seems to be to make the decision
in analyze.c:transformQuery.
Would that be an acceptable way forward?
ITYM transformStmt, but yeah, somewhere around there is probably reasonable.
The way I'm imagining this would work is that IntoClause disappears from
Query altogether: analyze.c would build a utility statement
CreateTableAs, pull the IntoClause out of the SelectStmt structure and
put it into the utility statement, and then proceed much as we do for
ExplainStmt (and for the same reasons).
regards, tom lane
Hi,
Hm. I just noticed a relatively big hole in the patch: The handling of
deletion of dependent objects currently is nonexistant because they don't go
through ProcessUtility...
Currently thinking what the best way to nicely implement that is. For other
commands the original command string is passed - that obviously won't work for
that case...
Andres
Hi all,
There is also the point about how permission checks on the actual commands (in
comparison of modifying command triggers) and such are handled:
BEFORE and INSTEAD will currently be called independently of the fact whether
the user is actually allowed to do said action (which is inconsistent with
data triggers) and indepentent of whether the object they concern exists.
I wonder if anybody considers that a problem?
Andres
Hi,
First thing first: thank you Andres for a great review, I do appreciate
it. Please find attached a newer version of the patch. The github
repository is also updated.
Andres Freund <andres@anarazel.de> writes:
I think at least a
* command_tag text
Added.
Why is there explicit documentation of not checking the arguments of said
trigger function? That seems to be a bit strange to me.
The code is searching for a function with the given name and 5 text
arguments, whatever you say in the command. That could be changed later
on if there's a need.
schemaname currently is mightily unusable because whether its sent or not
depends wether the table was created with a schemaname specified or not. That
doesn't seem to be a good approach.
I'm not sure about that, we're spiting out what the user entered.
Imo the output should fully schema qualify anything including operators. Yes,
thats rather ugly but anything else seems to be too likely to lead to
problems.
Will look into qualifying names.
As an alternative it would be possible to pass the current search path but
that doesn't seem to the best approach to me;
The trigger runs with the same search_path settings as the command, right?
Then there is nice stuff like CREATE TABLE .... (LIKE ...);
What shall that return in future? I vote for showing it as the plain CREATE
TABLE where all columns are specified.
I don't think so. Think about the main use cases for this patch,
auditing and replication. Both cases you want to deal with what the
user said, not what the system understood.
I think it would sensible to allow multiple actions on which to trigger to be
specified just as you can for normal triggers. I also would like an ALL option
Both are now implemented.
When dealing with an ANY command trigger, your procedure can get fired
on a command for which we don't have internal support for back parsing
etc, so you only get the command tag not null. I think that's the way to
go here, as I don't want to think we need to implement full support for
command triggers on ALTER OPERATOR FAMILY from the get go.
Currently the grammar allows options to be passed to command triggers. Do we
want to keep that? If yes, with the same arcane set of datatypes as in data
triggers?
If yes it needs to be wired up.
In fact it's not the case, you always get called with the same 5
arguments, all nullable now that we have ANY COMMAND support.
* schema qualification:
An option to add triggers only to a specific schema would be rather neat for
many scenarios.
I am not totally sure if its worth the hassle of defining what happens in the
edge cases of e.g. moving from one into another schema though.
I had written down support for a WHEN clause in command triggers, but
the exact design has yet to be worked out. I'd like to have this
facility but I'd like it even more if that could happen in a later
patch.
Currently there is no locking strategy at all. Which e.g. means that there is
no protection against two sessions changing stuff concurrently or such.Was that just left out - which is ok - or did you miss that?
I left out the locking as of now. I tried to fix some of it in the new
patch though, but I will need to spend more time on that down the road.
Command triggers should only be allowed for the database owner.
Yes, that needs to happen soon, along with pg_dump and psql support.
I contrast to data triggers command triggers cannot change what is done. They
can either prevent it from running or not. Which imo is fine.
The question I have is whether it would be a good idea to make it easy to
prevent recursion.... Especially if the triggers wont be per schema.
You already have
ATER TRIGGER foo ON COMMAND create table DISABLE;
that you can use from the trigger procedure itself. Of course, locking
is an issue if you want to go parallel on running commands with
recursive triggers attached. I think we might be able to skip solving
that problem in the first run.
Imo the current callsite in ProcessUtility isn't that good. I think it would
make more sense moving it into standard_ProcessUtility. It should be *after*
the check_xact_readonly check in there in my opinion.
Makes sense, will fix in the next revision.
I am also don't think its a good idea to run the
ExecBeforeOrInsteadOfCommandTriggers stuff there because thats allso the path
that COMMIT/BEGIN and other stuff take. Those are pretty "fast path".I suggest making two switches in standard_ProcessUtility, one for the non-
command trigger stuff which returns on success and one after that. Only after
the first triggers are checked.
Agreed.
Also youre very repeatedly transforming the parstree into a string. It would
be better doing that only once instead of every trigger...
It was only done once per before and instead of triggers (you can't have
both on the same command), and another time for any and all after
triggers, meaning at most twice. It's now down to only once, at most.
* many functions should be static but are not. Especially in cmdtrigger.c
Fixed.
* Either tgenable's datatype or its readfunc is wrong (watch the compiler ;))
* ruleutils.c:
* generic routine for IF EXISTS, CASCADE, ...
Will have to wait until next revision.
Thanks you again for a great review, regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
command-trigger.v3.patch.gzapplication/octet-streamDownload
��U�Ncommand-trigger.v3.patch �<kw�6���_�z�D�h=l���ge�v��H^IN�'���HH��"U>�����wf ��DY��n{��il���13 ���)����1�jN`�����^-���r+��8tg3V��M5l����Y'���=�V��S~<��F�~|t�������v*��c���������8`�sp���=0[#��c�~o8�����F����9`g!_x�������YQ���Mk�zc�����o��YMs�����������V�b��������y l�}�����-�w3���������4�m8^Gm:�d+�%s���*:;l�"�d��_��y����.a����V��/���7�^gd;��-�Ya{�����;[��V����x����`V����&�>���:�Ix R��9�����Pa`�V���y!��V�VA���u�k S<���6,�Ua �(�
�������R����)ZZ��2ZCg�������]�Gm@�*���Ffo����AZH�-�`�3�=U
�� �n���h�Y�yl�� 7r�1<vxd��Bx �$���|�������y#=��v&�a�,��{��"������]�C� �����������h�mw�BSL�!��4R�G��gn'1_��-� ��0%h��l���32�����U��� l����=�
�`0C.��C����fRK!O����'`Z�&dXRepR�V�K������i���)�D���������\?����i�v�������`���Gy���(f!���YM����o��v������_Z^lQ�����>Jy��; ���GGE!�U�-ac�1 @�Ha����T�A�����.#f��X�I�O���N�{��Z
L�q
\�
�����h���n���.����7
=8���?�}�B���q�T��Y��<��<?��7�S%���w]G����5IZ��+�4�������Z�*ub�Y��=L���ak�
���+(�PE+[�����x��V9<>1�0��K�0C��p?J/���;+F�:����@���������2�l3^.x��RNg�s��f�����]��w"%��e.�!\�|�2�B]XQ��/
�T.K�*
��U�$�L�f��X�?��V�z��&�|��V�"W�������A�+�nAW��F��m1)�6�jmcU�R�e:��D ��i�������_ ��#��x��n��&KvD�,��]���������J����0�>U��v]���rn��[���T��� ZaK��s+v�K��9�� }�AV��?'n�b�2 �Xg�D@�>��8������{��x���������������5�M�^�h� ��<���������s_*F�l�G��W���� 6wgwB��)yi�M��6�:�*���"�n��:�]�[D5�3�=��MrC"�jb���jr����x��aS�WT���sQD�b���l�`����J��q���^c�$����gj�5�� |��9��l>���q/=P�s0"���P+S^=?��^�'�_���D��������V�d���� �ch��eZ��c��K�B��7�>��?��Y����{i���.4��9����E��u�wu��2��[�����5d�~�S�0����v/!
�����;�c��W+?�&��{n�B���9������!w�������]��������g���+��sC��Z�#t�e�zhR� �U��e�hp�]�����5�gE�S�����gek^/�g�0QE+�*�@��<����qh4�
���������� �3s�S��*.*���1��<�����#���6+�m������� ��%D^�.0�"S�`L-�xY�����b�������RoF=�����y��K�N,����2�����O���!?<���-e�u0���)S��~7^��d�` �<=YV���KgZ�IT����C�6�GC�� �W�n[&Q0��'�vk�nu�h;0��L{�>�T~o�������X���Pypj�B\����YpO�S�hh�����O���0�w�U'>u}WkF�Vk�lB �����d����;�{3(�>E
�>O��+�Z�;+����_�����cPe�-�SQ�b�S(�$06�y�("���|9Jj�
l�[�S�X�G�;5��Y����N���i5N�j���8G�q�C?@���P����
�F�����Y�/���!��}[�gD���9�@7�#��o{��S���VZ������*�u��
hZ��w����zR�c��}3Bx�p������R���k����u�l�g����q�Y����n��B���H@3��fV� �>��p{����?K�J3���,�fW~�l��Ft�~JD��H.�#���R��0JL#8���Rz�;�T�����h�/4$
����
����sf�H��30v>�Z�Dw���-O�Vk��o/��m����������T�M�<�����&d������3�4M���7i
lW��-aw� ��M�B��;��^Ma6B<��<b�x�����wE&@Q]��!��8��M��:��]�xn-��y^�,����x���ng�7��S����� :�F�������f�$���P:�Y<z�������n
����
t����C��d.�u�R�#���u�z��9x���L�n���'i�na��M���U���Sr���m��1/[�k�_2U^��-�c���W��o����>�~�Q����5FL��u�o��)C7'�p�#��d<1��8!�"�B�`8 ?��[_����Q*�V���+�*
(p���Xv����/�]s�ra�01�~E
h���� �`��i���i��C�
M3
��U[hN�)���J��
��rn�����3�!_hujt$��?�uF)H��{Y?!�^6N��F�:-n~�\YZ�,�_�wI�TB���
y������/;��X��/�5>��e��8���e��Z����Kq��m`��q�MS)�[JW�p�1��E7X����;+����q/�!Q�B ]~F��?�~�R�Z����{ n������sbyCp�1�-e7����_���x\I�����$�DE�HN������H64�o�������g���� 5�{����C[+Y��
��s\(d��)+�����^&����8l6es0�����t�
��8�2�������[V�2A��u��cW�������I����pcH���?
��& V�
6.���A%����=�w�Q! ��������G���������`�Z�&��� ��h�U3�
*9KG�K�m�x7�Cn3����)�w ��� XiE���:m2�K8��q��(j�_�ZV�z|2=�Z����*�����H���`c����W�9�hr��4�Q���5�D���G���������%�,�M�����:�~P?���Y�)<�M��s��=�|�;8�D������q�F����p��wh~;��gl�VJ��F�v�O]=T��u������^�����*m����K{=��'��T�V�n6J
� X��
R ��oQ2q�7���;�)���1�Z��;L��(��!�������`by���/�����p�UfA��_����p|���
z$�TV0�u��+AhVJ/��������|
�)�R���a%��Y��v���'0
9�7����,N ���L�S����V�w�9���X�+�F���j-�fo�7�-��><hU��z�9>9x�R+ ���*},��d�����S�0���#{��=��`k;Q�Td1����"�\��%f�0�����@�^�4�8�]]�8]+uZ��Ekh��H��RYy\����T~���1;x1��<