W dniu 20.08.2013 16:47, Karol Trzcionka pisze: > Thank you for the review and tests. New version introduce a lot of > improvements: > - Fix regression test for view (wrong table_name) > - Add regression test for inheritance > - Delete hack in initsplan.c (now we ignore all RTE_BEFORE) - the > uninitialized issue > - Revert changing varno in add_vars_to_targetlist > - Add all "before" variables to targetlist > - Avoid adding variables to slot for AFTER. > - Treat varnoold like a flag - prevent from adjustment if RTE_BEFORE > - All before/after are now set on OUTER_VAR > - Rename fix_varno_varattno to bind_returning_variables > - Add comment about bind_returning_variables > - Remove unneeded code in fix_join_expr_mutator (it was changing varno > of RTE_BEFORE - now there is not any var with varno assigned to it) I've just realized the prepare_returning_before() is unneeded right now so I've removed it. Version 7, hopefully the last. ;) Regards, Karol Trzcionka
diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml index 90b9208..eba35f0 100644 --- a/doc/src/sgml/ref/update.sgml +++ b/doc/src/sgml/ref/update.sgml @@ -194,12 +194,27 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table_name</replaceable> [ * ] [ <term><replaceable class="PARAMETER">output_expression</replaceable></term> <listitem> <para> - An expression to be computed and returned by the <command>UPDATE</> - command after each row is updated. The expression can use any - column names of the table named by <replaceable class="PARAMETER">table_name</replaceable> - or table(s) listed in <literal>FROM</>. - Write <literal>*</> to return all columns. + An expression to be computed and returned by the + <command>UPDATE</> command either before or after (prefixed with + <literal>BEFORE.</literal> and <literal>AFTER.</literal>, + respectively) each row is updated. The expression can use any + column names of the table named by <replaceable + class="PARAMETER">table_name</replaceable> or table(s) listed in + <literal>FROM</>. Write <literal>AFTER.*</literal> to return all + columns after the update. Write <literal>BEFORE.*</literal> for all + columns before the update. Write <literal>*</literal> to return all + columns after update and all triggers fired (these values are in table + after command). You may combine BEFORE, AFTER and raw columns in the + expression. </para> + <warning><para> + Mixing table names or aliases named before or after with the + above will result in confusion and suffering. If you happen to + have a table called <literal>before</literal> or + <literal>after</literal>, alias it to something else when using + RETURNING. + </para></warning> + </listitem> </varlistentry> @@ -287,15 +302,16 @@ UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT </para> <para> - Perform the same operation and return the updated entries: + Perform the same operation and return information on the changed entries: <programlisting> UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT WHERE city = 'San Francisco' AND date = '2003-07-03' - RETURNING temp_lo, temp_hi, prcp; + RETURNING temp_lo AS new_low, temp_hi AS new_high, BEFORE.temp_hi/BEFORE.temp_low AS old_ratio, AFTER.temp_hi/AFTER.temp_low AS new_ratio prcp; </programlisting> </para> + <para> Use the alternative column-list syntax to do the same update: <programlisting> diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index d86e9ad..fafd311 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -2335,7 +2335,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo) TupleTableSlot * ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, - ItemPointer tupleid, TupleTableSlot *slot) + ItemPointer tupleid, TupleTableSlot *slot, TupleTableSlot **planSlot) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; HeapTuple slottuple = ExecMaterializeSlot(slot); @@ -2381,6 +2381,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, if (newSlot != NULL) { slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot); + *planSlot = newSlot; slottuple = ExecMaterializeSlot(slot); newtuple = slottuple; } diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 15f5dcc..06ebaf3 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -603,7 +603,7 @@ ExecUpdate(ItemPointer tupleid, resultRelInfo->ri_TrigDesc->trig_update_before_row) { slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, - tupleid, slot); + tupleid, slot, &planSlot); if (slot == NULL) /* "do nothing" */ return NULL; @@ -737,6 +737,7 @@ lreplace:; hufd.xmax); if (!TupIsNull(epqslot)) { + planSlot = epqslot; *tupleid = hufd.ctid; slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); tuple = ExecMaterializeSlot(slot); diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 908f397..461ec4f 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -1987,6 +1987,7 @@ range_table_walker(List *rtable, { case RTE_RELATION: case RTE_CTE: + case RTE_BEFORE: /* nothing to do */ break; case RTE_SUBQUERY: @@ -2701,6 +2702,7 @@ range_table_mutator(List *rtable, { case RTE_RELATION: case RTE_CTE: + case RTE_BEFORE: /* we don't bother to copy eref, aliases, etc; OK? */ break; case RTE_SUBQUERY: diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index cff4734..3c4e045 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2368,6 +2368,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) switch (node->rtekind) { case RTE_RELATION: + case RTE_BEFORE: WRITE_OID_FIELD(relid); WRITE_CHAR_FIELD(relkind); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index aad63e5..7af749c 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1207,6 +1207,7 @@ _readRangeTblEntry(void) switch (local_node->rtekind) { case RTE_RELATION: + case RTE_BEFORE: READ_OID_FIELD(relid); READ_CHAR_FIELD(relkind); break; diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index c5998b9..42923ce 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -184,8 +184,18 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, if (IsA(node, Var)) { Var *var = (Var *) node; - RelOptInfo *rel = find_base_rel(root, var->varno); + RelOptInfo *rel; + Index varno = var->varno; int attno = var->varattno; + RangeTblEntry *rte; + + if (root->parse->commandType == CMD_UPDATE) + { + rte = ((RangeTblEntry *) list_nth(root->parse->rtable, varno-1)); + if(rte->rtekind == RTE_BEFORE) + continue; + } + rel = find_base_rel(root, varno); if (bms_is_subset(where_needed, rel->relids)) continue; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index bcc0d45..89970ab 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -2157,6 +2157,9 @@ preprocess_rowmarks(PlannerInfo *root) if (rte->relkind == RELKIND_FOREIGN_TABLE) continue; + if (rte->relkind == RELKIND_BEFORE) + continue; + rels = bms_del_member(rels, rc->rti); newrc = makeNode(PlanRowMark); @@ -2196,6 +2199,9 @@ preprocess_rowmarks(PlannerInfo *root) if (!bms_is_member(i, rels)) continue; + if (rte->relkind == RELKIND_BEFORE) + continue; + newrc = makeNode(PlanRowMark); newrc->rti = newrc->prti = i; newrc->rowmarkId = ++(root->glob->lastRowMarkId); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index b78d727..a6d7778 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -134,6 +134,7 @@ static List *set_returning_clause_references(PlannerInfo *root, static bool fix_opfuncids_walker(Node *node, void *context); static bool extract_query_dependencies_walker(Node *node, PlannerInfo *context); +static void bind_returning_variables(List *rlist, int bef, int aft); /***************************************************************************** @@ -1715,7 +1716,10 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context) var = copyVar(var); var->varno += context->rtoffset; if (var->varnoold > 0) - var->varnoold += context->rtoffset; + { + if(((RangeTblEntry *)list_nth(context->root->parse->rtable,var->varnoold-1))->rtekind != RTE_BEFORE) + var->varnoold += context->rtoffset; + } return (Node *) var; } @@ -1865,6 +1869,56 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context) } /* + * bind_returning_variables + * Fix description of BEFORE. and AFTER. variables + * + * It replaces each variable generated by parser for + * BEFORE. and AFTER. statements. It binds var to proper + * places in slot. + * + * 'rlist': the RETURNING targetlist to be fixed + * 'bef': index of RTE_BEFORE "before" in rtable + * value 2 in most cases + * 'aft': index of RTE_BEFORE "after" in rtable + * value 3 in most cases + */ +void bind_returning_variables(List *rlist, int bef, int aft) +{ + ListCell *temp; + Var *var = NULL; + foreach(temp, rlist){ + TargetEntry *tle = (TargetEntry *)lfirst(temp); + + var = NULL; + if(IsA(tle, TargetEntry)) + { + var = (Var*)tle->expr; + } + else if(IsA(tle, Var)) + var=(Var*)tle; + if(var) + { + if( IsA(var, Var) ) + { + if(var->varnoold == aft || var->varnoold == bef) + { + var->varno = OUTER_VAR; + var->varattno = var->varoattno; + } + } + else if( IsA(var, OpExpr )) + { + bind_returning_variables(((OpExpr*)var)->args, bef, aft); + } + else if( IsA(var, FuncExpr )) + { + bind_returning_variables(((FuncExpr*)var)->args, bef, aft); + } + } + } +} + +/* * set_returning_clause_references * Perform setrefs.c's work on a RETURNING targetlist * @@ -1900,7 +1954,27 @@ set_returning_clause_references(PlannerInfo *root, int rtoffset) { indexed_tlist *itlist; + int after_index=0, before_index; + Query *parse = root->parse; + ListCell *rt; + RangeTblEntry *bef; + + int index_rel=1; + + foreach(rt,parse->rtable) + { + bef = (RangeTblEntry *)lfirst(rt); + if(strcmp(bef->eref->aliasname,"after") == 0 && bef->rtekind == RTE_BEFORE ) + { + after_index = index_rel; + } + if(strcmp(bef->eref->aliasname,"before") == 0 && bef->rtekind == RTE_BEFORE ) + { + before_index = index_rel; + } + index_rel++; + } /* * We can perform the desired Var fixup by abusing the fix_join_expr * machinery that formerly handled inner indexscan fixup. We search the @@ -1924,6 +1998,7 @@ set_returning_clause_references(PlannerInfo *root, resultRelation, rtoffset); + bind_returning_variables(rlist, before_index, after_index); pfree(itlist); return rlist; diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index c742cc9..e5c1ca1 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -652,6 +652,9 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, int varno = ((RangeTblRef *) jtnode)->rtindex; RangeTblEntry *rte = rt_fetch(varno, root->parse->rtable); + if (rte->rtekind == RTE_BEFORE) + return NULL; + /* * Is this a subquery RTE, and if so, is the subquery simple enough to * pull up? @@ -989,6 +992,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, case RTE_RELATION: case RTE_JOIN: case RTE_CTE: + case RTE_BEFORE: /* these can't contain any lateral references */ break; } @@ -1635,6 +1639,7 @@ replace_vars_in_jointree(Node *jtnode, case RTE_RELATION: case RTE_JOIN: case RTE_CTE: + case RTE_BEFORE: /* these shouldn't be marked LATERAL */ Assert(false); break; @@ -1788,6 +1793,17 @@ pullup_replace_vars_callback(Var *var, /* Make a copy of the tlist item to return */ newnode = copyObject(tle->expr); + if(IsA(newnode,Var) && rcon->root->parse->commandType == CMD_UPDATE && + var->varno <= list_length(rcon->root->parse->rtable) ) + { + RangeTblEntry *rte = rt_fetch(((Var*)var)->varnoold, rcon->root->parse->rtable); + if(rte->rtekind == RTE_BEFORE) + { + ((Var*)newnode)->varoattno = ((Var*)var)->varoattno; + ((Var*)newnode)->varnoold = ((Var*)var)->varnoold; + } + } + /* Insert PlaceHolderVar if needed */ if (rcon->need_phvs) { diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index fb67f9e..a989d36 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -165,6 +165,20 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) var->varno == result_relation) continue; /* don't need it */ + if (command_type == CMD_UPDATE) + { + RangeTblEntry *rte = ((RangeTblEntry *) list_nth(root->parse->rtable, (var->varno)-1)); + + if(rte->rtekind == RTE_BEFORE) + { + var->varno = result_relation; + if(strcmp(rte->eref->aliasname,"before") == 0) + var->varoattno = list_length(tlist) + 1; + else + continue; + } + } + if (tlist_member((Node *) var, tlist)) continue; /* already got it */ diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 2ac215f..b10e2ba 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -136,6 +136,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) /* Table --- retrieve statistics from the system catalogs */ get_relation_info(root, rte->relid, rte->inh, rel); break; + case RTE_BEFORE: + break; case RTE_SUBQUERY: case RTE_FUNCTION: case RTE_VALUES: @@ -487,6 +489,7 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, Var *var = (Var *) lfirst(vars); RelOptInfo *baserel; int ndx; + RangeTblEntry *rte; /* * Ignore PlaceHolderVars in the input tlists; we'll make our own @@ -504,6 +507,10 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, elog(ERROR, "unexpected node type in reltargetlist: %d", (int) nodeTag(var)); + rte = ((RangeTblEntry *) list_nth(root->parse->rtable, (var->varno)-1)); + if(rte->rtekind == RTE_BEFORE) + continue; + /* Get the Var's original base rel */ baserel = find_base_rel(root, var->varno); diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 4a3d5c8..c6f0183 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -697,6 +697,16 @@ flatten_join_alias_vars_mutator(Node *node, newvar = (Node *) list_nth(rte->joinaliasvars, var->varattno - 1); Assert(newvar != NULL); newvar = copyObject(newvar); + if(IsA(newvar,Var) && context->root->parse->commandType == CMD_UPDATE && + var->varno <= list_length(context->root->parse->rtable)) + { + RangeTblEntry *rt = rt_fetch(var->varno, context->root->parse->rtable); + if(rt->rtekind == RTE_BEFORE) + { + ((Var*)newvar)->varoattno = ((Var*)var)->varoattno; + ((Var*)newvar)->varnoold = ((Var*)var)->varnoold; + } + } /* * If we are expanding an alias carried down from an upper query, must diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index a9d1fec..1e73f12 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2015,6 +2015,9 @@ transformReturningList(ParseState *pstate, List *returningList) save_next_resno = pstate->p_next_resno; pstate->p_next_resno = 1; + if (pstate->p_is_update) + addAliases(pstate); + /* transform RETURNING identically to a SELECT targetlist */ rlist = transformTargetList(pstate, returningList, EXPR_KIND_RETURNING); diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index ea90e58..9b693dd 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -84,7 +84,56 @@ static WindowClause *findWindowClause(List *wclist, const char *name); static Node *transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause); +extern void addAliases(ParseState *pstate); +void addAliases(ParseState *pstate) +{ + const int noal = 2; + char *aliases[] = {"before","after"}; + int i; + ListCell *l; + ParseNamespaceItem *nsitem; + RangeTblEntry *rte = NULL; + + foreach(l, pstate->p_namespace) + { + nsitem = (ParseNamespaceItem *) lfirst(l); + rte = nsitem->p_rte; + + /* Ignore columns-only items */ + if (!nsitem->p_rel_visible) + continue; + /* If not inside LATERAL, ignore lateral-only items */ + if (nsitem->p_lateral_only && !pstate->p_lateral_active) + continue; + + for(i=0 ; i<noal; i++) + { + if (aliases[i] && strcmp(rte->eref->aliasname, aliases[i]) == 0) + { + aliases[i] = NULL; + } + } + } + + l = pstate->p_namespace->head; + nsitem = (ParseNamespaceItem *) lfirst(l); + + for(i=0 ; i<noal; i++) + { + if (aliases[i]) + { + rte = makeNode(RangeTblEntry); + rte->eref = makeAlias(aliases[i], nsitem->p_rte->eref->colnames); + rte->inh = INH_NO; + rte->rtekind = RTE_BEFORE; + rte->relkind = RELKIND_BEFORE; + rte->relid = nsitem->p_rte->relid; + pstate->p_rtable = lappend(pstate->p_rtable, rte); + addRTEtoQuery(pstate, rte, true, true, false); + } + } +} /* * transformFromClause - * Process the FROM clause and add items to the query's range table, diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 39922d3..a0575a7 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -1710,6 +1710,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, switch (rte->rtekind) { case RTE_RELATION: + case RTE_BEFORE: /* Ordinary relation RTE */ expandRelation(rte->relid, rte->eref, rtindex, sublevels_up, location, @@ -2211,6 +2212,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, switch (rte->rtekind) { case RTE_RELATION: + case RTE_BEFORE: { /* Plain relation RTE --- get the attribute's type info */ HeapTuple tp; @@ -2389,6 +2391,7 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) switch (rte->rtekind) { case RTE_RELATION: + case RTE_BEFORE: { /* * Plain relation RTE --- get the attribute's catalog entry diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 9c6c202..ca66a1b 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -317,6 +317,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle, break; case RTE_FUNCTION: case RTE_VALUES: + case RTE_BEFORE: /* not a simple relation, leave it unmarked */ break; case RTE_CTE: @@ -1422,6 +1423,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) { case RTE_RELATION: case RTE_VALUES: + case RTE_BEFORE: /* * This case should not occur: a column of a table or values list diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 2b005d6..6df6c8a 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -5603,6 +5603,7 @@ get_name_for_var_field(Var *var, int fieldno, { case RTE_RELATION: case RTE_VALUES: + case RTE_BEFORE: /* * This case should not occur: a column of a table or values list diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 49c4f6f..1b09994 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -154,6 +154,7 @@ DESCR(""); #define RELKIND_COMPOSITE_TYPE 'c' /* composite type */ #define RELKIND_FOREIGN_TABLE 'f' /* foreign table */ #define RELKIND_MATVIEW 'm' /* materialized view */ +#define RELKIND_BEFORE 'b' /* virtual table for before/after statements */ #define RELPERSISTENCE_PERMANENT 'p' /* regular table */ #define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */ diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 411a66d..e4be684 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -162,7 +162,8 @@ extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tupleid, - TupleTableSlot *slot); + TupleTableSlot *slot, + TupleTableSlot **planSlot); extern void ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 51fef68..c2c1783 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -707,7 +707,8 @@ typedef enum RTEKind RTE_JOIN, /* join */ RTE_FUNCTION, /* function in FROM */ RTE_VALUES, /* VALUES (<exprlist>), (<exprlist>), ... */ - RTE_CTE /* common table expr (WITH list element) */ + RTE_CTE, /* common table expr (WITH list element) */ + RTE_BEFORE /* for before/after statements */ } RTEKind; typedef struct RangeTblEntry diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h index 9bdb033..67cbbb2 100644 --- a/src/include/parser/parse_clause.h +++ b/src/include/parser/parse_clause.h @@ -44,5 +44,6 @@ extern List *transformDistinctOnClause(ParseState *pstate, List *distinctlist, extern Index assignSortGroupRef(TargetEntry *tle, List *tlist); extern bool targetIsInSortList(TargetEntry *tle, Oid sortop, List *sortList); +extern void addAliases(ParseState *pstate); #endif /* PARSE_CLAUSE_H */ diff --git a/src/test/regress/expected/returning_before_after.out b/src/test/regress/expected/returning_before_after.out new file mode 100644 index 0000000..06679cd --- /dev/null +++ b/src/test/regress/expected/returning_before_after.out @@ -0,0 +1,152 @@ +-- +-- Test BEFORE/AFTER feature in RETURNING statements +CREATE TABLE foo ( + bar1 INTEGER, + bar2 TEXT + ); +INSERT INTO foo VALUES (1, 'x'),(2,'y'); +UPDATE foo SET bar1=bar1+1 RETURNING before.*, bar1, bar2; + bar1 | bar2 | bar1 | bar2 +------+------+------+------ + 1 | x | 2 | x + 2 | y | 3 | y +(2 rows) + +UPDATE foo SET bar1=bar1-1 RETURNING after.bar1, before.bar1*2; + bar1 | ?column? +------+---------- + 1 | 4 + 2 | 6 +(2 rows) + +UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'z' RETURNING before.*, after.*; + bar1 | bar2 | bar1 | bar2 +------+------+------+------ + 1 | x | 2 | xz + 2 | y | 3 | yz +(2 rows) + +-- check single after +UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'a' RETURNING after.*; + bar1 | bar2 +------+------ + 3 | xza + 4 | yza +(2 rows) + +-- check single before +UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'b' RETURNING before.*; + bar1 | bar2 +------+------ + 3 | xza + 4 | yza +(2 rows) + +-- it should fail +UPDATE foo SET bar1=bar1+before.bar1 RETURNING before.*; +ERROR: missing FROM-clause entry for table "before" +LINE 1: UPDATE foo SET bar1=bar1+before.bar1 RETURNING before.*; + ^ +UPDATE foo SET bar1=bar1+after.bar1 RETURNING after.*; +ERROR: missing FROM-clause entry for table "after" +LINE 1: UPDATE foo SET bar1=bar1+after.bar1 RETURNING after.*; + ^ +-- test before/after aliases +UPDATE foo AS before SET bar1=bar1+1 RETURNING before.*,after.*; + bar1 | bar2 | bar1 | bar2 +------+------+------+------ + 5 | xzab | 5 | xzab + 6 | yzab | 6 | yzab +(2 rows) + +UPDATE foo AS after SET bar1=bar1-1 RETURNING before.*,after.*; + bar1 | bar2 | bar1 | bar2 +------+------+------+------ + 5 | xzab | 4 | xzab + 6 | yzab | 5 | yzab +(2 rows) + +-- test inheritance +CREATE TABLE foo2 (bar INTEGER) INHERITS(foo); +INSERT INTO foo2 VALUES (1,'b',5); +UPDATE foo2 SET bar1=bar1*2, bar=bar1+5, bar2=bar1::text || bar::text RETURNING before.*, after.*, *; + bar1 | bar2 | bar | bar1 | bar2 | bar | bar1 | bar2 | bar +------+------+-----+------+------+-----+------+------+----- + 1 | b | 5 | 2 | 15 | 6 | 2 | 15 | 6 +(1 row) + +UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'z' RETURNING before.*, after.*; + bar1 | bar2 | bar1 | bar2 +------+------+------+------- + 4 | xzab | 5 | xzabz + 5 | yzab | 6 | yzabz + 2 | 15 | 3 | 15z +(3 rows) + +-- check views +CREATE VIEW view_foo AS SELECT * FROM foo; +UPDATE view_foo SET bar1=bar1+1 RETURNING before.*, bar1, bar2; + bar1 | bar2 | bar1 | bar2 +------+-------+------+------- + 5 | xzabz | 6 | xzabz + 6 | yzabz | 7 | yzabz + 3 | 15z | 4 | 15z +(3 rows) + +CREATE TABLE foo3 (bar1 INTEGER, bar4 FLOAT); +INSERT INTO foo2 VALUES (2, 'asdf', 33); +INSERT INTO foo3 VALUES (2, 7.77); +CREATE VIEW view_join AS SELECT f2.*, f3.bar1 AS f1bar1, f3.bar4 FROM foo2 f2 +JOIN foo3 f3 ON f2.bar1 = f3.bar1; +UPDATE view_join SET bar1=bar1+5, bar2=bar2||'join', bar=bar1*2, bar4=7 RETURNING before.*, after.*; +ERROR: cannot update view "view_join" +DETAIL: Views that do not select from a single table or view are not automatically updatable. +HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule. +-- check triggers +CREATE FUNCTION returning_trig() returns trigger as $$ +BEGIN +NEW.bar1 = NEW.bar1*NEW.bar1; +RETURN NEW; +END; $$ language plpgsql; +DROP TABLE foo2 CASCADE; +NOTICE: drop cascades to view view_join +CREATE TRIGGER bef_foo BEFORE UPDATE ON foo FOR EACH ROW EXECUTE PROCEDURE returning_trig(); +UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'z' RETURNING before.*, after.*, *; + bar1 | bar2 | bar1 | bar2 | bar1 | bar2 +------+-------+------+--------+------+-------- + 6 | xzabz | 7 | xzabzz | 49 | xzabzz + 7 | yzabz | 8 | yzabzz | 64 | yzabzz +(2 rows) + +DROP TABLE foo CASCADE; +NOTICE: drop cascades to view view_foo +DROP TABLE foo3 CASCADE; +CREATE TABLE t1 (id serial, x int, y int, z int); +CREATE TABLE t2 (id serial, x int, y int, z int); +INSERT INTO t1 VALUES (DEFAULT,1,2,3); +INSERT INTO t1 VALUES (DEFAULT,4,5,6); +-- check WITH statement +WITH foo AS (UPDATE t1 SET x=x*2, y=y+1, z=x+y+z RETURNING BEFORE.x, BEFORE.y, AFTER.z) INSERT INTO t2 (x,y,z) SELECT x, y, z FROM foo RETURNING *; + id | x | y | z +----+---+---+---- + 1 | 1 | 2 | 6 + 2 | 4 | 5 | 15 +(2 rows) + +-- check UPDATE ... FROM statement +UPDATE t2 SET x = t1.x+2 FROM t1 WHERE t2.id=t1.id RETURNING after.x, before.x; + x | x +----+--- + 4 | 1 + 10 | 4 +(2 rows) + +UPDATE t2 SET x = t1.x*2 FROM t1 WHERE t2.id=t1.id RETURNING after.*, before.*; + id | x | y | z | id | x | y | z +----+----+---+----+----+----+---+---- + 1 | 4 | 2 | 6 | 1 | 4 | 2 | 6 + 2 | 16 | 5 | 15 | 2 | 10 | 5 | 15 +(2 rows) + +DROP TABLE t1; +DROP TABLE t2; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index fd08e8d..1169d27 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -107,5 +107,8 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo # ---------- test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml +test: returning_before_after + # run stats by itself because its delay may be insufficient under heavy load test: stats + diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 1ed059b..8aa6243 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -136,6 +136,7 @@ test: sequence test: polymorphism test: rowtypes test: returning +test: returning_before_after test: largeobject test: with test: xml diff --git a/src/test/regress/sql/returning_before_after.sql b/src/test/regress/sql/returning_before_after.sql new file mode 100644 index 0000000..0d7a0ac --- /dev/null +++ b/src/test/regress/sql/returning_before_after.sql @@ -0,0 +1,86 @@ +-- +-- Test BEFORE/AFTER feature in RETURNING statements + +CREATE TABLE foo ( + bar1 INTEGER, + bar2 TEXT + ); + +INSERT INTO foo VALUES (1, 'x'),(2,'y'); + +UPDATE foo SET bar1=bar1+1 RETURNING before.*, bar1, bar2; + +UPDATE foo SET bar1=bar1-1 RETURNING after.bar1, before.bar1*2; + +UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'z' RETURNING before.*, after.*; + +-- check single after + +UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'a' RETURNING after.*; + +-- check single before + +UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'b' RETURNING before.*; + +-- it should fail +UPDATE foo SET bar1=bar1+before.bar1 RETURNING before.*; +UPDATE foo SET bar1=bar1+after.bar1 RETURNING after.*; + +-- test before/after aliases +UPDATE foo AS before SET bar1=bar1+1 RETURNING before.*,after.*; +UPDATE foo AS after SET bar1=bar1-1 RETURNING before.*,after.*; + +-- test inheritance +CREATE TABLE foo2 (bar INTEGER) INHERITS(foo); + +INSERT INTO foo2 VALUES (1,'b',5); + +UPDATE foo2 SET bar1=bar1*2, bar=bar1+5, bar2=bar1::text || bar::text RETURNING before.*, after.*, *; +UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'z' RETURNING before.*, after.*; + +-- check views + +CREATE VIEW view_foo AS SELECT * FROM foo; + +UPDATE view_foo SET bar1=bar1+1 RETURNING before.*, bar1, bar2; + +CREATE TABLE foo3 (bar1 INTEGER, bar4 FLOAT); + +INSERT INTO foo2 VALUES (2, 'asdf', 33); +INSERT INTO foo3 VALUES (2, 7.77); + +CREATE VIEW view_join AS SELECT f2.*, f3.bar1 AS f1bar1, f3.bar4 FROM foo2 f2 +JOIN foo3 f3 ON f2.bar1 = f3.bar1; + +UPDATE view_join SET bar1=bar1+5, bar2=bar2||'join', bar=bar1*2, bar4=7 RETURNING before.*, after.*; + +-- check triggers +CREATE FUNCTION returning_trig() returns trigger as $$ +BEGIN +NEW.bar1 = NEW.bar1*NEW.bar1; +RETURN NEW; +END; $$ language plpgsql; + +DROP TABLE foo2 CASCADE; +CREATE TRIGGER bef_foo BEFORE UPDATE ON foo FOR EACH ROW EXECUTE PROCEDURE returning_trig(); + +UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'z' RETURNING before.*, after.*, *; + +DROP TABLE foo CASCADE; +DROP TABLE foo3 CASCADE; + +CREATE TABLE t1 (id serial, x int, y int, z int); +CREATE TABLE t2 (id serial, x int, y int, z int); + +INSERT INTO t1 VALUES (DEFAULT,1,2,3); +INSERT INTO t1 VALUES (DEFAULT,4,5,6); + +-- check WITH statement +WITH foo AS (UPDATE t1 SET x=x*2, y=y+1, z=x+y+z RETURNING BEFORE.x, BEFORE.y, AFTER.z) INSERT INTO t2 (x,y,z) SELECT x, y, z FROM foo RETURNING *; + +-- check UPDATE ... FROM statement +UPDATE t2 SET x = t1.x+2 FROM t1 WHERE t2.id=t1.id RETURNING after.x, before.x; +UPDATE t2 SET x = t1.x*2 FROM t1 WHERE t2.id=t1.id RETURNING after.*, before.*; + +DROP TABLE t1; +DROP TABLE t2;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers