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 name [ WHEN ( condition ) ] EXECUTE PROCEDURE function_name ( arguments ) +CREATE TRIGGER name { BEFORE | AFTER | INSTEAD OF } COMMAND command + EXECUTE PROCEDURE function_name ( arguments ) + where event can be one of: INSERT UPDATE [ OF column_name [, ... ] ] DELETE TRUNCATE + +and where command can be one of: + + CREATE TABLE + ALTER TABLE + DROP TABLE + CREATE VIEW + DROP VIEW + CREATE EXTENSION + DROP EXTENSION + @@ -42,10 +56,20 @@ CREATE [ CONSTRAINT ] TRIGGER name Description - CREATE TRIGGER creates a new trigger. The - trigger will be associated with the specified table or view and will - execute the specified function function_name when certain events occur. + CREATE TRIGGER creates a new trigger. The trigger will + be associated with the specified table, view or command and will execute + the specified + function function_name when + certain events occur. + + + + The command trigger can be specified to fire before or after the command + is executed, or instead of executing the command. A before trigger's + function must return a boolean, returning False allows + the procedure to prevent the command execution. AFTER and INSTEAD OF + triggers return value is not used, such trigger's function must then + return void. @@ -251,6 +275,15 @@ UPDATE OF column_name1 [, column_name2 + command + + + The tag of the command the trigger is for. + + + + + referenced_table_name @@ -334,7 +367,23 @@ UPDATE OF column_name1 [, column_name2 A user-supplied function that is declared as taking no arguments and returning type trigger, which is executed when - the trigger fires. + the trigger fires, for table and view triggers. + + + In the case of a BEFORE COMMAND trigger, the user-supplied function + must be declared as taking 4 text arguments and returning a boolean. + In the case of an AFTER COMMAND trigger or an INSTEAD OF trigger, the + user-supplied function must be declare as taking 4 text arguments and + returning void. + + + The command trigger function is called with the + parameters command string (normalized command + string rewritten by PostgreSQL), command node + string (the command syntax tree, in its text form, is easier + to deal with programmaticaly for more complex inquiries about the + command), schemaname (can be null) + and object name. @@ -352,6 +401,10 @@ UPDATE OF column_name1 [, column_name2 + + Command triggers pay no attention to + the arguments. + @@ -469,6 +522,35 @@ CREATE TRIGGER view_insert FOR EACH ROW EXECUTE PROCEDURE view_insert_row(); + + Execute the function enforce_local_style each time + a CREATE TABLE command is run: + + +CREATE OR REPLACE FUNCTION enforce_local_style + ( + IN cmd_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(); + @@ -531,6 +613,11 @@ CREATE TRIGGER view_insert + The ability to run triggers on commands is PostgreSQL + extension of the SQL standard. + + + The ability to specify multiple actions for a single trigger using OR is a PostgreSQL extension of the SQL standard. 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 DROP TRIGGER [ IF EXISTS ] name ON table [ CASCADE | RESTRICT ] +DROP TRIGGER [ IF EXISTS ] name ON COMMAND command [ CASCADE | RESTRICT ] + +where command can be one of: + + CREATE TABLE + ALTER TABLE + DROP TABLE + CREATE VIEW + DROP VIEW + CREATE EXTENSION + DROP EXTENSION + @@ -29,9 +41,10 @@ DROP TRIGGER [ IF EXISTS ] name ON Description - DROP TRIGGER removes an existing - trigger definition. To execute this command, the current - user must be the owner of the table for which the trigger is defined. + DROP TRIGGER removes an existing trigger definition. + To execute this command, the current user must be the owner of the table + for which the trigger is defined, or a database owner in case of a + command trigger. 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 TriggerEvents TriggerOneEvent %type TriggerFuncArg %type TriggerWhen +%type trigger_command %type 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 */ + 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;