On Sun, Feb 02, 2014 at 02:52:42PM -0800, David Fetter wrote:
> On Wed, Aug 21, 2013 at 08:52:25PM +0200, Karol Trzcionka wrote:
> > W dniu 21.08.2013 19:17, Boszormenyi Zoltan pisze:
> > > With this fixed, a more complete review:
> > Thanks.
> 
> I've done some syntactic and white space cleanup, here attached.
> 
> Karol, would you care to help with commenting the sections that need
> same?

Karol,

Thanks for the updates :)

Other folks,

Next version attached.

Cheers,
David.
-- 
David Fetter <da...@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fet...@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml
index 90b9208..5addfc1 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,12 +302,12 @@ 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>
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 86449a6..ad4eecb 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);
@@ -2378,10 +2378,15 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
         * junkfilter's output slot, so we are clobbering the original value of
         * slottuple by doing the filtering.  This is OK since neither we nor 
our
         * caller have any more interest in the prior contents of that slot.
+        *
+        * Execution plan is changed so it is reported up by planSlot,
+        * it is needed to get correct value for BEFORE/AFTER statements
+        * in RETURNING syntax.
         */
        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 6f0f47e..926a80b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -604,12 +604,17 @@ ExecUpdate(ItemPointer tupleid,
        resultRelInfo = estate->es_result_relation_info;
        resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
-       /* BEFORE ROW UPDATE Triggers */
+       /* BEFORE ROW UPDATE Triggers
+        *
+        * Caution: planSlot would change here since the target row
+        * can be modified after planner but before execution
+        * (READ COMMITTED and above)
+        * */
        if (resultRelInfo->ri_TrigDesc &&
                resultRelInfo->ri_TrigDesc->trig_update_before_row)
        {
                slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-                                                                       
tupleid, slot);
+                                                                       
tupleid, slot, &planSlot);
 
                if (slot == NULL)               /* "do nothing" */
                        return NULL;
