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
From 99a67fa63e5bcec6631c00253653717688bbdfde Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@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

From f48b887b5090823a621ff896b01412b044f02f64 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@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

From 016aedc955ccd4cb2eb6cdead6957bf7cb58cee8 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@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

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to