*** a/doc/src/sgml/ref/update.sgml --- b/doc/src/sgml/ref/update.sgml *************** *** 194,205 **** UPDATE [ ONLY ] table_name [ * ] [ output_expression ! An expression to be computed and returned by the UPDATE ! command after each row is updated. The expression can use any ! column names of the table named by table_name ! or table(s) listed in FROM. ! Write * to return all columns. --- 194,220 ---- output_expression ! An expression to be computed and returned by the ! UPDATE command either before or after (prefixed with ! BEFORE. and AFTER., ! respectively) each row is updated. The expression can use any ! column names of the table named by table_name or table(s) listed in ! FROM. Write AFTER.* to return all ! columns after the update. Write BEFORE.* for all ! columns before the update. Write * 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. + + 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 before or + after, alias it to something else when using + RETURNING. + + *************** *** 287,301 **** UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT ! Perform the same operation and return the updated entries: 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; Use the alternative column-list syntax to do the same update: --- 302,317 ---- ! Perform the same operation and return information on the changed entries: 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 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; + Use the alternative column-list syntax to do the same update: *** a/src/backend/commands/trigger.c --- b/src/backend/commands/trigger.c *************** *** 2335,2341 **** ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo) TupleTableSlot * ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ! ItemPointer tupleid, TupleTableSlot *slot) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; HeapTuple slottuple = ExecMaterializeSlot(slot); --- 2335,2341 ---- TupleTableSlot * ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ! ItemPointer tupleid, TupleTableSlot *slot, TupleTableSlot **planSlot) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; HeapTuple slottuple = ExecMaterializeSlot(slot); *************** *** 2382,2387 **** ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, --- 2382,2388 ---- if (newSlot != NULL) { slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot); + *planSlot = newSlot; slottuple = ExecMaterializeSlot(slot); newtuple = slottuple; } *** a/src/backend/executor/nodeModifyTable.c --- b/src/backend/executor/nodeModifyTable.c *************** *** 609,615 **** ExecUpdate(ItemPointer tupleid, resultRelInfo->ri_TrigDesc->trig_update_before_row) { slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, ! tupleid, slot); if (slot == NULL) /* "do nothing" */ return NULL; --- 609,615 ---- resultRelInfo->ri_TrigDesc->trig_update_before_row) { slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, ! tupleid, slot, &planSlot); if (slot == NULL) /* "do nothing" */ return NULL; *************** *** 749,754 **** lreplace:; --- 749,755 ---- hufd.xmax); if (!TupIsNull(epqslot)) { + planSlot = epqslot; *tupleid = hufd.ctid; slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); tuple = ExecMaterializeSlot(slot); *** a/src/backend/nodes/nodeFuncs.c --- b/src/backend/nodes/nodeFuncs.c *************** *** 1999,2004 **** range_table_walker(List *rtable, --- 1999,2005 ---- { case RTE_RELATION: case RTE_CTE: + case RTE_ALIAS: /* nothing to do */ break; case RTE_SUBQUERY: *************** *** 2725,2730 **** range_table_mutator(List *rtable, --- 2726,2732 ---- { case RTE_RELATION: case RTE_CTE: + case RTE_ALIAS: /* we don't bother to copy eref, aliases, etc; OK? */ break; case RTE_SUBQUERY: *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** *** 2370,2375 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) --- 2370,2376 ---- switch (node->rtekind) { case RTE_RELATION: + case RTE_ALIAS: WRITE_OID_FIELD(relid); WRITE_CHAR_FIELD(relkind); break; *** a/src/backend/nodes/readfuncs.c --- b/src/backend/nodes/readfuncs.c *************** *** 1212,1217 **** _readRangeTblEntry(void) --- 1212,1218 ---- switch (local_node->rtekind) { case RTE_RELATION: + case RTE_ALIAS: READ_OID_FIELD(relid); READ_CHAR_FIELD(relkind); break; *** a/src/backend/optimizer/plan/initsplan.c --- b/src/backend/optimizer/plan/initsplan.c *************** *** 184,191 **** add_vars_to_targetlist(PlannerInfo *root, List *vars, if (IsA(node, Var)) { Var *var = (Var *) node; ! RelOptInfo *rel = find_base_rel(root, var->varno); int attno = var->varattno; if (bms_is_subset(where_needed, rel->relids)) continue; --- 184,201 ---- if (IsA(node, Var)) { Var *var = (Var *) node; ! 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_ALIAS) + continue; + } + rel = find_base_rel(root, varno); if (bms_is_subset(where_needed, rel->relids)) continue; *** a/src/backend/optimizer/plan/planner.c --- b/src/backend/optimizer/plan/planner.c *************** *** 2164,2169 **** preprocess_rowmarks(PlannerInfo *root) --- 2164,2172 ---- if (rte->relkind == RELKIND_FOREIGN_TABLE) continue; + if (rte->rtekind == RTE_ALIAS) + continue; + rels = bms_del_member(rels, rc->rti); newrc = makeNode(PlanRowMark); *************** *** 2203,2208 **** preprocess_rowmarks(PlannerInfo *root) --- 2206,2214 ---- if (!bms_is_member(i, rels)) continue; + if (rte->rtekind == RTE_ALIAS) + continue; + newrc = makeNode(PlanRowMark); newrc->rti = newrc->prti = i; newrc->rowmarkId = ++(root->glob->lastRowMarkId); *** a/src/backend/optimizer/plan/setrefs.c --- b/src/backend/optimizer/plan/setrefs.c *************** *** 134,139 **** static List *set_returning_clause_references(PlannerInfo *root, --- 134,140 ---- 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 before, int after); /***************************************************************************** *************** *** 1712,1719 **** 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; return (Node *) var; } --- 1713,1721 ---- { var = copyVar(var); var->varno += context->rtoffset; ! if (var->varnoold > 0 && ! ((RangeTblEntry *)list_nth(context->root->parse->rtable,var->varnoold-1))->rtekind != RTE_ALIAS) ! var->varnoold += context->rtoffset; return (Node *) var; } *************** *** 1863,1868 **** fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context) --- 1865,1913 ---- } /* + * bind_returning_variables + * Fix description of BEFORE. and AFTER. variables + * + * This replaces each variable generated by parser for "BEFORE." and + * "AFTER." statements. It binds var to the proper places in slot. + * + * 'rlist': the RETURNING targetlist to be fixed + * 'before': index of RTE_ALIAS "before" in rtable + * value 2 in most cases + * 'after': index of RTE_ALIAS "after" in rtable + * value 3 in most cases + */ + static void + bind_returning_variables(List *rlist, int before, int after) + { + 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) && (var->varnoold == after || var->varnoold == before)) + { + var->varno = OUTER_VAR; + var->varattno = var->varoattno; + } + else if (IsA(var, OpExpr)) + bind_returning_variables(((OpExpr*)var)->args, before, after); + else if (IsA(var, FuncExpr)) + bind_returning_variables(((FuncExpr*)var)->args, before, after); + } + } + } + + /* * set_returning_clause_references * Perform setrefs.c's work on a RETURNING targetlist * *************** *** 1898,1904 **** set_returning_clause_references(PlannerInfo *root, --- 1943,1965 ---- int rtoffset) { indexed_tlist *itlist; + int after_index=0, before_index=0; + 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_ALIAS ) + after_index = index_rel; + if (strcmp(bef->eref->aliasname,"before") == 0 && bef->rtekind == RTE_ALIAS ) + 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 *************** *** 1922,1927 **** set_returning_clause_references(PlannerInfo *root, --- 1983,1989 ---- resultRelation, rtoffset); + bind_returning_variables(rlist, before_index, after_index); pfree(itlist); return rlist; *** a/src/backend/optimizer/prep/prepjointree.c --- b/src/backend/optimizer/prep/prepjointree.c *************** *** 649,654 **** pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, --- 649,657 ---- int varno = ((RangeTblRef *) jtnode)->rtindex; RangeTblEntry *rte = rt_fetch(varno, root->parse->rtable); + if (rte->rtekind == RTE_ALIAS) + return NULL; + /* * Is this a subquery RTE, and if so, is the subquery simple enough to * pull up? *************** *** 998,1003 **** pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, --- 1001,1007 ---- case RTE_RELATION: case RTE_JOIN: case RTE_CTE: + case RTE_ALIAS: /* these can't contain any lateral references */ break; } *************** *** 1644,1649 **** replace_vars_in_jointree(Node *jtnode, --- 1648,1654 ---- case RTE_RELATION: case RTE_JOIN: case RTE_CTE: + case RTE_ALIAS: /* these shouldn't be marked LATERAL */ Assert(false); break; *************** *** 1797,1802 **** pullup_replace_vars_callback(Var *var, --- 1802,1818 ---- /* 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_ALIAS) + { + ((Var*)newnode)->varoattno = ((Var*)var)->varoattno; + ((Var*)newnode)->varnoold = ((Var*)var)->varnoold; + } + } + /* Insert PlaceHolderVar if needed */ if (rcon->need_phvs) { *** a/src/backend/optimizer/prep/preptlist.c --- b/src/backend/optimizer/prep/preptlist.c *************** *** 165,170 **** preprocess_targetlist(PlannerInfo *root, List *tlist) --- 165,184 ---- 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_ALIAS) + { + 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 */ *** a/src/backend/optimizer/util/relnode.c --- b/src/backend/optimizer/util/relnode.c *************** *** 136,141 **** build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) --- 136,143 ---- /* Table --- retrieve statistics from the system catalogs */ get_relation_info(root, rte->relid, rte->inh, rel); break; + case RTE_ALIAS: + break; case RTE_SUBQUERY: case RTE_FUNCTION: case RTE_VALUES: *************** *** 487,492 **** build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, --- 489,495 ---- Var *var = (Var *) lfirst(vars); RelOptInfo *baserel; int ndx; + RangeTblEntry *rte; /* * Ignore PlaceHolderVars in the input tlists; we'll make our own *************** *** 504,509 **** build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, --- 507,516 ---- 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_ALIAS) + continue; + /* Get the Var's original base rel */ baserel = find_base_rel(root, var->varno); *** a/src/backend/optimizer/util/var.c --- b/src/backend/optimizer/util/var.c *************** *** 697,702 **** flatten_join_alias_vars_mutator(Node *node, --- 697,712 ---- 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_ALIAS) + { + ((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 *** a/src/backend/parser/analyze.c --- b/src/backend/parser/analyze.c *************** *** 2040,2045 **** transformReturningList(ParseState *pstate, List *returningList) --- 2040,2048 ---- 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); *** a/src/backend/parser/parse_clause.c --- b/src/backend/parser/parse_clause.c *************** *** 82,88 **** static WindowClause *findWindowClause(List *wclist, const char *name); --- 82,134 ---- static Node *transformFrameOffset(ParseState *pstate, int frameOptions, Node *clause); + void + addAliases(ParseState *pstate) + { + const int n_aliases = 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 < n_aliases; 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 < n_aliases; i++) + { + if (aliases[i]) + { + rte = makeNode(RangeTblEntry); + rte->eref = makeAlias(aliases[i], nsitem->p_rte->eref->colnames); + rte->inh = INH_NO; + rte->rtekind = RTE_ALIAS; + rte->relkind = RELKIND_RELATION; + 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, *** a/src/backend/parser/parse_relation.c --- b/src/backend/parser/parse_relation.c *************** *** 1806,1811 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, --- 1806,1812 ---- switch (rte->rtekind) { case RTE_RELATION: + case RTE_ALIAS: /* Ordinary relation RTE */ expandRelation(rte->relid, rte->eref, rtindex, sublevels_up, location, *************** *** 2335,2340 **** get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, --- 2336,2342 ---- switch (rte->rtekind) { case RTE_RELATION: + case RTE_ALIAS: { /* Plain relation RTE --- get the attribute's type info */ HeapTuple tp; *************** *** 2527,2532 **** get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) --- 2529,2535 ---- switch (rte->rtekind) { case RTE_RELATION: + case RTE_ALIAS: { /* * Plain relation RTE --- get the attribute's catalog entry *** a/src/backend/parser/parse_target.c --- b/src/backend/parser/parse_target.c *************** *** 317,322 **** markTargetListOrigin(ParseState *pstate, TargetEntry *tle, --- 317,323 ---- break; case RTE_FUNCTION: case RTE_VALUES: + case RTE_ALIAS: /* not a simple relation, leave it unmarked */ break; case RTE_CTE: *************** *** 1424,1429 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup) --- 1425,1431 ---- { case RTE_RELATION: case RTE_VALUES: + case RTE_ALIAS: /* * This case should not occur: a column of a table or values list *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** *** 5709,5714 **** get_name_for_var_field(Var *var, int fieldno, --- 5709,5715 ---- { case RTE_RELATION: case RTE_VALUES: + case RTE_ALIAS: /* * This case should not occur: a column of a table or values list *** a/src/include/commands/trigger.h --- b/src/include/commands/trigger.h *************** *** 162,168 **** extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tupleid, ! TupleTableSlot *slot); extern void ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, --- 162,169 ---- EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tupleid, ! TupleTableSlot *slot, ! TupleTableSlot **planSlot); extern void ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 715,721 **** typedef enum RTEKind RTE_JOIN, /* join */ RTE_FUNCTION, /* function in FROM */ RTE_VALUES, /* VALUES (), (), ... */ ! RTE_CTE /* common table expr (WITH list element) */ } RTEKind; typedef struct RangeTblEntry --- 715,722 ---- RTE_JOIN, /* join */ RTE_FUNCTION, /* function in FROM */ RTE_VALUES, /* VALUES (), (), ... */ ! RTE_CTE, /* common table expr (WITH list element) */ ! RTE_ALIAS /* for before/after statements */ } RTEKind; typedef struct RangeTblEntry *** a/src/include/parser/parse_clause.h --- b/src/include/parser/parse_clause.h *************** *** 47,51 **** extern List *addTargetToSortList(ParseState *pstate, TargetEntry *tle, --- 47,52 ---- bool resolveUnknown); 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 */ *** /dev/null --- b/src/test/regress/expected/returning_before_after.out *************** *** 0 **** --- 1,152 ---- + -- + -- Test BEFORE/AFTER feature in RETURNING statements + CREATE TABLE foo_ret ( + bar1 INTEGER, + bar2 TEXT + ); + INSERT INTO foo_ret VALUES (1, 'x'),(2,'y'); + UPDATE foo_ret SET bar1=bar1+1 RETURNING before.*, bar1, bar2; + bar1 | bar2 | bar1 | bar2 + ------+------+------+------ + 1 | x | 2 | x + 2 | y | 3 | y + (2 rows) + + UPDATE foo_ret SET bar1=bar1-1 RETURNING after.bar1, before.bar1*2; + bar1 | ?column? + ------+---------- + 1 | 4 + 2 | 6 + (2 rows) + + UPDATE foo_ret 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_ret SET bar1=bar1+1, bar2=bar2 || 'a' RETURNING after.*; + bar1 | bar2 + ------+------ + 3 | xza + 4 | yza + (2 rows) + + -- check single before + UPDATE foo_ret SET bar1=bar1+1, bar2=bar2 || 'b' RETURNING before.*; + bar1 | bar2 + ------+------ + 3 | xza + 4 | yza + (2 rows) + + -- it should fail + UPDATE foo_ret SET bar1=bar1+before.bar1 RETURNING before.*; + ERROR: missing FROM-clause entry for table "before" + LINE 1: UPDATE foo_ret SET bar1=bar1+before.bar1 RETURNING before.*; + ^ + UPDATE foo_ret SET bar1=bar1+after.bar1 RETURNING after.*; + ERROR: missing FROM-clause entry for table "after" + LINE 1: UPDATE foo_ret SET bar1=bar1+after.bar1 RETURNING after.*; + ^ + -- test before/after aliases + UPDATE foo_ret 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_ret 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 foo_ret2_ret (bar INTEGER) INHERITS(foo_ret); + INSERT INTO foo_ret2_ret VALUES (1,'b',5); + UPDATE foo_ret2_ret 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_ret 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_ret AS SELECT * FROM foo_ret; + UPDATE view_foo_ret 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 foo_ret3 (bar1 INTEGER, bar4 FLOAT); + INSERT INTO foo_ret2_ret VALUES (2, 'asdf', 33); + INSERT INTO foo_ret3 VALUES (2, 7.77); + CREATE VIEW view_join AS SELECT f2.*, f3.bar1 AS f1bar1, f3.bar4 FROM foo_ret2_ret f2 + JOIN foo_ret3 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 foo_ret2_ret CASCADE; + NOTICE: drop cascades to view view_join + CREATE TRIGGER bef_foo_ret BEFORE UPDATE ON foo_ret FOR EACH ROW EXECUTE PROCEDURE returning_trig(); + UPDATE foo_ret 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_ret CASCADE; + NOTICE: drop cascades to view view_foo_ret + DROP TABLE foo_ret3 CASCADE; + CREATE TABLE t1_ret (id serial, x int, y int, z int); + CREATE TABLE t2_ret (id serial, x int, y int, z int); + INSERT INTO t1_ret VALUES (DEFAULT,1,2,3); + INSERT INTO t1_ret VALUES (DEFAULT,4,5,6); + -- check WITH statement + WITH foo_ret AS (UPDATE t1_ret SET x=x*2, y=y+1, z=x+y+z RETURNING BEFORE.x, BEFORE.y, AFTER.z) INSERT INTO t2_ret (x,y,z) SELECT x, y, z FROM foo_ret RETURNING *; + id | x | y | z + ----+---+---+---- + 1 | 1 | 2 | 6 + 2 | 4 | 5 | 15 + (2 rows) + + -- check UPDATE ... FROM statement + UPDATE t2_ret SET x = t1_ret.x+2 FROM t1_ret WHERE t2_ret.id=t1_ret.id RETURNING after.x, before.x; + x | x + ----+--- + 4 | 1 + 10 | 4 + (2 rows) + + UPDATE t2_ret SET x = t1_ret.x*2 FROM t1_ret WHERE t2_ret.id=t1_ret.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_ret; + DROP TABLE t2_ret; *** a/src/test/regress/parallel_schedule --- b/src/test/regress/parallel_schedule *************** *** 105,111 **** test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo # NB: temp.sql does a reconnect which transiently uses 2 connections, # so keep this parallel group to at most 19 tests # ---------- ! test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml # run stats by itself because its delay may be insufficient under heavy load test: stats --- 105,111 ---- # NB: temp.sql does a reconnect which transiently uses 2 connections, # so keep this parallel group to at most 19 tests # ---------- ! test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml returning_before_after # run stats by itself because its delay may be insufficient under heavy load test: stats *** a/src/test/regress/serial_schedule --- b/src/test/regress/serial_schedule *************** *** 138,143 **** test: sequence --- 138,144 ---- test: polymorphism test: rowtypes test: returning + test: returning_before_after test: largeobject test: with test: xml *** /dev/null --- b/src/test/regress/sql/returning_before_after.sql *************** *** 0 **** --- 1,86 ---- + -- + -- Test BEFORE/AFTER feature in RETURNING statements + + CREATE TABLE foo_ret ( + bar1 INTEGER, + bar2 TEXT + ); + + INSERT INTO foo_ret VALUES (1, 'x'),(2,'y'); + + UPDATE foo_ret SET bar1=bar1+1 RETURNING before.*, bar1, bar2; + + UPDATE foo_ret SET bar1=bar1-1 RETURNING after.bar1, before.bar1*2; + + UPDATE foo_ret SET bar1=bar1+1, bar2=bar2 || 'z' RETURNING before.*, after.*; + + -- check single after + + UPDATE foo_ret SET bar1=bar1+1, bar2=bar2 || 'a' RETURNING after.*; + + -- check single before + + UPDATE foo_ret SET bar1=bar1+1, bar2=bar2 || 'b' RETURNING before.*; + + -- it should fail + UPDATE foo_ret SET bar1=bar1+before.bar1 RETURNING before.*; + UPDATE foo_ret SET bar1=bar1+after.bar1 RETURNING after.*; + + -- test before/after aliases + UPDATE foo_ret AS before SET bar1=bar1+1 RETURNING before.*,after.*; + UPDATE foo_ret AS after SET bar1=bar1-1 RETURNING before.*,after.*; + + -- test inheritance + CREATE TABLE foo_ret2_ret (bar INTEGER) INHERITS(foo_ret); + + INSERT INTO foo_ret2_ret VALUES (1,'b',5); + + UPDATE foo_ret2_ret SET bar1=bar1*2, bar=bar1+5, bar2=bar1::text || bar::text RETURNING before.*, after.*, *; + UPDATE foo_ret SET bar1=bar1+1, bar2=bar2 || 'z' RETURNING before.*, after.*; + + -- check views + + CREATE VIEW view_foo_ret AS SELECT * FROM foo_ret; + + UPDATE view_foo_ret SET bar1=bar1+1 RETURNING before.*, bar1, bar2; + + CREATE TABLE foo_ret3 (bar1 INTEGER, bar4 FLOAT); + + INSERT INTO foo_ret2_ret VALUES (2, 'asdf', 33); + INSERT INTO foo_ret3 VALUES (2, 7.77); + + CREATE VIEW view_join AS SELECT f2.*, f3.bar1 AS f1bar1, f3.bar4 FROM foo_ret2_ret f2 + JOIN foo_ret3 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 foo_ret2_ret CASCADE; + CREATE TRIGGER bef_foo_ret BEFORE UPDATE ON foo_ret FOR EACH ROW EXECUTE PROCEDURE returning_trig(); + + UPDATE foo_ret SET bar1=bar1+1, bar2=bar2 || 'z' RETURNING before.*, after.*, *; + + DROP TABLE foo_ret CASCADE; + DROP TABLE foo_ret3 CASCADE; + + CREATE TABLE t1_ret (id serial, x int, y int, z int); + CREATE TABLE t2_ret (id serial, x int, y int, z int); + + INSERT INTO t1_ret VALUES (DEFAULT,1,2,3); + INSERT INTO t1_ret VALUES (DEFAULT,4,5,6); + + -- check WITH statement + WITH foo_ret AS (UPDATE t1_ret SET x=x*2, y=y+1, z=x+y+z RETURNING BEFORE.x, BEFORE.y, AFTER.z) INSERT INTO t2_ret (x,y,z) SELECT x, y, z FROM foo_ret RETURNING *; + + -- check UPDATE ... FROM statement + UPDATE t2_ret SET x = t1_ret.x+2 FROM t1_ret WHERE t2_ret.id=t1_ret.id RETURNING after.x, before.x; + UPDATE t2_ret SET x = t1_ret.x*2 FROM t1_ret WHERE t2_ret.id=t1_ret.id RETURNING after.*, before.*; + + DROP TABLE t1_ret; + DROP TABLE t2_ret;