@@ -749,6 +754,10 @@ lreplace:;
                                                                                
   hufd.xmax);
                                        if (!TupIsNull(epqslot))
                                        {
+                                               /* We need current planSlot in 
original form for BEFORE/AFTER
+                                                * in RETURNING syntax
+                                                */
+                                               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 123f2a6..a5b5bcb 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1999,6 +1999,7 @@ range_table_walker(List *rtable,
                {
                        case RTE_RELATION:
                        case RTE_CTE:
+                       case RTE_ALIAS:
                                /* nothing to do */
                                break;
                        case RTE_SUBQUERY:
@@ -2725,6 +2726,7 @@ range_table_mutator(List *rtable,
                {
                        case RTE_RELATION:
                        case RTE_CTE:
+                       case RTE_ALIAS:
                                /* 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 568c3b8..38bdd43 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2370,6 +2370,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry 
*node)
        switch (node->rtekind)
        {
                case RTE_RELATION:
+               case RTE_ALIAS:
                        WRITE_OID_FIELD(relid);
                        WRITE_CHAR_FIELD(relkind);
                        break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 216d75e..e7a67b2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1212,6 +1212,7 @@ _readRangeTblEntry(void)
        switch (local_node->rtekind)
        {
                case RTE_RELATION:
+               case RTE_ALIAS:
                        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 b57bfd2..b7c23af 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -184,8 +184,23 @@ 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;
+
+                       /* Ignore all variables not attached to real tables
+                        * All vars used by RTE_ALIAS are fetched in fact
+                        * with parent 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;
diff --git a/src/backend/optimizer/plan/planner.c 
b/src/backend/optimizer/plan/planner.c
index 35bda67..bbb4632 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2164,6 +2164,13 @@ preprocess_rowmarks(PlannerInfo *root)
                if (rte->relkind == RELKIND_FOREIGN_TABLE)
                        continue;
 
+               /*
+                * Simirarly, ignore all marks for aliases since they are not 
real tables
+                * All the work is done for parent RTE (RTE_ALIAS is never 
executed alone)
+                */
+               if (rte->rtekind == RTE_ALIAS)
+                       continue;
+
                rels = bms_del_member(rels, rc->rti);
 
                newrc = makeNode(PlanRowMark);
@@ -2203,6 +2210,14 @@ preprocess_rowmarks(PlannerInfo *root)
                if (!bms_is_member(i, rels))
                        continue;
 
+               /*
+                * Ignore all rowmarks for RTE_ALIAS since it is done already
+                * (all will be done here) for the parent table and isn't needed
+                * for alias
+                */
+               if (rte->rtekind == RTE_ALIAS)
+                       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 46affe7..d30981f 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 before, int after);
 
 
 /*****************************************************************************
@@ -1712,8 +1713,13 @@ 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;
+                       /*
+                        * We mustn't adjust RTE_ALIAS since we do it later 
while binding to target variables
+                        * We need original position of vars because in the 
other case it will point to nowhere
+                        */
+                       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,6 +1869,49 @@ fix_upper_expr_mutator(Node *node, 
fix_upper_expr_context *context)
 }
 
 /*
+ * 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,7 +1947,27 @@ set_returning_clause_references(PlannerInfo *root,
                                                                int rtoffset)
 {
        indexed_tlist *itlist;
+       int after_index=0, before_index=0;
+       Query      *parse = root->parse;
 
+       ListCell   *rt;
+       RangeTblEntry *bef;
+
+       int index_rel = 1;
+
+       /*
+        * We are looking for parsed position of BEFORE/AFTER aliases to handle
+        * correct the adjustment of variables related to them.
+        */
+       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,6 +1991,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 812e56d..1b79dca 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -650,6 +650,13 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
                RangeTblEntry *rte = rt_fetch(varno, root->parse->rtable);
 
                /*
+                * Ignore parsing RTE_ALIAS since it is not real table
+                * and all related variables are already parsed with parent 
table
+                */
+               if (rte->rtekind == RTE_ALIAS)
+                       return NULL;
+
+               /*
                 * Is this a subquery RTE, and if so, is the subquery simple 
enough to
                 * pull up?
                 *
@@ -998,6 +1005,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, 
RangeTblEntry *rte,
                                case RTE_RELATION:
                                case RTE_JOIN:
                                case RTE_CTE:
+                               case RTE_ALIAS:
                                        /* these can't contain any lateral 
references */
                                        break;
                        }
@@ -1644,6 +1652,7 @@ replace_vars_in_jointree(Node *jtnode,
                                        case RTE_RELATION:
                                        case RTE_JOIN:
                                        case RTE_CTE:
+                                       case RTE_ALIAS:
                                                /* these shouldn't be marked 
LATERAL */
                                                Assert(false);
                                                break;
@@ -1797,6 +1806,23 @@ pullup_replace_vars_callback(Var *var,
                /* Make a copy of the tlist item to return */
                newnode = copyObject(tle->expr);
 
+               /*
+                * We need to preserve original position of variable
+                * because for RTE_ALIAS 'old' position is related to OUTER_VAR 
and MATTERS
+                *
+                * For other statements it is not needed so are ignored
+                */
+               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)
                {
diff --git a/src/backend/optimizer/prep/preptlist.c 
b/src/backend/optimizer/prep/preptlist.c
index ee773b8..ba04554 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -165,6 +165,29 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
                                var->varno == result_relation)
                                continue;               /* don't need it */
 
+
+                       /*
+                        * We need add new target for all BEFORE variables
+                        * in RETURNING syntax because in other cases the old 
value
+                        * is forgot and not available in OUTER_VAR
+                        *
+                        * For AFTER it is not required since all fields of the 
parent
+                        * is prepared to return
+                        */
+                       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 */
 
diff --git a/src/backend/optimizer/util/relnode.c 
b/src/backend/optimizer/util/relnode.c
index 8ae8f55..e8b6b54 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_ALIAS:
+                       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,14 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
                        elog(ERROR, "unexpected node type in reltargetlist: %d",
                                 (int) nodeTag(var));
 
+               /*
+                * Variable is related to BEFORE/AFTER in RETURNING
+                * so is is impossible to find them here (and in fact it is 
unnecessary)
+                */
+               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);
 
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index d629fcd..255660e 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -699,6 +699,21 @@ flatten_join_alias_vars_mutator(Node *node,
                newvar = copyObject(newvar);
 
                /*
+                * We need to preserve more info about position since the 
BEFORE/AFTER
+                * refers to the OUTER_VAR and use 'old' to store info about it
+                */
+               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
                 * adjust its varlevelsup fields.
                 */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 7225bb6..abf1fbb 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2040,6 +2040,14 @@ transformReturningList(ParseState *pstate, List 
*returningList)
        save_next_resno = pstate->p_next_resno;
        pstate->p_next_resno = 1;
 
+       /*
+        * Add aliases for BEFORE/AFTER statements in RETURNING
+        * They are only valid for UPDATE and only in RETURNING part (so can't
+        * be added earlier)
+        */
+       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 aa704bb..074ae85 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -82,7 +82,61 @@ static WindowClause *findWindowClause(List *wclist, const 
char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
                                         Node *clause);
 
+/*
+ * addAliases -
+ *             add RTEs that are not valid tables,
+ *             they are always related to parent which is the main
+ *             table/view in UPDATE statement
+ *
+ * RTE_ALIASes are ignored in most of the cases in planning/execution
+ */
+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,
diff --git a/src/backend/parser/parse_relation.c 
b/src/backend/parser/parse_relation.c
index 8760952..91e67cd 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1806,6 +1806,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int 
sublevels_up,
        switch (rte->rtekind)
        {
                case RTE_RELATION:
+               case RTE_ALIAS:
                        /* Ordinary relation RTE */
                        expandRelation(rte->relid, rte->eref,
                                                   rtindex, sublevels_up, 
location,
@@ -2335,6 +2336,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber 
attnum,
        switch (rte->rtekind)
        {
                case RTE_RELATION:
+               case RTE_ALIAS:
                        {
                                /* Plain relation RTE --- get the attribute's 
type info */
                                HeapTuple       tp;
@@ -2527,6 +2529,7 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, 
AttrNumber attnum)
        switch (rte->rtekind)
        {
                case RTE_RELATION:
+               case RTE_ALIAS:
                        {
                                /*
                                 * 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 f971c71..6793281 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_ALIAS:
                        /* not a simple relation, leave it unmarked */
                        break;
                case RTE_CTE:
@@ -1424,6 +1425,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int 
levelsup)
        {
                case RTE_RELATION:
                case RTE_VALUES:
+               case RTE_ALIAS:
 
                        /*
                         * 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 add5cd1..ca65900 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5709,6 +5709,7 @@ get_name_for_var_field(Var *var, int fieldno,
        {
                case RTE_RELATION:
                case RTE_VALUES:
+               case RTE_ALIAS:
 
                        /*
                         * This case should not occur: a column of a table or 
values list
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 44d686c..d5bfdc5 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 ad58b39..b3631e4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -715,7 +715,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_ALIAS                                       /* 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 e9e7cdc..6889a94 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -47,5 +47,6 @@ extern List *addTargetToSortList(ParseState *pstate, 
TargetEntry *tle,
                                        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 */
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..f40d440
--- /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_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;
diff --git a/src/test/regress/parallel_schedule 
b/src/test/regress/parallel_schedule
index 5758b07..05935ff 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -105,7 +105,7 @@ 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
+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
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 78348f5..d230273 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -138,6 +138,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..011b7d1
--- /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_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;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to