diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index a157a244e4ef..c4e44eb3298b 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -88,7 +88,7 @@ class="parameter">referential_action ] [ ON UPDATE and like_option is: -{ INCLUDING | EXCLUDING } { COMMENTS | COMPRESSION | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL } +{ INCLUDING | EXCLUDING } { COMMENTS | COMPRESSION | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | TRIGGERS | ALL } and partition_bound_spec is: @@ -672,7 +672,7 @@ WITH ( MODULUS numeric_literal, REM INCLUDING COMMENTS - Comments for the copied columns, constraints, and indexes will be + Comments for the copied columns, constraints, indexes and triggers will be copied. The default behavior is to exclude comments, resulting in the copied columns and constraints in the new table having no comments. @@ -776,6 +776,15 @@ WITH ( MODULUS numeric_literal, REM + + INCLUDING TRIGGERS + + + Any non-internal triggers on the original table will be created on the new table. + + + + INCLUDING ALL diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 5d9db167e595..5c0df73c1c19 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -2039,10 +2039,12 @@ index_constraint_create(Relation heapRelation, trigger->deferrable = true; trigger->initdeferred = initdeferred; trigger->constrrel = NULL; + trigger->transformed = true; + trigger->trigcomment = NULL; (void) CreateTrigger(trigger, NULL, RelationGetRelid(heapRelation), InvalidOid, conOid, indexRelationId, InvalidOid, - InvalidOid, NULL, true, false); + InvalidOid, true, false); } /* diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 3aac459e483d..568f93f8d94a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -13826,10 +13826,12 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint, fk_trigger->deferrable = fkconstraint->deferrable; fk_trigger->initdeferred = fkconstraint->initdeferred; fk_trigger->constrrel = NULL; + fk_trigger->trigcomment = NULL; + fk_trigger->transformed = true; trigAddress = CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid, constraintOid, indexOid, InvalidOid, - parentTrigOid, NULL, true, false); + parentTrigOid, true, false); /* Make changes-so-far visible */ CommandCounterIncrement(); @@ -13871,6 +13873,8 @@ createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstr fk_trigger->whenClause = NULL; fk_trigger->transitionRels = NIL; fk_trigger->constrrel = NULL; + fk_trigger->trigcomment = NULL; + fk_trigger->transformed = true; switch (fkconstraint->fk_del_action) { @@ -13907,7 +13911,7 @@ createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstr trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid, indexOid, InvalidOid, - parentDelTrigger, NULL, true, false); + parentDelTrigger, true, false); if (deleteTrigOid) *deleteTrigOid = trigAddress.objectId; @@ -13931,6 +13935,8 @@ createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstr fk_trigger->whenClause = NULL; fk_trigger->transitionRels = NIL; fk_trigger->constrrel = NULL; + fk_trigger->trigcomment = NULL; + fk_trigger->transformed = true; switch (fkconstraint->fk_upd_action) { @@ -13967,7 +13973,7 @@ createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstr trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid, indexOid, InvalidOid, - parentUpdTrigger, NULL, true, false); + parentUpdTrigger, true, false); if (updateTrigOid) *updateTrigOid = trigAddress.objectId; } @@ -20857,15 +20863,17 @@ CloneRowTriggersToPartition(Relation parent, Relation partition) trigStmt->timing = trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK; trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK; trigStmt->columns = cols; - trigStmt->whenClause = NULL; /* passed separately */ + trigStmt->whenClause = qual; trigStmt->transitionRels = NIL; /* not supported at present */ trigStmt->deferrable = trigForm->tgdeferrable; trigStmt->initdeferred = trigForm->tginitdeferred; trigStmt->constrrel = NULL; /* passed separately */ + trigStmt->trigcomment = NULL; + trigStmt->transformed = true; /* whenClause alerady transformed */ CreateTriggerFiringOn(trigStmt, NULL, RelationGetRelid(partition), trigForm->tgconstrrelid, InvalidOid, InvalidOid, - trigForm->tgfoid, trigForm->oid, qual, + trigForm->tgfoid, trigForm->oid, false, true, trigForm->tgenabled); MemoryContextSwitchTo(oldcxt); diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 579ac8d76ae7..324f31fc5017 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -30,6 +30,7 @@ #include "catalog/pg_proc.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" +#include "commands/comment.h" #include "commands/trigger.h" #include "executor/executor.h" #include "miscadmin.h" @@ -40,6 +41,7 @@ #include "parser/parse_collate.h" #include "parser/parse_func.h" #include "parser/parse_relation.h" +#include "parser/parse_utilcmd.h" #include "partitioning/partdesc.h" #include "pgstat.h" #include "rewrite/rewriteHandler.h" @@ -139,9 +141,6 @@ static HeapTuple check_modified_virtual_generated(TupleDesc tupdesc, HeapTuple t * create the trigger on partitions, 2) when creating child foreign key * triggers; see CreateFKCheckTrigger() and createForeignKeyActionTriggers(). * - * If whenClause is passed, it is an already-transformed expression for - * WHEN. In this case, we ignore any that may come in stmt->whenClause. - * * If isInternal is true then this is an internally-generated trigger. * This argument sets the tgisinternal field of the pg_trigger entry, and * if true causes us to modify the given trigger name to ensure uniqueness. @@ -159,13 +158,13 @@ static HeapTuple check_modified_virtual_generated(TupleDesc tupdesc, HeapTuple t ObjectAddress CreateTrigger(CreateTrigStmt *stmt, const char *queryString, Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid, - Oid funcoid, Oid parentTriggerOid, Node *whenClause, + Oid funcoid, Oid parentTriggerOid, bool isInternal, bool in_partition) { return CreateTriggerFiringOn(stmt, queryString, relOid, refRelOid, constraintOid, indexOid, funcoid, - parentTriggerOid, whenClause, isInternal, + parentTriggerOid, isInternal, in_partition, TRIGGER_FIRES_ON_ORIGIN); } @@ -177,15 +176,15 @@ ObjectAddress CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid, Oid funcoid, Oid parentTriggerOid, - Node *whenClause, bool isInternal, bool in_partition, + bool isInternal, bool in_partition, char trigger_fires_when) { int16 tgtype; int ncolumns; int16 *columns; int2vector *tgattr; - List *whenRtable; - char *qual; + List *whenRtable = NIL; + char *qual = NULL; Datum values[Natts_pg_trigger]; bool nulls[Natts_pg_trigger]; Relation rel; @@ -207,6 +206,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, Oid existing_constraint_oid = InvalidOid; bool existing_isInternal = false; bool existing_isClone = false; + Node *whenClause = NULL; if (OidIsValid(relOid)) rel = table_open(relOid, ShareRowExclusiveLock); @@ -557,133 +557,21 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, /* * Parse the WHEN clause, if any and we weren't passed an already * transformed one. - * - * Note that as a side effect, we fill whenRtable when parsing. If we got - * an already parsed clause, this does not occur, which is what we want -- - * no point in adding redundant dependencies below. */ - if (!whenClause && stmt->whenClause) + if (stmt->whenClause) { - ParseState *pstate; - ParseNamespaceItem *nsitem; - List *varList; - ListCell *lc; - - /* Set up a pstate to parse with */ - pstate = make_parsestate(NULL); - pstate->p_sourcetext = queryString; - - /* - * Set up nsitems for OLD and NEW references. - * - * 'OLD' must always have varno equal to 1 and 'NEW' equal to 2. - */ - nsitem = addRangeTableEntryForRelation(pstate, rel, - AccessShareLock, - makeAlias("old", NIL), - false, false); - addNSItemToQuery(pstate, nsitem, false, true, true); - nsitem = addRangeTableEntryForRelation(pstate, rel, - AccessShareLock, - makeAlias("new", NIL), - false, false); - addNSItemToQuery(pstate, nsitem, false, true, true); - - /* Transform expression. Copy to be sure we don't modify original */ - whenClause = transformWhereClause(pstate, - copyObject(stmt->whenClause), - EXPR_KIND_TRIGGER_WHEN, - "WHEN"); - /* we have to fix its collations too */ - assign_expr_collations(pstate, whenClause); - - /* - * Check for disallowed references to OLD/NEW. - * - * NB: pull_var_clause is okay here only because we don't allow - * subselects in WHEN clauses; it would fail to examine the contents - * of subselects. - */ - varList = pull_var_clause(whenClause, 0); - foreach(lc, varList) + if (!stmt->transformed) { - Var *var = (Var *) lfirst(lc); + stmt = transformCreateTriggerStmt(RelationGetRelid(rel), stmt, + queryString); - switch (var->varno) - { - case PRS2_OLD_VARNO: - if (!TRIGGER_FOR_ROW(tgtype)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("statement trigger's WHEN condition cannot reference column values"), - parser_errposition(pstate, var->location))); - if (TRIGGER_FOR_INSERT(tgtype)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("INSERT trigger's WHEN condition cannot reference OLD values"), - parser_errposition(pstate, var->location))); - /* system columns are okay here */ - break; - case PRS2_NEW_VARNO: - if (!TRIGGER_FOR_ROW(tgtype)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("statement trigger's WHEN condition cannot reference column values"), - parser_errposition(pstate, var->location))); - if (TRIGGER_FOR_DELETE(tgtype)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("DELETE trigger's WHEN condition cannot reference NEW values"), - parser_errposition(pstate, var->location))); - if (var->varattno < 0 && TRIGGER_FOR_BEFORE(tgtype)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"), - parser_errposition(pstate, var->location))); - if (TRIGGER_FOR_BEFORE(tgtype) && - var->varattno == 0 && - RelationGetDescr(rel)->constr && - (RelationGetDescr(rel)->constr->has_generated_stored || - RelationGetDescr(rel)->constr->has_generated_virtual)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"), - errdetail("A whole-row reference is used and the table contains generated columns."), - parser_errposition(pstate, var->location))); - if (TRIGGER_FOR_BEFORE(tgtype) && - var->varattno > 0 && - TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attgenerated) - ereport(ERROR, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"), - errdetail("Column \"%s\" is a generated column.", - NameStr(TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attname)), - parser_errposition(pstate, var->location))); - break; - default: - /* can't happen without add_missing_from, so just elog */ - elog(ERROR, "trigger WHEN condition cannot contain references to other relations"); - break; - } + whenClause = stmt->whenClause; + Assert(whenClause != NULL); } + else + whenClause = stmt->whenClause; - /* we'll need the rtable for recordDependencyOnExpr */ - whenRtable = pstate->p_rtable; - - qual = nodeToString(whenClause); - - free_parsestate(pstate); - } - else if (!whenClause) - { - whenClause = NULL; - whenRtable = NIL; - qual = NULL; - } - else - { qual = nodeToString(whenClause); - whenRtable = NIL; } /* @@ -1129,10 +1017,40 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, * If it has a WHEN clause, add dependencies on objects mentioned in the * expression (eg, functions, as well as any columns used). */ - if (whenRtable != NIL) + if (whenClause != NULL) + { + ParseState *pstate; + ParseNamespaceItem *nsitem; + + /* Set up a pstate to parse with */ + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + /* + * Set up nsitems for OLD and NEW references. + * + * 'OLD' must always have varno equal to 1 and 'NEW' equal to 2. + */ + nsitem = addRangeTableEntryForRelation(pstate, rel, + AccessShareLock, + makeAlias("old", NIL), + false, false); + addNSItemToQuery(pstate, nsitem, false, true, true); + + nsitem = addRangeTableEntryForRelation(pstate, rel, + AccessShareLock, + makeAlias("new", NIL), + false, false); + addNSItemToQuery(pstate, nsitem, false, true, true); + + /* we'll need the rtable for recordDependencyOnExpr */ + whenRtable = pstate->p_rtable; recordDependencyOnExpr(&myself, whenClause, whenRtable, DEPENDENCY_NORMAL); + free_parsestate(pstate); + } + /* Post creation hook for new trigger */ InvokeObjectPostCreateHookArg(TriggerRelationId, trigoid, 0, isInternal); @@ -1175,7 +1093,6 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, */ childStmt = copyObject(stmt); childStmt->funcname = NIL; - childStmt->whenClause = NULL; /* If there is a WHEN clause, create a modified copy of it */ qual = copyObject(whenClause); @@ -1185,11 +1102,13 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, qual = (Node *) map_partition_varattnos((List *) qual, PRS2_NEW_VARNO, childTbl, rel); + childStmt->whenClause = qual; + childStmt->transformed = true; CreateTriggerFiringOn(childStmt, queryString, partdesc->oids[i], refRelOid, InvalidOid, InvalidOid, - funcoid, trigoid, qual, + funcoid, trigoid, isInternal, true, trigger_fires_when); table_close(childTbl, NoLock); @@ -1204,6 +1123,11 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, /* Keep lock on target rel until end of xact */ table_close(rel, NoLock); + /* Add any requested comment */ + if (stmt->trigcomment != NULL) + CreateComments(trigoid, TriggerRelationId, 0, + stmt->trigcomment); + return myself; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 8a0470d5b841..11344bf73643 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -783,7 +783,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); TABLE TABLES TABLESAMPLE TABLESPACE TARGET TEMP TEMPLATE TEMPORARY TEXT_P THEN TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM - TREAT TRIGGER TRIM TRUE_P + TREAT TRIGGER TRIGGERS TRIM TRUE_P TRUNCATE TRUSTED TYPE_P TYPES_P UESCAPE UNBOUNDED UNCONDITIONAL UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN @@ -4243,6 +4243,7 @@ TableLikeOption: | INDEXES { $$ = CREATE_TABLE_LIKE_INDEXES; } | STATISTICS { $$ = CREATE_TABLE_LIKE_STATISTICS; } | STORAGE { $$ = CREATE_TABLE_LIKE_STORAGE; } + | TRIGGERS { $$ = CREATE_TABLE_LIKE_TRIGGERS; } | ALL { $$ = CREATE_TABLE_LIKE_ALL; } ; @@ -6097,6 +6098,8 @@ CreateTrigStmt: n->deferrable = false; n->initdeferred = false; n->constrrel = NULL; + n->trigcomment = NULL; + n->transformed = false; $$ = (Node *) n; } | CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON @@ -6147,6 +6150,8 @@ CreateTrigStmt: &n->deferrable, &n->initdeferred, &dummy, NULL, NULL, yyscanner); n->constrrel = $10; + n->trigcomment = NULL; + n->transformed = false; $$ = (Node *) n; } ; @@ -18114,6 +18119,7 @@ unreserved_keyword: | TRANSACTION | TRANSFORM | TRIGGER + | TRIGGERS | TRUNCATE | TRUSTED | TYPE_P @@ -18763,6 +18769,7 @@ bare_label_keyword: | TRANSFORM | TREAT | TRIGGER + | TRIGGERS | TRIM | TRUE_P | TRUNCATE diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index e96b38a59d50..7b8cccc40185 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -37,7 +37,9 @@ #include "catalog/pg_constraint.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" +#include "catalog/pg_proc.h" #include "catalog/pg_statistic_ext.h" +#include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "commands/comment.h" #include "commands/defrem.h" @@ -61,6 +63,7 @@ #include "rewrite/rewriteManip.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/partcache.h" #include "utils/rel.h" @@ -120,6 +123,11 @@ static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid, Oid source_statsid, const AttrMap *attmap); +static CreateTrigStmt *generateClonedTriggerStmt(RangeVar *heapRel, + Oid source_trigid, + Relation source_rel, + const AttrMap *attmap); + static List *get_collation(Oid collation, Oid actual_datatype); static List *get_opclass(Oid opclass, Oid actual_datatype); static void transformIndexConstraints(CreateStmtContext *cxt); @@ -1318,7 +1326,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla CREATE_TABLE_LIKE_GENERATED | CREATE_TABLE_LIKE_CONSTRAINTS | CREATE_TABLE_LIKE_INDEXES | - CREATE_TABLE_LIKE_STATISTICS)) + CREATE_TABLE_LIKE_STATISTICS | + CREATE_TABLE_LIKE_TRIGGERS)) { table_like_clause->relationOid = RelationGetRelid(relation); cxt->likeclauses = lappend(cxt->likeclauses, table_like_clause); @@ -1584,6 +1593,46 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause) } } + /* Process triggers if required */ + if ((table_like_clause->options & CREATE_TABLE_LIKE_TRIGGERS) && + relation->trigdesc != NULL && + childrel->rd_rel->relkind != RELKIND_FOREIGN_TABLE) + { + bool include_comments; + + include_comments = (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS); + + for (int nt = 0; nt < relation->trigdesc->numtriggers; nt++) + { + Trigger *trig; + Oid trigoid = InvalidOid; + CreateTrigStmt *trig_stmt = NULL; + + trig = relation->trigdesc->triggers + nt; + trigoid = trig->tgoid; + + if (trig->tgisinternal) + continue; + + /* internal trigger won't copied to new table */ + trig_stmt = generateClonedTriggerStmt(heapRel, + trigoid, + relation, + attmap); + + /* Copy comment on trigger, if requested */ + if (include_comments) + { + comment = GetComment(trigoid, TriggerRelationId, 0); + + /* We make use of CreateTrigStmt's trigcomment option */ + trig_stmt->trigcomment = comment; + } + + result = lappend(result, trig_stmt); + } + } + /* * Process extended statistics if required. */ @@ -2163,6 +2212,200 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid, return stats; } +/* + * Generate a CreateTrigStmt node using information from an already existing + * trigger "source_trigid" on source_rel, for the rel identified by heapRel. + * Ensure source trigger is not internal trigger. + * + * Attribute numbers in expression Vars are adjusted according to attmap. + */ +static CreateTrigStmt * +generateClonedTriggerStmt(RangeVar *heapRel, Oid source_trigid, + Relation source_rel, const AttrMap *attmap) +{ + CreateTrigStmt *trigStmt; + HeapTuple triggerTuple; + HeapTuple proctup; + Form_pg_trigger trigForm; + Form_pg_proc procform; + Relation pg_trigger; + SysScanDesc tgscan; + ScanKeyData skey[1]; + Datum value; + bool isnull; + Node *qual = NULL; + List *trigargs = NIL; + List *cols = NIL; + List *funcname = NIL; + List *transitionRels = NIL; + char *schemaname; + + pg_trigger = table_open(TriggerRelationId, AccessShareLock); + + /* Find the trigger to copy */ + ScanKeyInit(&skey[0], + Anum_pg_trigger_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(source_trigid)); + + tgscan = systable_beginscan(pg_trigger, TriggerOidIndexId, true, + NULL, 1, skey); + + triggerTuple = systable_getnext(tgscan); + if (!HeapTupleIsValid(triggerTuple)) + elog(ERROR, "could not find tuple for trigger %u", source_trigid); + + trigForm = (Form_pg_trigger) GETSTRUCT(triggerTuple); + + Assert(!trigForm->tgisinternal); + + /* Reconstruct trigger function String list */ + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(trigForm->tgfoid)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for function %u", trigForm->tgfoid); + procform = (Form_pg_proc) GETSTRUCT(proctup); + + schemaname = get_namespace_name(procform->pronamespace); + funcname = list_make2(makeString(schemaname), + makeString(NameStr(procform->proname))); + ReleaseSysCache(proctup); + + /* Reconstruct trigger arguments list */ + if (trigForm->tgnargs > 0) + { + char *p; + + value = heap_getattr(triggerTuple, Anum_pg_trigger_tgargs, + RelationGetDescr(pg_trigger), &isnull); + if (isnull) + elog(ERROR, "tgargs is null for trigger \"%s\" in relation \"%s\"", + NameStr(trigForm->tgname), + RelationGetRelationName(source_rel)); + + p = (char *) VARDATA_ANY(DatumGetByteaPP(value)); + + for (int i = 0; i < trigForm->tgnargs; i++) + { + trigargs = lappend(trigargs, makeString(pstrdup(p))); + p += strlen(p) + 1; + } + } + + /* + * If there is a column list, transform it to a list of column names. + * Note we don't need to map this list in any way ... + */ + if (trigForm->tgattr.dim1 > 0) + { + int i; + + for (i = 0; i < trigForm->tgattr.dim1; i++) + { + Form_pg_attribute col; + + col = TupleDescAttr(RelationGetDescr(source_rel), + trigForm->tgattr.values[i] - 1); + cols = lappend(cols, + makeString(pstrdup(NameStr(col->attname)))); + } + } + + /* If the trigger has a WHEN qualification, add that */ + value = fastgetattr(triggerTuple, Anum_pg_trigger_tgqual, + RelationGetDescr(pg_trigger), &isnull); + if (!isnull) + { + bool found_whole_row; + + qual = stringToNode(TextDatumGetCString(value)); + + /* Adjust Vars to match new table's column numbering */ + qual = map_variable_attnos(qual, PRS2_NEW_VARNO, 0, + attmap, + InvalidOid, &found_whole_row); + + /* As in expandTableLikeClause, reject whole-row variables */ + if (found_whole_row) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert whole-row table reference"), + errdetail("Trigger \"%s\" contains a whole-row table reference.", + NameStr(trigForm->tgname))); + + qual = map_variable_attnos(qual, PRS2_OLD_VARNO, 0, + attmap, + InvalidOid, &found_whole_row); + + /* As in expandTableLikeClause, reject whole-row variables */ + if (found_whole_row) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert whole-row table reference"), + errdetail("Trigger \"%s\" contains a whole-row table reference.", + NameStr(trigForm->tgname))); + } + + /* Reconstruct trigger old transition table */ + value = fastgetattr(triggerTuple, Anum_pg_trigger_tgoldtable, + RelationGetDescr(pg_trigger), &isnull); + if (!isnull) + { + TriggerTransition *old = makeNode(TriggerTransition); + old->isNew = false; + old->name = NameStr(*DatumGetName(value)); + old->isTable = true; + transitionRels = lappend(transitionRels, old); + } + + /* Reconstruct trigger old transition table */ + value = fastgetattr(triggerTuple, Anum_pg_trigger_tgnewtable, + RelationGetDescr(pg_trigger), &isnull); + if (!isnull) + { + TriggerTransition *new = makeNode(TriggerTransition); + new->isNew = true; + new->name = NameStr(*DatumGetName(value)); + new->isTable = true; + transitionRels = lappend(transitionRels, new); + } + + trigStmt = makeNode(CreateTrigStmt); + trigStmt->replace = false; + trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint); + trigStmt->trigname = NameStr(trigForm->tgname); + trigStmt->relation = heapRel; + trigStmt->funcname = funcname; + trigStmt->args = trigargs; + trigStmt->row = TRIGGER_FOR_ROW(trigForm->tgtype); + trigStmt->timing = trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK; + trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK; + trigStmt->columns = cols; + trigStmt->whenClause = qual; + trigStmt->transitionRels = transitionRels; + trigStmt->deferrable = trigForm->tgdeferrable; + trigStmt->initdeferred = trigForm->tginitdeferred; + + trigStmt->constrrel = NULL; + if (OidIsValid(trigForm->tgconstrrelid)) + { + const char *relname; + const char *nspname; + Oid nspoid; + + relname = quote_identifier(get_rel_name(trigForm->tgconstrrelid)); + nspoid = get_rel_namespace(trigForm->tgconstrrelid); + nspname = get_namespace_name_or_temp(nspoid); + nspname = quote_identifier(nspname); + trigStmt->constrrel = makeRangeVar((char *) nspname, (char *) relname, -1); + } + trigStmt->transformed = true; /* don't need transformCreateTriggerStmt again */ + + systable_endscan(tgscan); + table_close(pg_trigger, AccessShareLock); + + return trigStmt; +} + /* * get_collation - fetch qualified name of a collation * @@ -3133,6 +3376,149 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) return stmt; } +/* + * transformCreateTriggerStmt - parse analysis for CREATE TRIGGER + * + * Note: This is for parse analysis CreateTrigStmt->whenClause only, other + * CreateTrigStmt error checking happen in CreateTriggerFiringOn. + * + * To avoid race conditions, it's important that this function relies only on + * the passed-in relid (and not on stmt->relation) to determine the target + * relation. + */ +CreateTrigStmt * +transformCreateTriggerStmt(Oid relid, CreateTrigStmt *stmt, const char *queryString) +{ + int16 tgtype; + ParseState *pstate; + ParseNamespaceItem *nsitem; + List *varList; + Relation rel; + + /* Nothing to do if statement already transformed. */ + if (stmt->transformed) + return stmt; + + /* Compute tgtype */ + TRIGGER_CLEAR_TYPE(tgtype); + if (stmt->row) + TRIGGER_SETT_ROW(tgtype); + tgtype |= stmt->timing; + tgtype |= stmt->events; + + /* Set up a pstate to parse with */ + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + /* + * Put the parent table into the rtable so that the expressions can refer + * to its fields without qualification. Caller is responsible for locking + * relation, but we still need to open it. + */ + rel = relation_open(relid, NoLock); + + /* + * Set up nsitems for OLD and NEW references. + * + * 'OLD' must always have varno equal to 1 and 'NEW' equal to 2. + */ + nsitem = addRangeTableEntryForRelation(pstate, rel, + AccessShareLock, + makeAlias("old", NIL), + false, false); + addNSItemToQuery(pstate, nsitem, false, true, true); + nsitem = addRangeTableEntryForRelation(pstate, rel, + AccessShareLock, + makeAlias("new", NIL), + false, false); + addNSItemToQuery(pstate, nsitem, false, true, true); + + stmt->whenClause = transformWhereClause(pstate, + stmt->whenClause, + EXPR_KIND_TRIGGER_WHEN, + "WHEN"); + /* we have to fix its collations too */ + assign_expr_collations(pstate, stmt->whenClause); + + /* + * Check for disallowed references to OLD/NEW. + * + * NB: pull_var_clause is okay here only because we don't allow + * subselects in WHEN clauses; it would fail to examine the contents + * of subselects. + */ + varList = pull_var_clause(stmt->whenClause, 0); + foreach_node(Var, var, varList) + { + switch (var->varno) + { + case PRS2_OLD_VARNO: + if (!TRIGGER_FOR_ROW(tgtype)) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("statement trigger's WHEN condition cannot reference column values"), + parser_errposition(pstate, var->location)); + if (TRIGGER_FOR_INSERT(tgtype)) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("INSERT trigger's WHEN condition cannot reference OLD values"), + parser_errposition(pstate, var->location)); + /* system columns are okay here */ + break; + case PRS2_NEW_VARNO: + if (!TRIGGER_FOR_ROW(tgtype)) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("statement trigger's WHEN condition cannot reference column values"), + parser_errposition(pstate, var->location)); + if (TRIGGER_FOR_DELETE(tgtype)) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("DELETE trigger's WHEN condition cannot reference NEW values"), + parser_errposition(pstate, var->location)); + if (var->varattno < 0 && TRIGGER_FOR_BEFORE(tgtype)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"), + parser_errposition(pstate, var->location)); + if (TRIGGER_FOR_BEFORE(tgtype) && + var->varattno == 0 && + RelationGetDescr(rel)->constr && + (RelationGetDescr(rel)->constr->has_generated_stored || + RelationGetDescr(rel)->constr->has_generated_virtual)) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"), + errdetail("A whole-row reference is used and the table contains generated columns."), + parser_errposition(pstate, var->location)); + if (TRIGGER_FOR_BEFORE(tgtype) && + var->varattno > 0 && + TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attgenerated) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"), + errdetail("Column \"%s\" is a generated column.", + NameStr(TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attname)), + parser_errposition(pstate, var->location)); + break; + default: + /* can't happen without add_missing_from, so just elog */ + elog(ERROR, "trigger WHEN condition cannot contain references to other relations"); + break; + } + } + + free_parsestate(pstate); + + /* Close relation */ + table_close(rel, NoLock); + + /* Mark statement as successfully transformed */ + stmt->transformed = true; + + return stmt; +} + /* * transformStatsStmt - parse analysis for CREATE STATISTICS * diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 918db53dd5e7..73d47b5ebf22 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1695,7 +1695,7 @@ ProcessUtilitySlow(ParseState *pstate, address = CreateTrigger((CreateTrigStmt *) parsetree, queryString, InvalidOid, InvalidOid, InvalidOid, InvalidOid, InvalidOid, - InvalidOid, NULL, false, false); + InvalidOid, false, false); break; case T_CreatePLangStmt: diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index cfd7daa20eda..3f4951bca610 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -153,12 +153,12 @@ extern PGDLLIMPORT int SessionReplicationRole; extern ObjectAddress CreateTrigger(CreateTrigStmt *stmt, const char *queryString, Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid, - Oid funcoid, Oid parentTriggerOid, Node *whenClause, + Oid funcoid, Oid parentTriggerOid, bool isInternal, bool in_partition); extern ObjectAddress CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString, Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid, Oid funcoid, Oid parentTriggerOid, - Node *whenClause, bool isInternal, bool in_partition, + bool isInternal, bool in_partition, char trigger_fires_when); extern void TriggerSetParentTrigger(Relation trigRel, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ecbddd12e1b3..78341976e093 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -795,6 +795,7 @@ typedef enum TableLikeOption CREATE_TABLE_LIKE_INDEXES = 1 << 6, CREATE_TABLE_LIKE_STATISTICS = 1 << 7, CREATE_TABLE_LIKE_STORAGE = 1 << 8, + CREATE_TABLE_LIKE_TRIGGERS = 1 << 9, CREATE_TABLE_LIKE_ALL = PG_INT32_MAX } TableLikeOption; @@ -3125,6 +3126,8 @@ typedef struct CreateTrigStmt bool deferrable; /* [NOT] DEFERRABLE */ bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */ RangeVar *constrrel; /* opposite relation, if RI trigger */ + char *trigcomment; /* comment to apply to trigger, or NULL */ + bool transformed; /* true when transformCreateTriggerStmt is finished */ } CreateTrigStmt; /* ---------------------- diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 84182eaaae2a..65ba102a0482 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -461,6 +461,7 @@ PG_KEYWORD("transaction", TRANSACTION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("transform", TRANSFORM, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("treat", TREAT, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("trigger", TRIGGER, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("triggers", TRIGGERS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("trim", TRIM, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("true", TRUE_P, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("truncate", TRUNCATE, UNRESERVED_KEYWORD, BARE_LABEL) diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 4965fac4495e..5482719d997e 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -28,6 +28,8 @@ extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString); +extern CreateTrigStmt *transformCreateTriggerStmt(Oid relid, CreateTrigStmt *stmt, + const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); extern List *transformCreateSchemaStmtElements(List *schemaElts, diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index 1eb8fba09537..f4c9807bf502 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -174,6 +174,16 @@ CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt'); CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt'); +CREATE TRIGGER wholetrig BEFORE UPDATE ON main_table FOR EACH ROW WHEN (OLD is not null) EXECUTE PROCEDURE trigger_func('modified_a'); +CREATE TABLE main_table1(LIKE main_table INCLUDING TRIGGERS); --error, wholerow reference +ERROR: cannot convert whole-row table reference +DETAIL: Trigger "wholetrig" contains a whole-row table reference. +DROP TRIGGER wholetrig ON main_table; +CREATE TRIGGER wholetrig BEFORE UPDATE ON main_table FOR EACH ROW WHEN (NEW is not null) EXECUTE PROCEDURE trigger_func('modified_a'); +CREATE TABLE main_table1(LIKE main_table INCLUDING TRIGGERS); --error, wholerow reference +ERROR: cannot convert whole-row table reference +DETAIL: Trigger "wholetrig" contains a whole-row table reference. +DROP TRIGGER wholetrig ON main_table; -- -- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified, -- CREATE TRIGGER should default to 'FOR EACH STATEMENT' @@ -394,6 +404,50 @@ NOTICE: trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, NOTICE: trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW NOTICE: trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT NOTICE: trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT +--create table like tests +COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS 'trigger before_ins_stmt_trig'; +CREATE TABLE main_table1(c INT, LIKE main_table INCLUDING TRIGGERS INCLUDING COMMENTS); +\d main_table + Table "public.main_table" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | +Triggers: + after_ins_stmt_trig AFTER INSERT ON main_table FOR EACH STATEMENT EXECUTE FUNCTION trigger_func('after_ins_stmt') + after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table FOR EACH ROW EXECUTE FUNCTION trigger_func('after_upd_a_b_row') + after_upd_b_row_trig AFTER UPDATE OF b ON main_table FOR EACH ROW EXECUTE FUNCTION trigger_func('after_upd_b_row') + after_upd_b_stmt_trig AFTER UPDATE OF b ON main_table FOR EACH STATEMENT EXECUTE FUNCTION trigger_func('after_upd_b_stmt') + after_upd_stmt_trig AFTER UPDATE ON main_table FOR EACH STATEMENT EXECUTE FUNCTION trigger_func('after_upd_stmt') + before_ins_stmt_trig BEFORE INSERT ON main_table FOR EACH STATEMENT EXECUTE FUNCTION trigger_func('before_ins_stmt') + before_upd_a_row_trig BEFORE UPDATE OF a ON main_table FOR EACH ROW EXECUTE FUNCTION trigger_func('before_upd_a_row') + before_upd_a_stmt_trig BEFORE UPDATE OF a ON main_table FOR EACH STATEMENT EXECUTE FUNCTION trigger_func('before_upd_a_stmt') + +\d main_table1 + Table "public.main_table1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + c | integer | | | + a | integer | | | + b | integer | | | +Triggers: + after_ins_stmt_trig AFTER INSERT ON main_table1 FOR EACH STATEMENT EXECUTE FUNCTION trigger_func('after_ins_stmt') + after_upd_a_b_row_trig AFTER UPDATE OF a, b ON main_table1 FOR EACH ROW EXECUTE FUNCTION trigger_func('after_upd_a_b_row') + after_upd_b_row_trig AFTER UPDATE OF b ON main_table1 FOR EACH ROW EXECUTE FUNCTION trigger_func('after_upd_b_row') + after_upd_b_stmt_trig AFTER UPDATE OF b ON main_table1 FOR EACH STATEMENT EXECUTE FUNCTION trigger_func('after_upd_b_stmt') + after_upd_stmt_trig AFTER UPDATE ON main_table1 FOR EACH STATEMENT EXECUTE FUNCTION trigger_func('after_upd_stmt') + before_ins_stmt_trig BEFORE INSERT ON main_table1 FOR EACH STATEMENT EXECUTE FUNCTION trigger_func('before_ins_stmt') + before_upd_a_row_trig BEFORE UPDATE OF a ON main_table1 FOR EACH ROW EXECUTE FUNCTION trigger_func('before_upd_a_row') + before_upd_a_stmt_trig BEFORE UPDATE OF a ON main_table1 FOR EACH STATEMENT EXECUTE FUNCTION trigger_func('before_upd_a_stmt') + +\dd before_ins_stmt_trig + Object descriptions + Schema | Name | Object | Description +--------+----------------------+---------+------------------------------ + public | before_ins_stmt_trig | trigger | trigger before_ins_stmt_trig +(1 row) + +COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS NULL; -- -- Test case for bug with BEFORE trigger followed by AFTER trigger with WHEN -- @@ -861,6 +915,9 @@ CREATE TRIGGER instead_of_update_trig INSTEAD OF UPDATE ON main_view FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd'); CREATE TRIGGER instead_of_delete_trig INSTEAD OF DELETE ON main_view FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del'); +CREATE TABLE main_view_table(LIKE main_view INCLUDING TRIGGERS); --error +ERROR: "main_view_table" is a table +DETAIL: Tables cannot have INSTEAD OF triggers. -- Valid BEFORE statement VIEW triggers CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_ins_stmt'); @@ -2318,6 +2375,18 @@ create constraint trigger parted_trig_two after insert on parted_constr deferrable initially deferred enforced for each row when (bark(new.b) AND new.a % 2 = 1) execute procedure trigger_notice_ab(); +create table parted_constr_copy (like parted_constr including all); +select pg_get_triggerdef(oid) +from pg_trigger +where not tgisinternal and tgrelid = 'parted_constr_copy'::regclass +order by tgname; + pg_get_triggerdef +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE CONSTRAINT TRIGGER parted_trig AFTER INSERT ON public.parted_constr_copy DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW EXECUTE FUNCTION trigger_notice_ab() + CREATE CONSTRAINT TRIGGER parted_trig_two AFTER INSERT ON public.parted_constr_copy DEFERRABLE INITIALLY DEFERRED FOR EACH ROW WHEN ((bark(new.b) AND ((new.a % 2) = 1))) EXECUTE FUNCTION trigger_notice_ab() +(2 rows) + +drop table parted_constr_copy; -- The immediate constraint is fired immediately; the WHEN clause of the -- deferred constraint is also called immediately. The deferred constraint -- is fired at commit time. @@ -3092,6 +3161,18 @@ create trigger iocdu_tt_parted_insert_trig create trigger iocdu_tt_parted_update_trig after update on iocdu_tt_parted referencing old table as old_table new table as new_table for each statement execute procedure dump_update(); +CREATE TABLE iocdu_tt_parted_copy(LIKE iocdu_tt_parted INCLUDING TRIGGERS); +\d iocdu_tt_parted_copy + Table "public.iocdu_tt_parted_copy" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | not null | + b | text | | | +Triggers: + iocdu_tt_parted_insert_trig AFTER INSERT ON iocdu_tt_parted_copy REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE FUNCTION dump_insert() + iocdu_tt_parted_update_trig AFTER UPDATE ON iocdu_tt_parted_copy REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE FUNCTION dump_update() + +DROP TABLE iocdu_tt_parted_copy; -- inserts only insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB') on conflict (a) do @@ -3574,6 +3655,24 @@ begin end; $$; alter function whoami() owner to regress_fn_owner; +--CREATE TABLE LIKE INCLUDING TRIGGERS +-- test constraint trigger that reference another table +create table trig_t1 (id integer); +create table trig_t2 (id integer); +create constraint trigger con_trig_test after insert on trig_t1 from trig_t2 + deferrable initially deferred + for each row + execute function whoami(); +create table trig_t1_copy(like trig_t1 including triggers); +\d trig_t1_copy + Table "public.trig_t1_copy" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + id | integer | | | +Triggers: + con_trig_test AFTER INSERT ON trig_t1_copy FROM trig_t2 DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION whoami() + +drop table trig_t1, trig_t2, trig_t1_copy; create table defer_trig (id integer); grant insert on defer_trig to public; create constraint trigger whoami after insert on defer_trig diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index 5f7f75d7ba5d..cb9c6bfe38a3 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -120,6 +120,14 @@ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('before_ins_stmt'); CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func('after_ins_stmt'); +CREATE TRIGGER wholetrig BEFORE UPDATE ON main_table FOR EACH ROW WHEN (OLD is not null) EXECUTE PROCEDURE trigger_func('modified_a'); +CREATE TABLE main_table1(LIKE main_table INCLUDING TRIGGERS); --error, wholerow reference +DROP TRIGGER wholetrig ON main_table; + +CREATE TRIGGER wholetrig BEFORE UPDATE ON main_table FOR EACH ROW WHEN (NEW is not null) EXECUTE PROCEDURE trigger_func('modified_a'); +CREATE TABLE main_table1(LIKE main_table INCLUDING TRIGGERS); --error, wholerow reference +DROP TRIGGER wholetrig ON main_table; + -- -- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified, -- CREATE TRIGGER should default to 'FOR EACH STATEMENT' @@ -234,6 +242,15 @@ SELECT pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = 'main_table'::regc UPDATE main_table SET a = 50; UPDATE main_table SET b = 10; +--create table like tests +COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS 'trigger before_ins_stmt_trig'; +CREATE TABLE main_table1(c INT, LIKE main_table INCLUDING TRIGGERS INCLUDING COMMENTS); +\d main_table +\d main_table1 + +\dd before_ins_stmt_trig +COMMENT ON TRIGGER before_ins_stmt_trig ON main_table IS NULL; + -- -- Test case for bug with BEFORE trigger followed by AFTER trigger with WHEN -- @@ -621,6 +638,8 @@ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd'); CREATE TRIGGER instead_of_delete_trig INSTEAD OF DELETE ON main_view FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del'); +CREATE TABLE main_view_table(LIKE main_view INCLUDING TRIGGERS); --error + -- Valid BEFORE statement VIEW triggers CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_ins_stmt'); @@ -1608,6 +1627,14 @@ create constraint trigger parted_trig_two after insert on parted_constr for each row when (bark(new.b) AND new.a % 2 = 1) execute procedure trigger_notice_ab(); +create table parted_constr_copy (like parted_constr including all); +select pg_get_triggerdef(oid) +from pg_trigger +where not tgisinternal and tgrelid = 'parted_constr_copy'::regclass +order by tgname; + +drop table parted_constr_copy; + -- The immediate constraint is fired immediately; the WHEN clause of the -- deferred constraint is also called immediately. The deferred constraint -- is fired at commit time. @@ -2278,6 +2305,10 @@ create trigger iocdu_tt_parted_update_trig after update on iocdu_tt_parted referencing old table as old_table new table as new_table for each statement execute procedure dump_update(); +CREATE TABLE iocdu_tt_parted_copy(LIKE iocdu_tt_parted INCLUDING TRIGGERS); +\d iocdu_tt_parted_copy +DROP TABLE iocdu_tt_parted_copy; + -- inserts only insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB') on conflict (a) do @@ -2735,6 +2766,18 @@ end; $$; alter function whoami() owner to regress_fn_owner; +--CREATE TABLE LIKE INCLUDING TRIGGERS +-- test constraint trigger that reference another table +create table trig_t1 (id integer); +create table trig_t2 (id integer); +create constraint trigger con_trig_test after insert on trig_t1 from trig_t2 + deferrable initially deferred + for each row + execute function whoami(); +create table trig_t1_copy(like trig_t1 including triggers); +\d trig_t1_copy +drop table trig_t1, trig_t2, trig_t1_copy; + create table defer_trig (id integer); grant insert on defer_trig to public; create constraint trigger whoami after insert on defer_trig