GSOC13 proposal - extend RETURNING syntax

Started by Karol Trzcionkaover 12 years ago77 messages
#1Karol Trzcionka
karlikt@gmail.com

Hello,
I'm student who want to participate in Google Summer of Code. I want to
implement feature which allows to get old values directly from update
statement. I mean there should be possibility to choose the value
immedietly before or after update in RETURNING statement. The syntax may
be realized as "aliases". That means: OLD keywordswould be alias to row
before update and NEW to row after update. The conclusion of syntax is:
UPDATE foo SET bar=bar+1 RETURNING OLD.bar AS old_bar, NEW.bar AS new_bar;
UPDATE foo SET ... RETURNING NEW.* will be equivalent to UPDATE foo SET
... RETURNING foo.*
It may be possible to add similar syntax to DELETE and INSERT statements
but I'm not sure if it makes a sense (OLD for DELETE will be alias to
row before delete, NEW for INSERT will be alias to row after insert and
all triggers - however what about NEW for delete and OLD for INSERT?).
Additionally NEW and OLD values will be reserved keywords (it might be
some capability problem since in new PostgreSQL it isn't reserved -
however standard says it is and in old PgSQL it was).
I'd like to hear (read) yours feedback about syntax and/or implement
issues related to this proposal.
Regards,
Karol Trzcionka

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#2David Fetter
david@fetter.org
In reply to: Karol Trzcionka (#1)
Re: GSOC13 proposal - extend RETURNING syntax

On Thu, May 02, 2013 at 11:04:15AM +0200, Karol Trzcionka wrote:

Hello,
I'm student who want to participate in Google Summer of Code. I want to
implement feature which allows to get old values directly from update
statement. I mean there should be possibility to choose the value
immedietly before or after update in RETURNING statement. The syntax may
be realized as "aliases". That means: OLD keywordswould be alias to row
before update and NEW to row after update. The conclusion of syntax is:
UPDATE foo SET bar=bar+1 RETURNING OLD.bar AS old_bar, NEW.bar AS new_bar;
UPDATE foo SET ... RETURNING NEW.* will be equivalent to UPDATE foo SET
... RETURNING foo.*
It may be possible to add similar syntax to DELETE and INSERT statements
but I'm not sure if it makes a sense (OLD for DELETE will be alias to
row before delete, NEW for INSERT will be alias to row after insert and
all triggers - however what about NEW for delete and OLD for INSERT?).
Additionally NEW and OLD values will be reserved keywords (it might be
some capability problem since in new PostgreSQL it isn't reserved -
however standard says it is and in old PgSQL it was).
I'd like to hear (read) yours feedback about syntax and/or implement
issues related to this proposal.
Regards,
Karol Trzcionka

I would like to include the proposal as we've hammered it out together
on IRC and on GSoC site below.

Cheers,
David.

1. As the SQL standard mandates that OLD and NEW be reserved words, we'll re-reserve them.

2. Let's make OLD and NEW have the same meaning that INSERT/UPDATE/DELETE have when returning rows from the changed table. In particular

INSERT INTO foo (...) RETURNING NEW.*

will be equivalent to

INSERT INTO foo(...) RETURNING foo.*

Similarly for UPDATE and DELETE:

UPDATE foo SET ... RETURNING NEW.*

will be equivalent to

UPDATE foo SET ... RETURNING foo.*

and

DELETE FROM foo ... RETURNING OLD.*

will be equivalent to

DELETE FROM foo ... RETURNING foo.*

As RETURNING clauses have access to everything in the FROM/USING clause, it is important to limit the NEW/OLD rows as being only those in the table being written to in the statement.

3. Let's add an option to UPDATE so that it can RETURN OLD with the same characteristics as above, namely that it refers only to constants and columns in the updated table and not to everything available from the USING clause if included.

--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Fetter (#2)
Re: GSOC13 proposal - extend RETURNING syntax

David Fetter <david@fetter.org> writes:

1. As the SQL standard mandates that OLD and NEW be reserved words, we'll re-reserve them.

As I mentioned yesterday, I'm not exactly thrilled with re-reserving
those, and especially not NEW as it is utterly unnecessary (since the
default would already be to return the NEW column).

It should in any case be possible to do this without converting them to
reserved words; rather the implementation could be that those table
aliases are made available when parsing the UPDATE RETURNING expressions.
(This is, in fact, the way that rules use these names now.) Probably it
should work something like "add these aliases if they don't already
exist in the query", so as to avoid breaking existing applications.

I don't really see a lot of value in hacking the behavior of either
INSERT RETURNING or DELETE RETURNING.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Karol Trzcionka
karlikt@gmail.com
In reply to: Tom Lane (#3)
Re: GSOC13 proposal - extend RETURNING syntax

W dniu 02.05.2013 17:17, Tom Lane pisze:

It should in any case be possible to do this without converting them
to reserved words; rather the implementation could be that those table
aliases are made available when parsing the UPDATE RETURNING
expressions. (This is, in fact, the way that rules use these names
now.) Probably it should work something like "add these aliases if
they don't already exist in the query", so as to avoid breaking
existing applications. I don't really see a lot of value in hacking
the behavior of either INSERT RETURNING or DELETE RETURNING. regards,
tom lane

I'm not sure about it. If it is not reserved keyword how can user get
old value from table named "old". The new value is not a problem
(doesn't conflict) but what should happened in statement:
UPDATE old SET foo=foo+1 RETURNING old.foo;
If it returns old value, it'll break capability. If it returns new value
(as now), there is no possibility to get old value (and user cries
because of broken feature).
Regards,
Karol Trzcionka

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Marko Tiikkaja
marko@joh.to
In reply to: Karol Trzcionka (#4)
Re: GSOC13 proposal - extend RETURNING syntax

On 2013-05-02 17:32, Karol Trzcionka wrote:

W dniu 02.05.2013 17:17, Tom Lane pisze:

It should in any case be possible to do this without converting them
to reserved words; rather the implementation could be that those table
aliases are made available when parsing the UPDATE RETURNING
expressions. (This is, in fact, the way that rules use these names
now.) Probably it should work something like "add these aliases if
they don't already exist in the query", so as to avoid breaking
existing applications. I don't really see a lot of value in hacking
the behavior of either INSERT RETURNING or DELETE RETURNING.

what should happened in statement:
UPDATE old SET foo=foo+1 RETURNING old.foo;
If it returns old value, it'll break capability. If it returns new value
(as now), there is no possibility to get old value (and user cries
because of broken feature).

In Tom's proposal that would give you the "new" value.

Personally I would maybe prefer reserving NEW/OLD, but if we go through
with Tom's idea, this should work:

UPDATE old myold SET foo=foo+1 RETURNING myold.foo, old.foo;

What I'm more interested in is: how can we make this feature work in
PL/PgSQL where OLD means something different?

Regards,
Marko Tiikkaja

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Marko Tiikkaja (#5)
Re: GSOC13 proposal - extend RETURNING syntax

Marko Tiikkaja <marko@joh.to> writes:

What I'm more interested in is: how can we make this feature work in
PL/PgSQL where OLD means something different?

That's a really good point: if you follow this approach then you're
creating fundamental conflicts for use of the feature in trigger
functions or rules, which will necessarily have conflicting uses of
those names. Yeah, we could define scoping rules such that there's
an unambiguous interpretation, but then the user is just out of luck
if he wants to reference the other definition. (This problem is
probably actually worse if you implement with reserved words rather
than aliases.)

I'm thinking it would be better to invent some other notation for access
to old-row values.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Andres Freund
andres@2ndquadrant.com
In reply to: Tom Lane (#6)
Re: GSOC13 proposal - extend RETURNING syntax

On 2013-05-02 12:23:19 -0400, Tom Lane wrote:

Marko Tiikkaja <marko@joh.to> writes:

What I'm more interested in is: how can we make this feature work in
PL/PgSQL where OLD means something different?

That's a really good point: if you follow this approach then you're
creating fundamental conflicts for use of the feature in trigger
functions or rules, which will necessarily have conflicting uses of
those names. Yeah, we could define scoping rules such that there's
an unambiguous interpretation, but then the user is just out of luck
if he wants to reference the other definition. (This problem is
probably actually worse if you implement with reserved words rather
than aliases.)

I'm thinking it would be better to invent some other notation for access
to old-row values.

prior/after? Both are unreserved keywords atm and it seems far less
likely to have conflicts than new/old.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8David Fetter
david@fetter.org
In reply to: Andres Freund (#7)
Re: GSOC13 proposal - extend RETURNING syntax

On Thu, May 02, 2013 at 06:28:53PM +0200, Andres Freund wrote:

On 2013-05-02 12:23:19 -0400, Tom Lane wrote:

Marko Tiikkaja <marko@joh.to> writes:

What I'm more interested in is: how can we make this feature work in
PL/PgSQL where OLD means something different?

That's a really good point: if you follow this approach then you're
creating fundamental conflicts for use of the feature in trigger
functions or rules, which will necessarily have conflicting uses of
those names. Yeah, we could define scoping rules such that there's
an unambiguous interpretation, but then the user is just out of luck
if he wants to reference the other definition. (This problem is
probably actually worse if you implement with reserved words rather
than aliases.)

I'm thinking it would be better to invent some other notation for access
to old-row values.

prior/after? Both are unreserved keywords atm and it seems far less
likely to have conflicts than new/old.

BEFORE/AFTER seems more logical to me. Yes, those words both have
meaning in, for example, a trigger definition, but they're clearly
separable by context.

Yay, bike-shedding!

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#6)
Re: GSOC13 proposal - extend RETURNING syntax

2013/5/2 Tom Lane <tgl@sss.pgh.pa.us>

Marko Tiikkaja <marko@joh.to> writes:

What I'm more interested in is: how can we make this feature work in
PL/PgSQL where OLD means something different?

That's a really good point: if you follow this approach then you're
creating fundamental conflicts for use of the feature in trigger
functions or rules, which will necessarily have conflicting uses of
those names. Yeah, we could define scoping rules such that there's
an unambiguous interpretation, but then the user is just out of luck
if he wants to reference the other definition. (This problem is
probably actually worse if you implement with reserved words rather
than aliases.)

I'm thinking it would be better to invent some other notation for access
to old-row values.

I am not sure, but I am thinking so NEW and OLD are used in some statements
and features ANSI SQL 2012, so probably we should to do keywords from these
words if we would to support modern ANSI SQL

Regards

Pavel

Show quoted text

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Fetter (#8)
Re: GSOC13 proposal - extend RETURNING syntax

David Fetter <david@fetter.org> writes:

On Thu, May 02, 2013 at 06:28:53PM +0200, Andres Freund wrote:

prior/after? Both are unreserved keywords atm and it seems far less
likely to have conflicts than new/old.

BEFORE/AFTER seems more logical to me.

Works for me.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Karol Trzcionka
karlikt@gmail.com
In reply to: Tom Lane (#10)
Re: GSOC13 proposal - extend RETURNING syntax

W dniu 02.05.2013 19:40, Tom Lane pisze:

BEFORE/AFTER seems more logical to me.

Works for me.

What do you think about function- or cast-like syntax. I mean:
RETURNING OLD(foo.bar)
or RETURNING OLD(foo).bar
or RETURNING (foo::OLD).bar ?
I think none of them should conflict with any other statements.
Regards,
Karol Trzcionka

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12David Fetter
david@fetter.org
In reply to: Tom Lane (#10)
Re: GSOC13 proposal - extend RETURNING syntax

On Thu, May 02, 2013 at 01:40:59PM -0400, Tom Lane wrote:

David Fetter <david@fetter.org> writes:

On Thu, May 02, 2013 at 06:28:53PM +0200, Andres Freund wrote:

prior/after? Both are unreserved keywords atm and it seems far less
likely to have conflicts than new/old.

BEFORE/AFTER seems more logical to me.

Works for me.

regards, tom lane

Maybe we can make BEFORE and AFTER implied aliases rather than
keywords. What say?

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Atri Sharma
atri.jiit@gmail.com
In reply to: David Fetter (#12)
Re: GSOC13 proposal - extend RETURNING syntax

Sent from my iPad

On 03-May-2013, at 0:07, David Fetter <david@fetter.org> wrote:

On Thu, May 02, 2013 at 01:40:59PM -0400, Tom Lane wrote:

David Fetter <david@fetter.org> writes:

On Thu, May 02, 2013 at 06:28:53PM +0200, Andres Freund wrote:

prior/after? Both are unreserved keywords atm and it seems far less
likely to have conflicts than new/old.

BEFORE/AFTER seems more logical to me.

Works for me.

regards, tom lane

Maybe we can make BEFORE and AFTER implied aliases rather than
keywords. What say?

I agree.Overall,I like the concept.

Regards,

Atri

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Fetter (#12)
Re: GSOC13 proposal - extend RETURNING syntax

David Fetter <david@fetter.org> writes:

Maybe we can make BEFORE and AFTER implied aliases rather than
keywords. What say?

Well, they still have to be unreserved keywords for their use in
trigger-related commands, but as far as this feature is concerned
I think they're best off being handled as automatically-supplied
aliases. IOW the patch needn't touch gram.y at all.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Karol Trzcionka (#11)
Re: GSOC13 proposal - extend RETURNING syntax

Karol Trzcionka <karlikt@gmail.com> writes:

What do you think about function- or cast-like syntax. I mean:
RETURNING OLD(foo.bar)
or RETURNING OLD(foo).bar
or RETURNING (foo::OLD).bar ?
I think none of them should conflict with any other statements.

I think you'll find those alternatives are at least as ugly to
implement as they are to look at ...

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Karol Trzcionka
karlikt@gmail.com
In reply to: Tom Lane (#14)
Re: GSOC13 proposal - extend RETURNING syntax

W dniu 02.05.2013 20:45, Tom Lane pisze:

David Fetter <david@fetter.org> writes:

Maybe we can make BEFORE and AFTER implied aliases rather than
keywords. What say?

Well, they still have to be unreserved keywords for their use in
trigger-related commands, but as far as this feature is concerned
I think they're best off being handled as automatically-supplied
aliases. IOW the patch needn't touch gram.y at all.

Well... the syntax which wouldn't conflict with Pl/PgSQL is (draft):
INSERT INTO foo ... RETURNING AFTER.*
UPDATE foo SET ... RETURNING AFTER.*, BEFORE.*
DELETE FROM foo ... RETURNING BEFORE.*
It will not solve the problems:
1. How to access to old rows if the table is named "BEFORE"?
2. Should AFTER for DELETE and BEFORE for INSERT be allowed? If yes what
should they return? I mean: what should be result of:
INSERT INTO foo ... RETURNING BEFORE.*
and
DELETE FROM foo ... RETURNING AFTER.*
?
The best summarize of dropping NEW/OLD keywords for this proposal was
proposed while IRC meeting:
create or replace function ft1(integer) returns integer language plpgsql
as $f$ <<x>> declare r rt1; begin select 1 as a into r; update rt1 r set
a = a + 1 returning XXX into r; raise notice 'r = %', r; return null;
end; $f$;
Regards,
Karol Trzcionka

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Gavin Flower
GavinFlower@archidevsys.co.nz
In reply to: David Fetter (#8)
Re: GSOC13 proposal - extend RETURNING syntax

On 03/05/13 04:52, David Fetter wrote:

On Thu, May 02, 2013 at 06:28:53PM +0200, Andres Freund wrote:

On 2013-05-02 12:23:19 -0400, Tom Lane wrote:

Marko Tiikkaja <marko@joh.to> writes:

What I'm more interested in is: how can we make this feature work in
PL/PgSQL where OLD means something different?

That's a really good point: if you follow this approach then you're
creating fundamental conflicts for use of the feature in trigger
functions or rules, which will necessarily have conflicting uses of
those names. Yeah, we could define scoping rules such that there's
an unambiguous interpretation, but then the user is just out of luck
if he wants to reference the other definition. (This problem is
probably actually worse if you implement with reserved words rather
than aliases.)

I'm thinking it would be better to invent some other notation for access
to old-row values.

prior/after? Both are unreserved keywords atm and it seems far less
likely to have conflicts than new/old.

BEFORE/AFTER seems more logical to me. Yes, those words both have
meaning in, for example, a trigger definition, but they're clearly
separable by context.

Yay, bike-shedding!

Cheers,
David.

I prefer 'PRIOR & 'AFTER' as the both have the same length
- but perhaps that is just me! :-)

Cheers,
Gavin

#18Karol Trzcionka
karlikt@gmail.com
In reply to: Gavin Flower (#17)
Re: GSOC13 proposal - extend RETURNING syntax

W dniu 02.05.2013 23:34, Gavin Flower pisze:

I prefer 'PRIOR & 'AFTER' as the both have the same length
- but perhaps that is just me! :-)

I think it doesn't matter at now because PRIOR has the same problems as
BEFORE ;)
Regards,
Karol

#19Tom Lane
tgl@sss.pgh.pa.us
In reply to: Karol Trzcionka (#16)
Re: GSOC13 proposal - extend RETURNING syntax

Karol Trzcionka <karlikt@gmail.com> writes:

It will not solve the problems:
1. How to access to old rows if the table is named "BEFORE"?

The user can simply choose to use a different table alias, as Andres
explained upthread. If any table's active alias is "before" (or
"after"), we just don't activate the corresponding implicit alias.

2. Should AFTER for DELETE and BEFORE for INSERT be allowed? If yes what
should they return?

These cases should certainly fail. Now, IMO there's no very good reason
to alter the behavior at all for INSERT/DELETE; only UPDATE has an issue
here. But if we were going to support the extra aliases in those
commands, only the ones that actually make sense should be allowed.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Karol Trzcionka
karlikt@gmail.com
In reply to: Tom Lane (#19)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

Hello,
according to my mentor's suggestion, I send first PoC patch of
"RETURNING AFTER/BEFORE" statement. Some info:
- it is early version - more hack PoC than RC patch
- AFTER in this version works as decribed before but it returns row
after update but before post-update before (to be fixed or switch with
current "*" behaviour - I don't know yet)
- patch passes all regression tests
- the code is uncommented already, to be fixed
I'm not sure what may fail so you use it on your risk ;)
Regards,
Karol

Attachments:

before_after_poc.patchtext/x-patch; name=before_after_poc.patchDownload
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..2698535 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2351,6 +2351,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 3a16e9d..04931ee 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1189,6 +1189,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 839ed9d..7fc6ea1 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -174,9 +174,16 @@ 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;
 			int			attno = var->varattno;
+			RangeTblEntry *rte;
 
+			if (root->parse->commandType == CMD_UPDATE){
+				rte = ((RangeTblEntry *) list_nth(root->parse->rtable, (var->varno)-1));
+				if(rte->rtekind == RTE_BEFORE)
+					continue;
+			}
+			rel = find_base_rel(root, var->varno);
 			Assert(attno >= rel->min_attr && attno <= rel->max_attr);
 			attno -= rel->min_attr;
 			if (rel->attr_needed[attno] == NULL)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d80c264..77b0c38 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1989,6 +1989,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);
@@ -2028,6 +2031,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..6828a7d 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1691,6 +1691,12 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
 	if (IsA(node, Var))
 	{
 		Var		   *var = (Var *) node;
+		if (var->varno<=list_length(context->root->parse->rtable) && var->varno>1 && context->root->parse->commandType == CMD_UPDATE){
+			RangeTblEntry *rte_a,*rte_b;
+			rte_a = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-1);
+			rte_b = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-2);
+			if (rte_a->rtekind == RTE_BEFORE && rte_b->rtekind == RTE_BEFORE) var->varno-=1;
+		}
 
 		/* First look for the var in the input tlists */
 		newvar = search_indexed_tlist_for_var(var,
@@ -1892,6 +1898,37 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
  *
  * Note: resultRelation is not yet adjusted by rtoffset.
  */
+
+void fix_varno_varattno(List *rlist, int begin, int bef, int aft){
+	ListCell   *temp;
+	ListCell   *temp2;
+	Var *var;
+	foreach(temp, rlist){
+		TargetEntry *tle = (TargetEntry *)lfirst(temp);
+
+		if(IsA(tle, TargetEntry)){
+			var = (Var*)tle->expr;
+		}else if(IsA(tle, Var)) var=(Var*)tle;
+		if( IsA(var, Var) ){
+			if(var->varnoold == bef){
+				var->varno = OUTER_VAR;
+				var->varattno = var->varoattno + begin;
+			}
+			else if(var->varnoold == aft)
+			{
+				 var->varno = OUTER_VAR;
+                 var->varattno = var->varoattno;
+			}
+		}
+		else if( IsA(var, OpExpr )){
+			fix_varno_varattno(((OpExpr*)var)->args, begin, bef, aft);
+		}
+		else if( IsA(var, FuncExpr )){
+			fix_varno_varattno(((FuncExpr*)var)->args, begin, bef, aft);
+		}
+	}
+}
+
 static List *
 set_returning_clause_references(PlannerInfo *root,
 								List *rlist,
@@ -1900,7 +1937,48 @@ set_returning_clause_references(PlannerInfo *root,
 								int rtoffset)
 {
 	indexed_tlist *itlist;
+	int before_index=0, after_index=0, var_index=0;
+	Query      *parse = root->parse;
+
+	ListCell   *rt;
+	RangeTblEntry *bef;
 
+	TargetEntry *tr;
+
+	int index_rel=1;
+	int index_var=0;
+
+
+	foreach(rt,parse->rtable)
+	{
+		bef = (RangeTblEntry *)lfirst(rt);
+		if(strcmp(bef->eref->aliasname,"before") == 0 && bef->rtekind == RTE_BEFORE )
+		{
+			before_index = index_rel;
+		}
+		else if(strcmp(bef->eref->aliasname,"after") == 0 && bef->rtekind == RTE_BEFORE )
+		{
+			after_index = index_rel;
+		}
+		index_rel++;
+	}
+
+	foreach(rt, topplan->targetlist)
+	{
+		Var *var;
+		tr = (TargetEntry*)lfirst(rt);
+		var = tr->expr;
+		if(IsA(var,Var))
+		{
+			if(var->varnoold == before_index && var->varattno == 1)
+			{
+				var_index = index_var;
+				break;
+			}
+		}
+
+		index_var++;
+	}
 	/*
 	 * 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 +2002,9 @@ set_returning_clause_references(PlannerInfo *root,
 						  resultRelation,
 						  rtoffset);
 
+
+
+	fix_varno_varattno(rlist, var_index, 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 5284293..5e3f06d 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -648,6 +648,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?
@@ -753,6 +756,35 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
 	return jtnode;
 }
 
+void prepare_returning_before(PlannerInfo *root, List *ret, int varno)
+{
+	ListCell *v;
+	Var *var;
+	List *rtable = root->parse->rtable;
+	RangeTblEntry *rte;
+	TargetEntry *target;
+	foreach(v,ret)
+	{
+		target = (TargetEntry*)lfirst(v);
+		if(IsA(target,TargetEntry))
+		{
+			var = (Var*)target->expr;
+			if(IsA(var,Var))
+			{
+				if (var->varno <= list_length(rtable))
+				{
+					rte = (RangeTblEntry*)list_nth(rtable,var->varno-1);
+					if(rte->rtekind == RTE_BEFORE)
+					{
+						var->varno=varno;
+					}
+				}
+
+			}
+		}
+	}
+}
+
 /*
  * pull_up_simple_subquery
  *		Attempt to pull up a single simple subquery.
@@ -912,6 +944,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->targetList = (List *)
 		pullup_replace_vars((Node *) parse->targetList, &rvcontext);
+
+	prepare_returning_before(root,parse->returningList,varno);
 	parse->returningList = (List *)
 		pullup_replace_vars((Node *) parse->returningList, &rvcontext);
 	replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 8ee5671..28c5ff7 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -135,6 +135,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:
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 16ff234..fd51a0e 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2003,6 +2003,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 cbfb431..bcc3f91 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -85,7 +85,54 @@ 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])
+			if (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 a9254c8..e57fccf 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1658,6 +1658,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,
@@ -2113,6 +2114,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;
@@ -2273,6 +2275,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/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/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index de22dff..fa5083c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -698,7 +698,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 */
#21Arulappan, Arul Shaji
arul@fast.au.fujitsu.com
In reply to: Karol Trzcionka (#20)
Proposal - Support for National Characters functionality

This is a proposal to implement functionalities for the handling of
National Characters.

[Introduction]

The aim of this proposal is to eventually have a way to represent
'National Characters' in a uniform way, even in non-UTF8 encoded
databases. Many of our customers in the Asian region who are now, as
part of their platform modernization, are moving away from mainframes
where they have used National Characters representation in COBOL and
other databases. Having stronger support for national characters
representation will also make it easier for these customers to look at
PostgreSQL more favourably when migrating from other well known RDBMSs
who all have varying degrees of NCHAR/NVARCHAR support.

[Specifications]

Broadly speaking, the national characters implementation ideally will
include the following
- Support for NCHAR/NVARCHAR data types
- Representing NCHAR and NVARCHAR columns in UTF-8 encoding in non-UTF8
databases
- Support for UTF16 column encoding and representing NCHAR and NVARCHAR
columns in UTF16 encoding in all databases.
- Support for NATIONAL_CHARACTER_SET GUC variable that will determine
the encoding that will be used in NCHAR/NVARCHAR columns.

The above points are at the moment a 'wishlist' only. Our aim is to
tackle them one-by-one as we progress. I will send a detailed proposal
later with more technical details.

The main aim at the moment is to get some feedback on the above to know
if this feature is something that would benefit PostgreSQL in general,
and if users maintaining DBs in non-English speaking regions will find
this beneficial.

Rgds,
Arul Shaji

P.S.: It has been quite some time since I send a correspondence to this
list. Our mail server adds a standard legal disclaimer to all outgoing
mails, which I know that this list is not a huge fan of. I used to have
an exemption for the mails I send to this list. If the disclaimer
appears, apologies in advance. I will rectify that on the next one.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Tatsuo Ishii
ishii@postgresql.org
In reply to: Arulappan, Arul Shaji (#21)
Re: Proposal - Support for National Characters functionality

Arul Shaji,

NCHAR support is on our TODO list for some time and I would like to
welcome efforts trying to implement it. However I have a few
questions:

This is a proposal to implement functionalities for the handling of
National Characters.

[Introduction]

The aim of this proposal is to eventually have a way to represent
'National Characters' in a uniform way, even in non-UTF8 encoded
databases. Many of our customers in the Asian region who are now, as
part of their platform modernization, are moving away from mainframes
where they have used National Characters representation in COBOL and
other databases. Having stronger support for national characters
representation will also make it easier for these customers to look at
PostgreSQL more favourably when migrating from other well known RDBMSs
who all have varying degrees of NCHAR/NVARCHAR support.

[Specifications]

Broadly speaking, the national characters implementation ideally will
include the following
- Support for NCHAR/NVARCHAR data types
- Representing NCHAR and NVARCHAR columns in UTF-8 encoding in non-UTF8
databases

I think this is not a trivial work because we do not have framework to
allow mixed encodings in a database. I'm interested in how you are
going to solve the problem.

- Support for UTF16 column encoding and representing NCHAR and NVARCHAR
columns in UTF16 encoding in all databases.

Why do yo need UTF-16 as the database encoding? UTF-8 is already
supported, and any UTF-16 character can be represented in UTF-8 as far
as I know.

- Support for NATIONAL_CHARACTER_SET GUC variable that will determine
the encoding that will be used in NCHAR/NVARCHAR columns.

You said NCHAR's encoding is UTF-8. Why do you need the GUC if NCHAR's
encoding is fixed to UTF-8?

The above points are at the moment a 'wishlist' only. Our aim is to
tackle them one-by-one as we progress. I will send a detailed proposal
later with more technical details.

The main aim at the moment is to get some feedback on the above to know
if this feature is something that would benefit PostgreSQL in general,
and if users maintaining DBs in non-English speaking regions will find
this beneficial.

Rgds,
Arul Shaji

P.S.: It has been quite some time since I send a correspondence to this
list. Our mail server adds a standard legal disclaimer to all outgoing
mails, which I know that this list is not a huge fan of. I used to have
an exemption for the mails I send to this list. If the disclaimer
appears, apologies in advance. I will rectify that on the next one.

--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese: http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Claudio Freire
klaussfreire@gmail.com
In reply to: Tatsuo Ishii (#22)
Re: Proposal - Support for National Characters functionality

On Fri, Jul 5, 2013 at 2:02 AM, Tatsuo Ishii <ishii@postgresql.org> wrote:

- Support for NATIONAL_CHARACTER_SET GUC variable that will determine
the encoding that will be used in NCHAR/NVARCHAR columns.

You said NCHAR's encoding is UTF-8. Why do you need the GUC if NCHAR's
encoding is fixed to UTF-8?

Not only that, but I don't think it can be a GUC. Maybe a compile-time
switch, but if it were a GUC, how do you handle an existing database
in UTF-8 when the setting is switched to UTF-16? Re-encode everything?
Store the encoding along each value? It's a mess.

Either fix it at UTF-8, or make it a compile-time thing, I'd say.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Arulappan, Arul Shaji
arul@fast.au.fujitsu.com
In reply to: Tatsuo Ishii (#22)
Re: Proposal - Support for National Characters functionality

Ishii san,

Thank you for your positive and early response.

-----Original Message-----
From: Tatsuo Ishii [mailto:ishii@postgresql.org]
Sent: Friday, 5 July 2013 3:02 PM
To: Arulappan, Arul Shaji
Cc: pgsql-hackers@postgresql.org
Subject: Re: [HACKERS] Proposal - Support for National Characters
functionality

Arul Shaji,

NCHAR support is on our TODO list for some time and I would like to

welcome

efforts trying to implement it. However I have a few
questions:

This is a proposal to implement functionalities for the handling of
National Characters.

[Introduction]

The aim of this proposal is to eventually have a way to represent
'National Characters' in a uniform way, even in non-UTF8 encoded
databases. Many of our customers in the Asian region who are now, as
part of their platform modernization, are moving away from

mainframes

where they have used National Characters representation in COBOL and
other databases. Having stronger support for national characters
representation will also make it easier for these customers to look

at

PostgreSQL more favourably when migrating from other well known

RDBMSs

who all have varying degrees of NCHAR/NVARCHAR support.

[Specifications]

Broadly speaking, the national characters implementation ideally

will

include the following
- Support for NCHAR/NVARCHAR data types
- Representing NCHAR and NVARCHAR columns in UTF-8 encoding in
non-UTF8 databases

I think this is not a trivial work because we do not have framework to

allow

mixed encodings in a database. I'm interested in how you are going to

solve

the problem.

I would be lying if I said I have the design already speced out. I will
be working on this in the coming weeks and hope to design a working
solution consulting with the community.

- Support for UTF16 column encoding and representing NCHAR and
NVARCHAR columns in UTF16 encoding in all databases.

Why do yo need UTF-16 as the database encoding? UTF-8 is already

supported,

and any UTF-16 character can be represented in UTF-8 as far as I know.

Yes, that's correct. However there are advantages in using UTF-16
encoding for those characters that are always going to take atleast
two-bytes to represent.

Having said that, my intention is to use UTF-8 for NCHAR as well.
Supporting UTF-16 will be even more complicated as it is not supported
natively in some Linux platforms. I only included it to give an option.

- Support for NATIONAL_CHARACTER_SET GUC variable that will

determine

the encoding that will be used in NCHAR/NVARCHAR columns.

You said NCHAR's encoding is UTF-8. Why do you need the GUC if NCHAR's
encoding is fixed to UTF-8?

If we are going to only support UTF-8 for NCHAR, then we don't need the
GUC variable obviously.

Rgds,
Arul Shaji

The above points are at the moment a 'wishlist' only. Our aim is to
tackle them one-by-one as we progress. I will send a detailed

proposal

later with more technical details.

The main aim at the moment is to get some feedback on the above to
know if this feature is something that would benefit PostgreSQL in
general, and if users maintaining DBs in non-English speaking

regions

will find this beneficial.

Rgds,
Arul Shaji

P.S.: It has been quite some time since I send a correspondence to
this list. Our mail server adds a standard legal disclaimer to all
outgoing mails, which I know that this list is not a huge fan of. I
used to have an exemption for the mails I send to this list. If the
disclaimer appears, apologies in advance. I will rectify that on the

next

one.
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese: http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Arulappan, Arul Shaji
arul@fast.au.fujitsu.com
In reply to: Claudio Freire (#23)
Re: Proposal - Support for National Characters functionality

-----Original Message-----
From: Claudio Freire [mailto:klaussfreire@gmail.com]
Sent: Friday, 5 July 2013 3:41 PM
To: Tatsuo Ishii
Cc: Arulappan, Arul Shaji; PostgreSQL-Dev
Subject: Re: [HACKERS] Proposal - Support for National Characters
functionality

On Fri, Jul 5, 2013 at 2:02 AM, Tatsuo Ishii <ishii@postgresql.org>

wrote:

- Support for NATIONAL_CHARACTER_SET GUC variable that will

determine

the encoding that will be used in NCHAR/NVARCHAR columns.

You said NCHAR's encoding is UTF-8. Why do you need the GUC if

NCHAR's

encoding is fixed to UTF-8?

Not only that, but I don't think it can be a GUC. Maybe a compile-time

switch,

but if it were a GUC, how do you handle an existing database in UTF-8

when the

setting is switched to UTF-16? Re-encode everything?
Store the encoding along each value? It's a mess.

Either fix it at UTF-8, or make it a compile-time thing, I'd say.

Agreed, that to begin with we only support UTF-8 encoding for NCHAR
columns. If that is the case, do we still need a compile time option to
turn on/off NCHAR functionality ? ?

Rgds,
Arul Shaji

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Andrew Dunstan
andrew@dunslane.net
In reply to: Arulappan, Arul Shaji (#24)
Re: Proposal - Support for National Characters functionality

On 07/05/2013 02:12 AM, Arulappan, Arul Shaji wrote:

- Support for UTF16 column encoding and representing NCHAR and
NVARCHAR columns in UTF16 encoding in all databases.

Why do yo need UTF-16 as the database encoding? UTF-8 is already

supported,

and any UTF-16 character can be represented in UTF-8 as far as I know.

Yes, that's correct. However there are advantages in using UTF-16
encoding for those characters that are always going to take atleast
two-bytes to represent.

Any suggestion to store data in utf-16 is likely to be a complete
non-starter. I suggest you research our previously stated requirements
for server side encodings.

cheers

andrew

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27David Fetter
david@fetter.org
In reply to: Karol Trzcionka (#20)
Re: GSOC13 proposal - extend RETURNING syntax

On Fri, Jul 05, 2013 at 03:22:33AM +0200, Karol Trzcionka wrote:

Hello,
according to my mentor's suggestion, I send first PoC patch of
"RETURNING AFTER/BEFORE" statement. Some info:
- it is early version - more hack PoC than RC patch
- AFTER in this version works as decribed before but it returns row
after update but before post-update before (to be fixed or switch with
current "*" behaviour - I don't know yet)
- patch passes all regression tests
- the code is uncommented already, to be fixed
I'm not sure what may fail so you use it on your risk ;)
Regards,
Karol

Karol,

Per discussion in IRC, please follow up with your patch that includes
such documentation, new regression tests, etc., you've written for the
feature.

Thanks! :)

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Peter Eisentraut
peter_e@gmx.net
In reply to: Arulappan, Arul Shaji (#21)
Re: Proposal - Support for National Characters functionality

On 7/4/13 10:11 PM, Arulappan, Arul Shaji wrote:

The main aim at the moment is to get some feedback on the above to know
if this feature is something that would benefit PostgreSQL in general,
and if users maintaining DBs in non-English speaking regions will find
this beneficial.

For European languages, I think everyone has moved to using Unicode, so
the demand for supporting multiple encodings is approaching zero. The
CJK realm might have difference requirements.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#29Pavel Stehule
pavel.stehule@gmail.com
In reply to: Peter Eisentraut (#28)
Re: Proposal - Support for National Characters functionality

Yes, what I know almost all use utf8 without problems. Long time I didn't
see any request for multi encoding support.
Dne 5.7.2013 20:28 "Peter Eisentraut" <peter_e@gmx.net> napsal(a):

Show quoted text

On 7/4/13 10:11 PM, Arulappan, Arul Shaji wrote:

The main aim at the moment is to get some feedback on the above to know
if this feature is something that would benefit PostgreSQL in general,
and if users maintaining DBs in non-English speaking regions will find
this beneficial.

For European languages, I think everyone has moved to using Unicode, so
the demand for supporting multiple encodings is approaching zero. The
CJK realm might have difference requirements.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Karol Trzcionka
karlikt@gmail.com
In reply to: David Fetter (#27)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

Updated patch:
- include sgml
- fix all compiler warnings
- some cleanup
- fix correctness of feature
Regards,
Karol

Attachments:

before_after.patchtext/x-patch; name=before_after.patchDownload
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/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 42d6621..cc68568 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1920,6 +1920,7 @@ range_table_walker(List *rtable,
 		{
 			case RTE_RELATION:
 			case RTE_CTE:
+			case RTE_BEFORE:
 				/* nothing to do */
 				break;
 			case RTE_SUBQUERY:
@@ -2622,6 +2623,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 b2183f4..2698535 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2351,6 +2351,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 3a16e9d..04931ee 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1189,6 +1189,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 839ed9d..7fc6ea1 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -174,9 +174,16 @@ 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;
 			int			attno = var->varattno;
+			RangeTblEntry *rte;
 
+			if (root->parse->commandType == CMD_UPDATE){
+				rte = ((RangeTblEntry *) list_nth(root->parse->rtable, (var->varno)-1));
+				if(rte->rtekind == RTE_BEFORE)
+					continue;
+			}
+			rel = find_base_rel(root, var->varno);
 			Assert(attno >= rel->min_attr && attno <= rel->max_attr);
 			attno -= rel->min_attr;
 			if (rel->attr_needed[attno] == NULL)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d80c264..77b0c38 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1989,6 +1989,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);
@@ -2028,6 +2031,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..b30c6f8 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 fix_varno_varattno(List *rlist, int aft);
 
 
 /*****************************************************************************
@@ -1691,6 +1692,12 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
 	if (IsA(node, Var))
 	{
 		Var		   *var = (Var *) node;
+		if (var->varno<=list_length(context->root->parse->rtable) && var->varno>1 && context->root->parse->commandType == CMD_UPDATE){
+			RangeTblEntry *rte_a,*rte_b;
+			rte_a = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-1);
+			rte_b = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-2);
+			if (rte_a->rtekind == RTE_BEFORE && rte_b->rtekind == RTE_BEFORE) var->varno-=1;
+		}
 
 		/* First look for the var in the input tlists */
 		newvar = search_indexed_tlist_for_var(var,
@@ -1892,6 +1899,35 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
  *
  * Note: resultRelation is not yet adjusted by rtoffset.
  */
+
+void fix_varno_varattno(List *rlist, 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->varno = OUTER_VAR;
+					var->varattno = var->varoattno;
+				}
+			}
+			else if( IsA(var, OpExpr )){
+				fix_varno_varattno(((OpExpr*)var)->args, aft);
+			}
+			else if( IsA(var, FuncExpr )){
+				fix_varno_varattno(((FuncExpr*)var)->args, aft);
+			}
+		}
+	}
+}
+
 static List *
 set_returning_clause_references(PlannerInfo *root,
 								List *rlist,
@@ -1900,7 +1936,24 @@ set_returning_clause_references(PlannerInfo *root,
 								int rtoffset)
 {
 	indexed_tlist *itlist;
+	int after_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_BEFORE )
+		{
+			after_index = index_rel;
+			break;
+		}
+		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 +1977,7 @@ set_returning_clause_references(PlannerInfo *root,
 						  resultRelation,
 						  rtoffset);
 
+	fix_varno_varattno(rlist, after_index);
 	pfree(itlist);
 
 	return rlist;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 5284293..9a50987 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -103,6 +103,7 @@ static void substitute_multiple_relids(Node *node,
 static void fix_append_rel_relids(List *append_rel_list, int varno,
 					  Relids subrelids);
 static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
+static void prepare_returning_before(PlannerInfo *root, List *ret, int varno);
 
 
 /*
@@ -648,6 +649,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?
@@ -753,6 +757,35 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
 	return jtnode;
 }
 
+void prepare_returning_before(PlannerInfo *root, List *ret, int varno)
+{
+	ListCell *v;
+	Var *var;
+	List *rtable = root->parse->rtable;
+	RangeTblEntry *rte;
+	TargetEntry *target;
+	foreach(v,ret)
+	{
+		target = (TargetEntry*)lfirst(v);
+		if(IsA(target,TargetEntry))
+		{
+			var = (Var*)target->expr;
+			if(IsA(var,Var))
+			{
+				if (var->varno <= list_length(rtable))
+				{
+					rte = (RangeTblEntry*)list_nth(rtable,var->varno-1);
+					if(rte->rtekind == RTE_BEFORE)
+					{
+						var->varno=varno;
+					}
+				}
+
+			}
+		}
+	}
+}
+
 /*
  * pull_up_simple_subquery
  *		Attempt to pull up a single simple subquery.
@@ -912,6 +945,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->targetList = (List *)
 		pullup_replace_vars((Node *) parse->targetList, &rvcontext);
+
+	prepare_returning_before(root,parse->returningList,varno);
 	parse->returningList = (List *)
 		pullup_replace_vars((Node *) parse->returningList, &rvcontext);
 	replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
@@ -980,6 +1015,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;
 			}
@@ -1513,6 +1549,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;
@@ -1666,6 +1703,14 @@ 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){
+			RangeTblEntry *rte = (RangeTblEntry*)list_nth(rcon->root->parse->rtable, ((Var*)var)->varnoold-1);
+			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/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 8ee5671..28c5ff7 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -135,6 +135,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:
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 7eaf8d2..7d686bf 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -688,6 +688,16 @@ flatten_join_alias_vars_mutator(Node *node,
 		Assert(var->varattno > 0);
 		newvar = (Node *) list_nth(rte->joinaliasvars, var->varattno - 1);
 		newvar = copyObject(newvar);
+		if(IsA(newvar,Var) && context->root->parse->commandType == CMD_UPDATE){
+			if(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 16ff234..fd51a0e 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2003,6 +2003,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 cbfb431..bcc3f91 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -85,7 +85,54 @@ 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])
+			if (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 a9254c8..e57fccf 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1658,6 +1658,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,
@@ -2113,6 +2114,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;
@@ -2273,6 +2275,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 ca20e77..b8e08e6 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -316,6 +316,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:
@@ -1421,6 +1422,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 cf9ce3f..da17f98 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5587,6 +5587,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/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index de22dff..fa5083c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -698,7 +698,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 */
#31Karol Trzcionka
karlikt@gmail.com
In reply to: Karol Trzcionka (#30)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

Next version:
- cleanup
- regression test
- fix issue reported by johto (invalid values in parallel transactions)
I would like more feedback and comments about the patch, as some parts
may be too hacky.
In particular, is it a problem that I update a pointer to planSlot? In
my patch, it points to tuple after all updates done between planner and
executor (in fact it is not planSlot right now). I don't know whether
the tuple could be deleted in the intervening time and if the pointer
doesn't point to "unreserved" memory (I mean - memory which may be
overwritten by something meanwhile).
Regards,
Karol

Attachments:

before_after_v2.patchtext/x-patch; name=before_after_v2.patchDownload
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 e934c7b..27859dd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -599,7 +599,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;
@@ -733,6 +733,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 42d6621..cc68568 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1920,6 +1920,7 @@ range_table_walker(List *rtable,
 		{
 			case RTE_RELATION:
 			case RTE_CTE:
+			case RTE_BEFORE:
 				/* nothing to do */
 				break;
 			case RTE_SUBQUERY:
@@ -2622,6 +2623,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 b2183f4..2698535 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2351,6 +2351,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 3a16e9d..04931ee 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1189,6 +1189,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 839ed9d..506d84e 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -174,9 +174,17 @@ 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;
 			int			attno = var->varattno;
+			RangeTblEntry *rte;
 
+			if (root->parse->commandType == CMD_UPDATE)
+			{
+				rte = ((RangeTblEntry *) list_nth(root->parse->rtable, (var->varno)-1));
+				if(rte->rtekind == RTE_BEFORE)
+					continue;
+			}
+			rel = find_base_rel(root, var->varno);
 			Assert(attno >= rel->min_attr && attno <= rel->max_attr);
 			attno -= rel->min_attr;
 			if (rel->attr_needed[attno] == NULL)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d80c264..77b0c38 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1989,6 +1989,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);
@@ -2028,6 +2031,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..5c06d41 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 fix_varno_varattno(List *rlist, int aft);
 
 
 /*****************************************************************************
@@ -1691,6 +1692,15 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
 	if (IsA(node, Var))
 	{
 		Var		   *var = (Var *) node;
+		if (var->varno<=list_length(context->root->parse->rtable) && 
+			var->varno>1 && 
+			context->root->parse->commandType == CMD_UPDATE)
+		{
+			RangeTblEntry *rte_a,*rte_b;
+			rte_a = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-1);
+			rte_b = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-2);
+			if (rte_a->rtekind == RTE_BEFORE && rte_b->rtekind == RTE_BEFORE) var->varno-=1;
+		}
 
 		/* First look for the var in the input tlists */
 		newvar = search_indexed_tlist_for_var(var,
@@ -1892,6 +1902,43 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
  *
  * Note: resultRelation is not yet adjusted by rtoffset.
  */
+
+void fix_varno_varattno(List *rlist, 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->varno = OUTER_VAR;
+					var->varattno = var->varoattno;
+				}
+			}
+			else if( IsA(var, OpExpr ))
+			{
+				fix_varno_varattno(((OpExpr*)var)->args, aft);
+			}
+			else if( IsA(var, FuncExpr ))
+			{
+				fix_varno_varattno(((FuncExpr*)var)->args, aft);
+			}
+		}
+	}
+}
+
 static List *
 set_returning_clause_references(PlannerInfo *root,
 								List *rlist,
@@ -1900,7 +1947,24 @@ set_returning_clause_references(PlannerInfo *root,
 								int rtoffset)
 {
 	indexed_tlist *itlist;
+	int after_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_BEFORE )
+		{
+			after_index = index_rel;
+			break;
+		}
+		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 +1988,7 @@ set_returning_clause_references(PlannerInfo *root,
 						  resultRelation,
 						  rtoffset);
 
+	fix_varno_varattno(rlist, after_index);
 	pfree(itlist);
 
 	return rlist;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 5284293..8c452ad 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -103,6 +103,7 @@ static void substitute_multiple_relids(Node *node,
 static void fix_append_rel_relids(List *append_rel_list, int varno,
 					  Relids subrelids);
 static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
+static void prepare_returning_before(PlannerInfo *root, List *ret, int varno);
 
 
 /*
@@ -648,6 +649,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?
@@ -753,6 +757,35 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
 	return jtnode;
 }
 
+void prepare_returning_before(PlannerInfo *root, List *ret, int varno)
+{
+	ListCell *v;
+	Var *var;
+	List *rtable = root->parse->rtable;
+	RangeTblEntry *rte;
+	TargetEntry *target;
+	foreach(v,ret)
+	{
+		target = (TargetEntry*)lfirst(v);
+		if(IsA(target,TargetEntry))
+		{
+			var = (Var*)target->expr;
+			if(IsA(var,Var))
+			{
+				if (var->varno <= list_length(rtable))
+				{
+					rte = (RangeTblEntry*)list_nth(rtable,var->varno-1);
+					if(rte->rtekind == RTE_BEFORE)
+					{
+						var->varno=varno;
+					}
+				}
+
+			}
+		}
+	}
+}
+
 /*
  * pull_up_simple_subquery
  *		Attempt to pull up a single simple subquery.
@@ -912,6 +945,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->targetList = (List *)
 		pullup_replace_vars((Node *) parse->targetList, &rvcontext);
+
+	prepare_returning_before(root,parse->returningList,varno);
 	parse->returningList = (List *)
 		pullup_replace_vars((Node *) parse->returningList, &rvcontext);
 	replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
@@ -980,6 +1015,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;
 			}
@@ -1513,6 +1549,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;
@@ -1666,6 +1703,19 @@ 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)
+		{
+			if(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/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 8ee5671..28c5ff7 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -135,6 +135,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:
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 7eaf8d2..97fb970 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -688,6 +688,18 @@ flatten_join_alias_vars_mutator(Node *node,
 		Assert(var->varattno > 0);
 		newvar = (Node *) list_nth(rte->joinaliasvars, var->varattno - 1);
 		newvar = copyObject(newvar);
+		if(IsA(newvar,Var) && context->root->parse->commandType == CMD_UPDATE)
+		{
+			if(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 16ff234..fd51a0e 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2003,6 +2003,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 cbfb431..2297045 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -85,7 +85,57 @@ 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])
+				if (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 a9254c8..e57fccf 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1658,6 +1658,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,
@@ -2113,6 +2114,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;
@@ -2273,6 +2275,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 ca20e77..b8e08e6 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -316,6 +316,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:
@@ -1421,6 +1422,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 cf9ce3f..da17f98 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5587,6 +5587,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 de22dff..fa5083c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -698,7 +698,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..abf08df
--- /dev/null
+++ b/src/test/regress/expected/returning_before_after.out
@@ -0,0 +1,99 @@
+--
+-- 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)
+
+-- 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 
+------+------+------+------
+    3 | xz   |    3 | xz
+    4 | yz   |    4 | yz
+(2 rows)
+
+UPDATE foo AS after SET bar1=bar1-1 RETURNING before.*,after.*;
+ bar1 | bar2 | bar1 | bar2 
+------+------+------+------
+    3 | xz   |    2 | xz
+    4 | yz   |    3 | yz
+(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)
+
+-- check views
+CREATE VIEW view_foo AS SELECT * FROM foo;
+UPDATE foo SET bar1=bar1+1 RETURNING before.*, bar1, bar2;
+ bar1 | bar2 | bar1 | bar2 
+------+------+------+------
+    2 | xz   |    3 | xz
+    3 | yz   |    4 | yz
+    2 | 15   |    3 | 15
+(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 make the view updatable, provide an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.
+-- 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 
+------+------+------+------+------+------
+    3 | xz   |    4 | xzz  |   16 | xzz
+    4 | yz   |    5 | yzz  |   25 | yzz
+(2 rows)
+
+DROP TABLE foo CASCADE;
+NOTICE:  drop cascades to view view_foo
+DROP TABLE foo3 CASCADE;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 3e6b306..0b0fd7b 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 3ad289f..d9d63b9 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -135,6 +135,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..adfbfe8
--- /dev/null
+++ b/src/test/regress/sql/returning_before_after.sql
@@ -0,0 +1,64 @@
+--
+-- 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.*;
+
+
+-- 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.*, *;
+
+-- check views
+
+CREATE VIEW view_foo AS SELECT * FROM foo;
+
+UPDATE 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;
+
+
#32David Fetter
david@fetter.org
In reply to: Karol Trzcionka (#31)
Re: GSOC13 proposal - extend RETURNING syntax

On Sat, Jul 13, 2013 at 12:49:45AM +0200, Karol Trzcionka wrote:

Next version:
- cleanup
- regression test
- fix issue reported by johto (invalid values in parallel transactions)
I would like more feedback and comments about the patch, as some parts
may be too hacky.
In particular, is it a problem that I update a pointer to planSlot? In
my patch, it points to tuple after all updates done between planner and
executor (in fact it is not planSlot right now). I don't know whether
the tuple could be deleted in the intervening time and if the pointer
doesn't point to "unreserved" memory (I mean - memory which may be
overwritten by something meanwhile).

Thanks for the updated patch!

Anybody care to look this over for vulnerabilities as described above?

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#29)
Re: Proposal - Support for National Characters functionality

On Fri, Jul 5, 2013 at 2:35 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Yes, what I know almost all use utf8 without problems. Long time I didn't
see any request for multi encoding support.

Well, not *everything* can be represented as UTF-8; I think this is
particularly an issue with Asian languages.

If we chose to do it, I think that per-column encoding support would
end up looking a lot like per-column collation support: it would be
yet another per-column property along with typoid, typmod, and
typcollation. I'm not entirely sure it's worth it, although FWIW I do
believe Oracle has something like this. At any rate, it seems like
quite a lot of work.

Another idea would be to do something like what we do for range types
- i.e. allow a user to declare a type that is a differently-encoded
version of some base type. But even that seems pretty hard.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#34Arulappan, Arul Shaji
arul@fast.au.fujitsu.com
In reply to: Robert Haas (#33)
Re: Proposal - Support for National Characters functionality

On Fri, Jul 5, 2013 at 2:35 PM, Pavel Stehule

<pavel.stehule@gmail.com> wrote:

Yes, what I know almost all use utf8 without problems. Long time I
didn't see any request for multi encoding support.

Well, not *everything* can be represented as UTF-8; I think this is
particularly an issue with Asian languages.

If we chose to do it, I think that per-column encoding support would

end up

looking a lot like per-column collation support: it would be yet

another per-

column property along with typoid, typmod, and typcollation. I'm not

entirely

sure it's worth it, although FWIW I do believe Oracle has something

like this.

Yes, the idea is that users will be able to declare columns of type
NCHAR or NVARCHAR which will use the pre-determined encoding type. If we
say that NCHAR is UTF-8 then the NCHAR column will be of UTF-8 encoding
irrespective of the database encoding. It will be up to us to restrict
what Unicode encodings we want to support for NCHAR/NVARCHAR columns.
This is based on my interpretation of the SQL standard. As you allude to
above, Oracle has a similar behaviour (they support UTF-16 as well).

Support for UTF-16 will be difficult without linking with some external
libraries such as ICU.

At any rate, it seems like quite a lot of work.

Thanks for putting my mind at ease ;-)

Rgds,
Arul Shaji

Another idea would be to do something like what we do for range types
- i.e. allow a user to declare a type that is a differently-encoded

version of

some base type. But even that seems pretty hard.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL

Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Peter Geoghegan
pg@heroku.com
In reply to: Robert Haas (#33)
Re: Proposal - Support for National Characters functionality

On Mon, Jul 15, 2013 at 4:37 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Jul 5, 2013 at 2:35 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Yes, what I know almost all use utf8 without problems. Long time I didn't
see any request for multi encoding support.

Well, not *everything* can be represented as UTF-8; I think this is
particularly an issue with Asian languages.

What cannot be represented as UTF-8? UTF-8 can represent every
character in the Unicode character set, whereas UTF-16 can encode
characters 0 to 0x10FFFF.

Does support for alternative multi-byte encodings have something to do
with the Han unification controversy? I don't know terribly much about
this, so apologies if that's just wrong.

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#36Tatsuo Ishii
ishii@postgresql.org
In reply to: Arulappan, Arul Shaji (#34)
Re: Proposal - Support for National Characters functionality

On Fri, Jul 5, 2013 at 2:35 PM, Pavel Stehule

<pavel.stehule@gmail.com> wrote:

Yes, what I know almost all use utf8 without problems. Long time I
didn't see any request for multi encoding support.

Well, not *everything* can be represented as UTF-8; I think this is
particularly an issue with Asian languages.

If we chose to do it, I think that per-column encoding support would

end up

looking a lot like per-column collation support: it would be yet

another per-

column property along with typoid, typmod, and typcollation. I'm not

entirely

sure it's worth it, although FWIW I do believe Oracle has something

like this.

Yes, the idea is that users will be able to declare columns of type
NCHAR or NVARCHAR which will use the pre-determined encoding type. If we
say that NCHAR is UTF-8 then the NCHAR column will be of UTF-8 encoding
irrespective of the database encoding. It will be up to us to restrict
what Unicode encodings we want to support for NCHAR/NVARCHAR columns.
This is based on my interpretation of the SQL standard. As you allude to
above, Oracle has a similar behaviour (they support UTF-16 as well).

Support for UTF-16 will be difficult without linking with some external
libraries such as ICU.

Can you please elaborate more on this? Why do you exactly need ICU?

Also I don't understand why you need UTF-16 support as a database
encoding because UTF-8 and UTF-16 are logically equivalent, they are
just different represention (encoding) of Unicode. That means if we
already support UTF-8 (I'm sure we already do), there's no particular
reason we need to add UTF-16 support.

Maybe you just want to support UTF-16 as a client encoding?
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese: http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Peter Geoghegan
pg@heroku.com
In reply to: Tatsuo Ishii (#36)
Re: Proposal - Support for National Characters functionality

On Mon, Jul 15, 2013 at 8:58 AM, Tatsuo Ishii <ishii@postgresql.org> wrote:

Also I don't understand why you need UTF-16 support as a database
encoding because UTF-8 and UTF-16 are logically equivalent, they are
just different represention (encoding) of Unicode. That means if we
already support UTF-8 (I'm sure we already do), there's no particular
reason we need to add UTF-16 support.

To be fair, there is a small reason to support UTF-16 even with UTF-8
available. I personally do not find it compelling, but perhaps I am
not best placed to judge such things. As Wikipedia says on the the
English UTF-8 article:

"Characters U+0800 through U+FFFF use three bytes in UTF-8, but only
two in UTF-16. As a result, text in (for example) Chinese, Japanese or
Hindi could take more space in UTF-8 if there are more of these
characters than there are ASCII characters. This happens for pure text
but rarely for HTML documents. For example, both the Japanese UTF-8
and the Hindi Unicode articles on Wikipedia take more space in UTF-16
than in UTF-8."

This is the only advantage of UTF-16 over UTF-8 as a server encoding.
I'm inclined to take the fact that there has been so few (no?)
complaints from PostgreSQL's large Japanese user-base about the lack
of UTF-16 support as suggesting that that isn't considered to be a
compelling feature in the CJK realm.

--
Peter Geoghegan

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Tatsuo Ishii
ishii@postgresql.org
In reply to: Peter Geoghegan (#35)
Re: Proposal - Support for National Characters functionality

On Mon, Jul 15, 2013 at 4:37 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Jul 5, 2013 at 2:35 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Yes, what I know almost all use utf8 without problems. Long time I didn't
see any request for multi encoding support.

Well, not *everything* can be represented as UTF-8; I think this is
particularly an issue with Asian languages.

What cannot be represented as UTF-8? UTF-8 can represent every
character in the Unicode character set, whereas UTF-16 can encode
characters 0 to 0x10FFFF.

Does support for alternative multi-byte encodings have something to do
with the Han unification controversy? I don't know terribly much about
this, so apologies if that's just wrong.

There's a famous problem regarding conversion between Unicode and other
encodings, such as Shift Jis.

There are lots of discussion on this. Here is the one from Microsoft:

http://support.microsoft.com/kb/170559/EN-US
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese: http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#39Peter Eisentraut
peter_e@gmx.net
In reply to: Arulappan, Arul Shaji (#34)
Re: Proposal - Support for National Characters functionality

On 7/15/13 1:26 AM, Arulappan, Arul Shaji wrote:

Yes, the idea is that users will be able to declare columns of type
NCHAR or NVARCHAR which will use the pre-determined encoding type. If we
say that NCHAR is UTF-8 then the NCHAR column will be of UTF-8 encoding
irrespective of the database encoding. It will be up to us to restrict
what Unicode encodings we want to support for NCHAR/NVARCHAR columns.

I would try implementing this as an extension at first, with a new data
type that is internally encoded differently. We have citext as
precedent for successfully implementing text-like data types in user space.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#40Martijn van Oosterhout
kleptog@svana.org
In reply to: Tatsuo Ishii (#38)
Re: Proposal - Support for National Characters functionality

On Mon, Jul 15, 2013 at 05:11:40PM +0900, Tatsuo Ishii wrote:

Does support for alternative multi-byte encodings have something to do
with the Han unification controversy? I don't know terribly much about
this, so apologies if that's just wrong.

There's a famous problem regarding conversion between Unicode and other
encodings, such as Shift Jis.

There are lots of discussion on this. Here is the one from Microsoft:

http://support.microsoft.com/kb/170559/EN-US

Apart from Shift-JIS not being a well defined (it's more a family of
encodings) it has the unusual feature of providing multiple ways to
encode the same character. This is not even a Han unification issue,
they have largely been addressed. For example, the square-root symbol
exists twice (0x8795 and 0x81E3) and many other mathmatical symbols
also.

Here's the code page which you can browse online:

http://msdn.microsoft.com/en-us/goglobal/cc305152

Which means to be round-trippable Unicode would have to double those
characters, but this would make it hard/impossible to round-trip with
any other character set that had those characters. No easy solution
here.

Something that has been done before [1]Python does a similar trick to handle filenames coming from disk in an unknown encoding: http://docs.python.org/3/howto/unicode.html#files-in-an-unknown-encoding is to map the doubles to the
custom area of the unicode space (0xe000-0xffff). It gives you
round-trip support at the expense of having to handle those characters
yourself. But since postgres doesn't do anything meaningful with
unicode characters this might be acceptable.

[1]: Python does a similar trick to handle filenames coming from disk in an unknown encoding: http://docs.python.org/3/howto/unicode.html#files-in-an-unknown-encoding
an unknown encoding:
http://docs.python.org/3/howto/unicode.html#files-in-an-unknown-encoding

Have a nice day,
--
Martijn van Oosterhout <kleptog@svana.org> http://svana.org/kleptog/

He who writes carelessly confesses thereby at the very outset that he does
not attach much importance to his own thoughts.

-- Arthur Schopenhauer

#41Karol Trzcionka
karlikt@gmail.com
In reply to: David Fetter (#32)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

New version:
- fix returning "after" values if there are not "before"
- add more regression tests
I'd like to hear/read any feedback ;)
Regards,
Karol

Attachments:

before_after_v3.patchtext/x-patch; name=before_after_v3.patchDownload
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 a896d76..409b4d1 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1928,6 +1928,7 @@ range_table_walker(List *rtable,
 		{
 			case RTE_RELATION:
 			case RTE_CTE:
+			case RTE_BEFORE:
 				/* nothing to do */
 				break;
 			case RTE_SUBQUERY:
@@ -2642,6 +2643,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 48cd9dc..79b03af 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2366,6 +2366,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 dc9cb3e..2ca29da 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 839ed9d..506d84e 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -174,9 +174,17 @@ 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;
 			int			attno = var->varattno;
+			RangeTblEntry *rte;
 
+			if (root->parse->commandType == CMD_UPDATE)
+			{
+				rte = ((RangeTblEntry *) list_nth(root->parse->rtable, (var->varno)-1));
+				if(rte->rtekind == RTE_BEFORE)
+					continue;
+			}
+			rel = find_base_rel(root, var->varno);
 			Assert(attno >= rel->min_attr && attno <= rel->max_attr);
 			attno -= rel->min_attr;
 			if (rel->attr_needed[attno] == NULL)
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 01e2fa3..65e4a75 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2015,6 +2015,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);
@@ -2054,6 +2057,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..ea15981 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 fix_varno_varattno(List *rlist, int aft);
 
 
 /*****************************************************************************
@@ -1691,6 +1692,35 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
 	if (IsA(node, Var))
 	{
 		Var		   *var = (Var *) node;
+		if (var->varno<=list_length(context->root->parse->rtable) && 
+			var->varno>1 && 
+			context->root->parse->commandType == CMD_UPDATE)
+		{
+			RangeTblEntry *rte_a, *rte_r;
+			rte_a = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-1);
+			if (rte_a->rtekind == RTE_BEFORE && strcmp(rte_a->eref->aliasname,"after") == 0)
+			{
+				if (var->varno > 2)
+				{
+					rte_r = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-3);
+					if(rte_r->rtekind == RTE_RELATION && rte_r->relid == rte_a->relid)
+						var->varno -= 2;
+
+					else
+					{
+						rte_r = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-2);
+						if(rte_r->rtekind == RTE_RELATION && rte_r->relid == rte_a->relid)
+							var->varno -= 1;
+					}
+				}
+				else
+				{
+					rte_r = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-2);
+					if(rte_r->rtekind == RTE_RELATION && rte_r->relid == rte_a->relid)
+						var->varno -= 1;
+				}
+			}
+		}
 
 		/* First look for the var in the input tlists */
 		newvar = search_indexed_tlist_for_var(var,
@@ -1892,6 +1922,43 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
  *
  * Note: resultRelation is not yet adjusted by rtoffset.
  */
+
+void fix_varno_varattno(List *rlist, 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->varno = OUTER_VAR;
+					var->varattno = var->varoattno;
+				}
+			}
+			else if( IsA(var, OpExpr ))
+			{
+				fix_varno_varattno(((OpExpr*)var)->args, aft);
+			}
+			else if( IsA(var, FuncExpr ))
+			{
+				fix_varno_varattno(((FuncExpr*)var)->args, aft);
+			}
+		}
+	}
+}
+
 static List *
 set_returning_clause_references(PlannerInfo *root,
 								List *rlist,
@@ -1900,7 +1967,24 @@ set_returning_clause_references(PlannerInfo *root,
 								int rtoffset)
 {
 	indexed_tlist *itlist;
+	int after_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_BEFORE )
+		{
+			after_index = index_rel;
+			break;
+		}
+		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 +2008,7 @@ set_returning_clause_references(PlannerInfo *root,
 						  resultRelation,
 						  rtoffset);
 
+	fix_varno_varattno(rlist, after_index);
 	pfree(itlist);
 
 	return rlist;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 5284293..8c452ad 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -103,6 +103,7 @@ static void substitute_multiple_relids(Node *node,
 static void fix_append_rel_relids(List *append_rel_list, int varno,
 					  Relids subrelids);
 static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
+static void prepare_returning_before(PlannerInfo *root, List *ret, int varno);
 
 
 /*
@@ -648,6 +649,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?
@@ -753,6 +757,35 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
 	return jtnode;
 }
 
+void prepare_returning_before(PlannerInfo *root, List *ret, int varno)
+{
+	ListCell *v;
+	Var *var;
+	List *rtable = root->parse->rtable;
+	RangeTblEntry *rte;
+	TargetEntry *target;
+	foreach(v,ret)
+	{
+		target = (TargetEntry*)lfirst(v);
+		if(IsA(target,TargetEntry))
+		{
+			var = (Var*)target->expr;
+			if(IsA(var,Var))
+			{
+				if (var->varno <= list_length(rtable))
+				{
+					rte = (RangeTblEntry*)list_nth(rtable,var->varno-1);
+					if(rte->rtekind == RTE_BEFORE)
+					{
+						var->varno=varno;
+					}
+				}
+
+			}
+		}
+	}
+}
+
 /*
  * pull_up_simple_subquery
  *		Attempt to pull up a single simple subquery.
@@ -912,6 +945,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->targetList = (List *)
 		pullup_replace_vars((Node *) parse->targetList, &rvcontext);
+
+	prepare_returning_before(root,parse->returningList,varno);
 	parse->returningList = (List *)
 		pullup_replace_vars((Node *) parse->returningList, &rvcontext);
 	replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
@@ -980,6 +1015,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;
 			}
@@ -1513,6 +1549,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;
@@ -1666,6 +1703,19 @@ 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)
+		{
+			if(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/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 8ee5671..28c5ff7 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -135,6 +135,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:
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 7eaf8d2..97fb970 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -688,6 +688,18 @@ flatten_join_alias_vars_mutator(Node *node,
 		Assert(var->varattno > 0);
 		newvar = (Node *) list_nth(rte->joinaliasvars, var->varattno - 1);
 		newvar = copyObject(newvar);
+		if(IsA(newvar,Var) && context->root->parse->commandType == CMD_UPDATE)
+		{
+			if(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 16ff234..fd51a0e 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2003,6 +2003,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 cbfb431..2297045 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -85,7 +85,57 @@ 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])
+				if (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 a9254c8..e57fccf 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1658,6 +1658,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,
@@ -2113,6 +2114,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;
@@ -2273,6 +2275,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 ca20e77..b8e08e6 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -316,6 +316,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:
@@ -1421,6 +1422,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 40b565a..da6e15d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5587,6 +5587,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 9415e2c..9dac4ca 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -701,7 +701,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..24c1123
--- /dev/null
+++ b/src/test/regress/expected/returning_before_after.out
@@ -0,0 +1,129 @@
+--
+-- 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)
+
+-- check views
+CREATE VIEW view_foo AS SELECT * FROM foo;
+UPDATE foo SET bar1=bar1+1 RETURNING before.*, bar1, bar2;
+ bar1 | bar2 | bar1 | bar2 
+------+------+------+------
+    4 | xzab |    5 | xzab
+    5 | yzab |    6 | yzab
+    2 | 15   |    3 | 15
+(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 make the view updatable, provide an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.
+-- 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  
+------+------+------+-------+------+-------
+    5 | xzab |    6 | xzabz |   36 | xzabz
+    6 | yzab |    7 | yzabz |   49 | yzabz
+(2 rows)
+
+DROP TABLE foo CASCADE;
+NOTICE:  drop cascades to view view_foo
+DROP TABLE foo3 CASCADE;
+-- check WITH statement 
+CREATE TABLE t1 (x int, y int, z int);
+CREATE TABLE t2 (x int, y int, z int);
+INSERT INTO t1 VALUES (1,2,3);
+INSERT INTO t1 VALUES (4,5,6);
+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 *;
+ x | y | z  
+---+---+----
+ 1 | 2 |  6
+ 4 | 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..96d2a3f
--- /dev/null
+++ b/src/test/regress/sql/returning_before_after.sql
@@ -0,0 +1,81 @@
+--
+-- 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.*, *;
+
+-- check views
+
+CREATE VIEW view_foo AS SELECT * FROM foo;
+
+UPDATE 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;
+
+-- check WITH statement 
+CREATE TABLE t1 (x int, y int, z int);
+CREATE TABLE t2 (x int, y int, z int);
+
+INSERT INTO t1 VALUES (1,2,3);
+INSERT INTO t1 VALUES (4,5,6);
+
+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 *;
+
+DROP TABLE t1;
+DROP TABLE t2;
#42Karol Trzcionka
karlikt@gmail.com
In reply to: Karol Trzcionka (#41)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

I've noticed problem with "UPDATE ... FROM" statement. Fix in new version.
Regards,
Karol

Attachments:

before_after_v4.patchtext/x-patch; name=before_after_v4.patchDownload
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 a896d76..409b4d1 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1928,6 +1928,7 @@ range_table_walker(List *rtable,
 		{
 			case RTE_RELATION:
 			case RTE_CTE:
+			case RTE_BEFORE:
 				/* nothing to do */
 				break;
 			case RTE_SUBQUERY:
@@ -2642,6 +2643,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 48cd9dc..79b03af 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2366,6 +2366,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 dc9cb3e..2ca29da 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 839ed9d..aeae8a2 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -174,17 +174,52 @@ 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, (var->varno)-1));
+				if(rte->rtekind == RTE_BEFORE)
+				{
+					if(strcmp(rte->eref->aliasname,"after") == 0)
+					{
+						RangeTblEntry *rte_b;
+						ListCell *rte_c;
+						varno = 1;
+						foreach(rte_c, root->parse->rtable)
+						{
+							rte_b = (RangeTblEntry *)lfirst(rte_c);
+							if(rte_b->rtekind == RTE_RELATION && rte_b->relid == rte->relid)
+							{
+								break;
+							}
+							varno++;
+						}
+					}
+					else
+					{
+						Var *var2 = copyObject(var);
+						var2->varno = varno;
+						rel->reltargetlist = lappend(rel->reltargetlist,
+											 var2);
+						continue;
+					}
+				}
+			}
+			rel = find_base_rel(root, varno);
 			Assert(attno >= rel->min_attr && attno <= rel->max_attr);
 			attno -= rel->min_attr;
 			if (rel->attr_needed[attno] == NULL)
 			{
 				/* Variable not yet requested, so add to reltargetlist */
 				/* XXX is copyObject necessary here? */
+				Var *var2 = copyObject(var);
+				var2->varno = varno;
 				rel->reltargetlist = lappend(rel->reltargetlist,
-											 copyObject(var));
+											 var2);
 			}
 			rel->attr_needed[attno] = bms_add_members(rel->attr_needed[attno],
 													  where_needed);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 01e2fa3..65e4a75 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -2015,6 +2015,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);
@@ -2054,6 +2057,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..ef95791 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 fix_varno_varattno(List *rlist, int aft);
 
 
 /*****************************************************************************
@@ -1691,6 +1692,27 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
 	if (IsA(node, Var))
 	{
 		Var		   *var = (Var *) node;
+		if (var->varno<=list_length(context->root->parse->rtable) && 
+			var->varno>1 && 
+			context->root->parse->commandType == CMD_UPDATE)
+		{
+			RangeTblEntry *rte_a, *rte_r;
+			ListCell *rte_c;
+			rte_a = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-1);
+			if (rte_a->rtekind == RTE_BEFORE && strcmp(rte_a->eref->aliasname,"after") == 0)
+			{
+				var->varno = 1;
+				foreach(rte_c, context->root->parse->rtable)
+				{
+					rte_r = (RangeTblEntry *)lfirst(rte_c);
+					if(rte_r->rtekind == RTE_RELATION && rte_r->relid == rte_a->relid)
+					{
+						break;
+					}
+					var->varno++;
+				}
+			}
+		}
 
 		/* First look for the var in the input tlists */
 		newvar = search_indexed_tlist_for_var(var,
@@ -1892,6 +1914,43 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
  *
  * Note: resultRelation is not yet adjusted by rtoffset.
  */
+
+void fix_varno_varattno(List *rlist, 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->varno = OUTER_VAR;
+					var->varattno = var->varoattno;
+				}
+			}
+			else if( IsA(var, OpExpr ))
+			{
+				fix_varno_varattno(((OpExpr*)var)->args, aft);
+			}
+			else if( IsA(var, FuncExpr ))
+			{
+				fix_varno_varattno(((FuncExpr*)var)->args, aft);
+			}
+		}
+	}
+}
+
 static List *
 set_returning_clause_references(PlannerInfo *root,
 								List *rlist,
@@ -1900,7 +1959,24 @@ set_returning_clause_references(PlannerInfo *root,
 								int rtoffset)
 {
 	indexed_tlist *itlist;
+	int after_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_BEFORE )
+		{
+			after_index = index_rel;
+			break;
+		}
+		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 +2000,7 @@ set_returning_clause_references(PlannerInfo *root,
 						  resultRelation,
 						  rtoffset);
 
+	fix_varno_varattno(rlist, after_index);
 	pfree(itlist);
 
 	return rlist;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 5284293..8c452ad 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -103,6 +103,7 @@ static void substitute_multiple_relids(Node *node,
 static void fix_append_rel_relids(List *append_rel_list, int varno,
 					  Relids subrelids);
 static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
+static void prepare_returning_before(PlannerInfo *root, List *ret, int varno);
 
 
 /*
@@ -648,6 +649,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?
@@ -753,6 +757,35 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
 	return jtnode;
 }
 
+void prepare_returning_before(PlannerInfo *root, List *ret, int varno)
+{
+	ListCell *v;
+	Var *var;
+	List *rtable = root->parse->rtable;
+	RangeTblEntry *rte;
+	TargetEntry *target;
+	foreach(v,ret)
+	{
+		target = (TargetEntry*)lfirst(v);
+		if(IsA(target,TargetEntry))
+		{
+			var = (Var*)target->expr;
+			if(IsA(var,Var))
+			{
+				if (var->varno <= list_length(rtable))
+				{
+					rte = (RangeTblEntry*)list_nth(rtable,var->varno-1);
+					if(rte->rtekind == RTE_BEFORE)
+					{
+						var->varno=varno;
+					}
+				}
+
+			}
+		}
+	}
+}
+
 /*
  * pull_up_simple_subquery
  *		Attempt to pull up a single simple subquery.
@@ -912,6 +945,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->targetList = (List *)
 		pullup_replace_vars((Node *) parse->targetList, &rvcontext);
+
+	prepare_returning_before(root,parse->returningList,varno);
 	parse->returningList = (List *)
 		pullup_replace_vars((Node *) parse->returningList, &rvcontext);
 	replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
@@ -980,6 +1015,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;
 			}
@@ -1513,6 +1549,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;
@@ -1666,6 +1703,19 @@ 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)
+		{
+			if(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/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 8ee5671..5da4e78 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -135,6 +135,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:
@@ -503,6 +505,12 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
 				 (int) nodeTag(var));
 
 		/* Get the Var's original base rel */
+		{
+			RangeTblEntry *rte;
+			rte = ((RangeTblEntry *) list_nth(root->parse->rtable, (var->varno)-1));
+			if(rte->rtekind == RTE_BEFORE)
+				continue;
+		}
 		baserel = find_base_rel(root, var->varno);
 
 		/* Is it still needed above this joinrel? */
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 7eaf8d2..97fb970 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -688,6 +688,18 @@ flatten_join_alias_vars_mutator(Node *node,
 		Assert(var->varattno > 0);
 		newvar = (Node *) list_nth(rte->joinaliasvars, var->varattno - 1);
 		newvar = copyObject(newvar);
+		if(IsA(newvar,Var) && context->root->parse->commandType == CMD_UPDATE)
+		{
+			if(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 16ff234..fd51a0e 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2003,6 +2003,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 cbfb431..2297045 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -85,7 +85,57 @@ 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])
+				if (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 a9254c8..e57fccf 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1658,6 +1658,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,
@@ -2113,6 +2114,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;
@@ -2273,6 +2275,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 ca20e77..b8e08e6 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -316,6 +316,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:
@@ -1421,6 +1422,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 40b565a..da6e15d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5587,6 +5587,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 9415e2c..9dac4ca 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -701,7 +701,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..bc85bce
--- /dev/null
+++ b/src/test/regress/expected/returning_before_after.out
@@ -0,0 +1,144 @@
+--
+-- 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)
+
+-- check views
+CREATE VIEW view_foo AS SELECT * FROM foo;
+UPDATE foo SET bar1=bar1+1 RETURNING before.*, bar1, bar2;
+ bar1 | bar2 | bar1 | bar2 
+------+------+------+------
+    4 | xzab |    5 | xzab
+    5 | yzab |    6 | yzab
+    2 | 15   |    3 | 15
+(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 make the view updatable, provide an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.
+-- 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  
+------+------+------+-------+------+-------
+    5 | xzab |    6 | xzabz |   36 | xzabz
+    6 | yzab |    7 | yzabz |   49 | yzabz
+(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..e1b5955
--- /dev/null
+++ b/src/test/regress/sql/returning_before_after.sql
@@ -0,0 +1,85 @@
+--
+-- 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.*, *;
+
+-- check views
+
+CREATE VIEW view_foo AS SELECT * FROM foo;
+
+UPDATE 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;
#43David Fetter
david@fetter.org
In reply to: Karol Trzcionka (#42)
Re: GSOC13 proposal - extend RETURNING syntax

On Mon, Jul 22, 2013 at 09:52:14PM +0200, Karol Trzcionka wrote:

I've noticed problem with "UPDATE ... FROM" statement. Fix in new version.
Regards,
Karol

What problem or problems did you notice, and what did you change to
fix them?

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Karol Trzcionka
karlikt@gmail.com
In reply to: David Fetter (#43)
Re: GSOC13 proposal - extend RETURNING syntax

W dniu 23.07.2013 06:22, David Fetter pisze:

What problem or problems did you notice, and what did you change to
fix them?

"UPDATE ... FROM" generated "ERROR: variable not found in subplan
target lists". I've added some workaround in add_vars_to_targetlist:
- if it is an "after" - simple change var->varno to base RTE (it should
always exists, the value is temporary, it will change to OUTER_VAR)
- if it is a "before" - add to targetlist new var independently from
rel->attr_needed[attno].
Additionally I've change build_joinrel_tlist to ignore any "BEFORE"
RTEs. The regression tests are updated.
Regards,
Karol

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#45Arulappan, Arul Shaji
arul@fast.au.fujitsu.com
In reply to: Tatsuo Ishii (#36)
Re: Proposal - Support for National Characters functionality

-----Original Message-----
From: Tatsuo Ishii [mailto:ishii@postgresql.org]

Also I don't understand why you need UTF-16 support as a database

encoding

because UTF-8 and UTF-16 are logically equivalent, they are just

different

represention (encoding) of Unicode. That means if we already support

UTF-8

(I'm sure we already do), there's no particular reason we need to add

UTF-16

support.

Maybe you just want to support UTF-16 as a client encoding?

Given below is a design draft for this functionality:

Core new functionality (new code):
1)Create and register independent NCHAR/NVARCHAR/NTEXT data types.

2)Provide support for the new GUC nchar_collation to provide the
database with information about the default collation that needs to be
used for the new data types.

3)Create encoding conversion subroutines to convert strings between the
database encoding and UTF8 (from national strings to regular strings and
back).
PostgreSQL already have all required support (used for conversion
between the database encoding and client_encoding), so amount of the new
code will be minimal there.

4)Because all symbols from non-UTF8 encodings could be represented as
UTF8 (but the reverse is not true) comparison between N* types and the
regular string types inside database will be performed in UTF8 form. To
achieve this feature the new IMPLICIT casts may need to be created:
NCHAR -> CHAR
NVARCHAR -> VARCHAR
NTEXT -> TEXT.

Casting in the reverse direction will be available too but only as
EXPLICIT.
However, these casts could fail if national strings could not be
represented in the used database encoding.

All these casts will use subroutines created in 3).

Casting/conversion between N* types will follow the same rules/mechanics
as used for casting/conversion between usual (CHAR(N)/VARCHAR(N)/TEXT)
string types.

5)Comparison between NATIONAL string values will be performed via
specialized UTF8 optimized functions (with respect of the
nchar_collation setting).

6)Client input/output of NATIONAL strings - NATIONAL strings will
respect the client_encoding setting, and their values will be
transparently converted to the requested client_encoding before
sending(receiving) to client (the same mechanics as used for usual
string types).
So no mixed encoding in client input/output will be supported/available.

7)Create set of the regression tests for these new data types.

Additional changes:
1)ECPG support for these new types
2) Support in the database drivers for the data types.

Rgds,
Arul Shaji

--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese: http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#46Tom Lane
tgl@sss.pgh.pa.us
In reply to: Arulappan, Arul Shaji (#45)
Re: Proposal - Support for National Characters functionality

"Arulappan, Arul Shaji" <arul@fast.au.fujitsu.com> writes:

Given below is a design draft for this functionality:

Core new functionality (new code):
1)Create and register independent NCHAR/NVARCHAR/NTEXT data types.

2)Provide support for the new GUC nchar_collation to provide the
database with information about the default collation that needs to be
used for the new data types.

A GUC seems like completely the wrong tack to be taking. In the first
place, that would mandate just one value (at a time anyway) of
collation, which is surely not much of an advance over what's already
possible. In the second place, what happens if you change the value?
All your indexes on nchar columns are corrupt, that's what. Actually
the data itself would be corrupt, if you intend that this setting
determines the encoding and not just the collation. If you really are
speaking only of collation, it's not clear to me exactly what this
proposal offers that can't be achieved today (with greater security,
functionality and spec compliance) by using COLLATE clauses on plain
text columns.

Actually, you really haven't answered at all what it is you want to do
that COLLATE can't do.

4)Because all symbols from non-UTF8 encodings could be represented as
UTF8 (but the reverse is not true) comparison between N* types and the
regular string types inside database will be performed in UTF8 form.

I believe that in some Far Eastern character sets there are some
characters that map to the same Unicode glyph, but that some people
would prefer to keep separate. So transcoding to UTF8 isn't necessarily
lossless. This is one of the reasons why we've resisted adopting ICU or
standardizing on UTF8 as the One True Database Encoding. Now this may
or may not matter for comparison to strings that were in some other
encoding to start with --- but as soon as you base your design on the
premise that UTF8 is a universal encoding, you are sliding down a
slippery slope to a design that will meet resistance.

6)Client input/output of NATIONAL strings - NATIONAL strings will
respect the client_encoding setting, and their values will be
transparently converted to the requested client_encoding before
sending(receiving) to client (the same mechanics as used for usual
string types).
So no mixed encoding in client input/output will be supported/available.

If you have this restriction, then I'm really failing to see what
benefit there is over what can be done today with COLLATE.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#47Boguk, Maksym
maksymb@fast.au.fujitsu.com
In reply to: Tom Lane (#46)
Re: Proposal - Support for National Characters functionality

Hi everyone,

I will try answer on all questions related to proposed National
Characters support.

2)Provide support for the new GUC nchar_collation to provide the
database with information about the default collation that needs to

be

used for the new data types.

A GUC seems like completely the wrong tack to be taking. In the first

place, that would mandate just one value (at a time anyway) of
collation, which is surely not much of an advance over what's already
possible. In the second place, what happens if you change the value?

All your indexes on nchar columns are corrupt, that's what. Actually

the data itself would be corrupt, if you intend that this setting
determines the encoding and not just the collation. If you really are
speaking only of collation, it's not clear to me exactly what this
proposal offers that can't be achieved today (with greater security,

functionality and spec compliance) by using COLLATE clauses on plain

text columns.

Actually, you really haven't answered at all what it is you want to do

that COLLATE can't do.

I think I give a wrong description there... it will be not GUC but
GUC-type value which will be initialized during CREATE DATABASE and will
be read only after, very similar to the lc_collate.
So I think name national_lc_collate will be better.
Function of this value - provide information about the default collation
for the NATIONAL CHARACTERS inside the database.
That's not limits user ability of use an alternative collation for
NATIONAL CHARACTERS during create table via COLLATE keyword.

E.g. if we have second encoding inside the database - we should have
information about used collation somewhere.

4)Because all symbols from non-UTF8 encodings could be represented as
UTF8 (but the reverse is not true) comparison between N* types and

the

regular string types inside database will be performed in UTF8 form.

I believe that in some Far Eastern character sets there are some

characters that map to the same Unicode glyph, but that some people
would prefer to keep separate. So transcoding to UTF8 isn't necessarily
lossless. This is one of the reasons why we've resisted adopting ICU or
standardizing on UTF8 as the One True Database Encoding. >Now this may
or may not matter for comparison to strings that were in some other
encoding to start with --- but as soon as you base your design on the
premise that UTF8 is a universal encoding, you are sliding down a
slippery slope to a design that will meet resistance.

Will the conversion of both sides to the pg_wchar before comparison fix
this problem?
Anyway, if the database going to use more than one encoding, a some
universal encoding should be used to allow comparison between them.
After some analyse I think pg_wchar is better candidate to this role
than UTF8.

6)Client input/output of NATIONAL strings - NATIONAL strings will
respect the client_encoding setting, and their values will be
transparently converted to the requested client_encoding before
sending(receiving) to client (the same mechanics as used for usual
string types).
So no mixed encoding in client input/output will be

supported/available.

If you have this restriction, then I'm really failing to see what

benefit there is over what can be done today with COLLATE.

There are two targets for this project:

1. Legacy database with non-utf8 encoding, which should support old
non-utf8 applications and new UTF8 applications.
In that case the old applications will use the legacy database encoding
(and because these applications are legacy they doesn't work with new
NATIONAL CHARACTERS data/tables).
And the new applications will use client-side UTF8 encoding and they
will be able store international texts in NATIONAL CHARACTER columns.
Dump/restore of the whole database to change the database encoding to
UTF8 not always possible, so there necessity of the some easy to use
workaround.

2.Better compatibility with the ANSI SQL standard.

Kind Regards,
Maksym

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Boguk, Maksym (#47)
Re: Proposal - Support for National Characters functionality

Boguk, Maksym escribi�:

I think I give a wrong description there... it will be not GUC but
GUC-type value which will be initialized during CREATE DATABASE and will
be read only after, very similar to the lc_collate.
So I think name national_lc_collate will be better.
Function of this value - provide information about the default collation
for the NATIONAL CHARACTERS inside the database.
That's not limits user ability of use an alternative collation for
NATIONAL CHARACTERS during create table via COLLATE keyword.

This seems a bit odd. I mean, if I want the option for differing
encodings, surely I need to be able to set them for each column, not at
the database level.

Also, as far as I understand what we want to control here is the
encoding that the strings are in (the mapping of bytes to characters),
not the collation (the way a set of strings are ordered). So it doesn't
make sense to set the NATIONAL CHARACTER option using the COLLATE
keyword.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#48)
Re: Proposal - Support for National Characters functionality

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

Also, as far as I understand what we want to control here is the
encoding that the strings are in (the mapping of bytes to characters),
not the collation (the way a set of strings are ordered). So it doesn't
make sense to set the NATIONAL CHARACTER option using the COLLATE
keyword.

My thought is that we should simply ignore the NATIONAL CHARACTER syntax,
which is not the first nor the last brain-damaged feature design in the
SQL standard. It's basically useless for what we want because there's
noplace to specify which encoding you mean. Instead, let's consider that
COLLATE can define not only the collation but also the encoding of a
string datum. Contrary to what I think you meant above, that seems
perfectly sensible to me, because after all a collation is necessarily a
bunch of rules about how to order a particular set of characters. If the
data representation you use is unable to represent that set of characters,
it's not a very meaningful combination is it?

There's still the problem of how do you get a string of a nondefault
encoding into the database in the first place. If you have to convert
to DB encoding to get it in there, then what's the use of a further
conversion? This consideration may well kill the whole concept.
(It certainly kills NATIONAL CHARACTER syntax just as much.)

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#50Arulappan, Arul Shaji
arul@fast.au.fujitsu.com
In reply to: Alvaro Herrera (#48)
Re: Proposal - Support for National Characters functionality

From: Alvaro Herrera [mailto:alvherre@2ndquadrant.com]

Boguk, Maksym escribió:

I think I give a wrong description there... it will be not GUC but
GUC-type value which will be initialized during CREATE DATABASE and
will be read only after, very similar to the lc_collate.
So I think name national_lc_collate will be better.
Function of this value - provide information about the default
collation for the NATIONAL CHARACTERS inside the database.
That's not limits user ability of use an alternative collation for
NATIONAL CHARACTERS during create table via COLLATE keyword.

This seems a bit odd. I mean, if I want the option for differing encodings,
surely I need to be able to set them for each column, not at the database
level.

Also, as far as I understand what we want to control here is the encoding that
the strings are in (the mapping of bytes to characters), not the collation

Yes, that is our idea too. For the sql syntax

Create table tbl1 (col1 nchar);

What should be the encoding and collation for col1? Because the idea is to have them in separate encoding and collation (if needed) from that of the rest of the table. We have options of

a) Having guc variables that will determine the default encoding and collation for nchar/nvarchar columns. Note that the collate variable is default only. Users can still override them per column.
b) Having the encoding name and collation as part of the syntax. For ex., (col1 nchar encoding UTF-8 COLLATE "C"). Ugly, but.....
c) Be rigid and say nchar/nvarchar columns are by default UTF-8 (or something else). One cannot change the default. But they can override it when declaring the column by having a syntax similar to (b)

Rgds,
Arul Shaji

(the way a set of strings are ordered). So it doesn't make sense to set the
NATIONAL CHARACTER option using the COLLATE keyword.

--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51Arulappan, Arul Shaji
arul@fast.au.fujitsu.com
In reply to: Tom Lane (#49)
Re: Proposal - Support for National Characters functionality

From: Tom Lane [mailto:tgl@sss.pgh.pa.us]

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

Also, as far as I understand what we want to control here is the
encoding that the strings are in (the mapping of bytes to

characters),

not the collation (the way a set of strings are ordered). So it
doesn't make sense to set the NATIONAL CHARACTER option using the
COLLATE keyword.

My thought is that we should simply ignore the NATIONAL CHARACTER

syntax,

which is not the first nor the last brain-damaged feature design in

the SQL

standard. It's basically useless for what we want because there's

noplace to

specify which encoding you mean. Instead, let's consider that COLLATE

can

define not only the collation but also the encoding of a string datum.

Yes, don't have a problem with this. If I understand you correctly, this
will be simpler syntax wise, but still get nchar/nvarchar data types
into a table, in different encoding from the rest of the table.

There's still the problem of how do you get a string of a nondefault

encoding

into the database in the first place.

Yes, that is the bulk of the work. Will need change in a whole lot of
places.

Is a step-by-step approach worth exploring ? Something similar to:

Step 1: Support nchar/nvarchar data types. Restrict them only to UTF-8
databases to begin with.
Step 2: Support multiple encodings in a database. Remove the restriction
imposed in step1.

Rgds,
Arul Shaji

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Boszormenyi Zoltan
zb@cybertec.at
In reply to: Karol Trzcionka (#42)
Re: GSOC13 proposal - extend RETURNING syntax

Hi,

mini-review follows.

2013-07-22 21:52 keltez�ssel, Karol Trzcionka �rta:

I've noticed problem with "UPDATE ... FROM" statement. Fix in new version.
Regards,
Karol

* Does it apply cleanly to the current git master?

No. There's a reject in src/backend/optimizer/plan/initsplan.c

* Does it include reasonable tests?

Yes but the test fails after trying to fix the rejected chunk of the patch.

Best regards,
Zolt�n B�sz�rm�nyi

--
----------------------------------
Zolt�n B�sz�rm�nyi
Cybertec Sch�nig & Sch�nig GmbH
Gr�hrm�hlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
http://www.postgresql.at/

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#53Karol Trzcionka
karlikt@gmail.com
In reply to: Boszormenyi Zoltan (#52)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

W dniu 19.08.2013 19:56, Boszormenyi Zoltan pisze:

* Does it apply cleanly to the current git master?

No. There's a reject in src/backend/optimizer/plan/initsplan.c

Thank you, merged in attached version.

* Does it include reasonable tests?

Yes but the test fails after trying to fix the rejected chunk of the
patch.

It fails because the "HINT" was changed, fixed.
That version merges some nested "ifs" left over from earlier work.
Regards,
Karol Trzcionka

Attachments:

before_after_v5.patchtext/x-patch; name=before_after_v5.patchDownload
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..20de5a6 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -184,8 +184,42 @@ 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, (var->varno)-1));
+				if(rte->rtekind == RTE_BEFORE)
+				{
+					if(strcmp(rte->eref->aliasname,"after") == 0)
+					{
+						RangeTblEntry *rte_b;
+						ListCell *rte_c;
+						varno = 1;
+						foreach(rte_c, root->parse->rtable)
+						{
+							rte_b = (RangeTblEntry *)lfirst(rte_c);
+							if(rte_b->rtekind == RTE_RELATION && rte_b->relid == rte->relid)
+							{
+								break;
+							}
+							varno++;
+						}
+					}
+					else
+					{
+						Var *var2 = copyObject(var);
+						var2->varno = varno;
+						rel->reltargetlist = lappend(rel->reltargetlist,
+											 var2);
+						continue;
+					}
+				}
+			}
+			rel = find_base_rel(root, varno);
 
 			if (bms_is_subset(where_needed, rel->relids))
 				continue;
@@ -195,8 +229,10 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars,
 			{
 				/* Variable not yet requested, so add to reltargetlist */
 				/* XXX is copyObject necessary here? */
+				Var *var2 = copyObject(var);
+				var2->varno = varno;
 				rel->reltargetlist = lappend(rel->reltargetlist,
-											 copyObject(var));
+											 var2);
 			}
 			rel->attr_needed[attno] = bms_add_members(rel->attr_needed[attno],
 													  where_needed);
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..ef95791 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 fix_varno_varattno(List *rlist, int aft);
 
 
 /*****************************************************************************
@@ -1691,6 +1692,27 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
 	if (IsA(node, Var))
 	{
 		Var		   *var = (Var *) node;
+		if (var->varno<=list_length(context->root->parse->rtable) && 
+			var->varno>1 && 
+			context->root->parse->commandType == CMD_UPDATE)
+		{
+			RangeTblEntry *rte_a, *rte_r;
+			ListCell *rte_c;
+			rte_a = (RangeTblEntry *)list_nth(context->root->parse->rtable,var->varno-1);
+			if (rte_a->rtekind == RTE_BEFORE && strcmp(rte_a->eref->aliasname,"after") == 0)
+			{
+				var->varno = 1;
+				foreach(rte_c, context->root->parse->rtable)
+				{
+					rte_r = (RangeTblEntry *)lfirst(rte_c);
+					if(rte_r->rtekind == RTE_RELATION && rte_r->relid == rte_a->relid)
+					{
+						break;
+					}
+					var->varno++;
+				}
+			}
+		}
 
 		/* First look for the var in the input tlists */
 		newvar = search_indexed_tlist_for_var(var,
@@ -1892,6 +1914,43 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
  *
  * Note: resultRelation is not yet adjusted by rtoffset.
  */
+
+void fix_varno_varattno(List *rlist, 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->varno = OUTER_VAR;
+					var->varattno = var->varoattno;
+				}
+			}
+			else if( IsA(var, OpExpr ))
+			{
+				fix_varno_varattno(((OpExpr*)var)->args, aft);
+			}
+			else if( IsA(var, FuncExpr ))
+			{
+				fix_varno_varattno(((FuncExpr*)var)->args, aft);
+			}
+		}
+	}
+}
+
 static List *
 set_returning_clause_references(PlannerInfo *root,
 								List *rlist,
@@ -1900,7 +1959,24 @@ set_returning_clause_references(PlannerInfo *root,
 								int rtoffset)
 {
 	indexed_tlist *itlist;
+	int after_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_BEFORE )
+		{
+			after_index = index_rel;
+			break;
+		}
+		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 +2000,7 @@ set_returning_clause_references(PlannerInfo *root,
 						  resultRelation,
 						  rtoffset);
 
+	fix_varno_varattno(rlist, after_index);
 	pfree(itlist);
 
 	return rlist;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index c742cc9..ba70960 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -107,6 +107,7 @@ static void substitute_multiple_relids(Node *node,
 static void fix_append_rel_relids(List *append_rel_list, int varno,
 					  Relids subrelids);
 static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
+static void prepare_returning_before(PlannerInfo *root, List *ret, int varno);
 
 
 /*
@@ -652,6 +653,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?
@@ -757,6 +761,34 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
 	return jtnode;
 }
 
+void prepare_returning_before(PlannerInfo *root, List *ret, int varno)
+{
+	ListCell *v;
+	Var *var;
+	List *rtable = root->parse->rtable;
+	RangeTblEntry *rte;
+	TargetEntry *target;
+	foreach(v,ret)
+	{
+		target = (TargetEntry*)lfirst(v);
+		if(!IsA(target,TargetEntry))
+			continue;
+
+		if(!IsA(target->expr,Var))
+			continue;
+
+		var = (Var*)target->expr;
+		if(var->varno <= list_length(rtable))
+		{
+			rte = (RangeTblEntry*)list_nth(rtable,var->varno-1);
+			if(rte->rtekind == RTE_BEFORE)
+			{
+				var->varno=varno;
+			}
+		}
+	}
+}
+
 /*
  * pull_up_simple_subquery
  *		Attempt to pull up a single simple subquery.
@@ -921,6 +953,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->targetList = (List *)
 		pullup_replace_vars((Node *) parse->targetList, &rvcontext);
+
+	prepare_returning_before(root,parse->returningList,varno);
 	parse->returningList = (List *)
 		pullup_replace_vars((Node *) parse->returningList, &rvcontext);
 	replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
@@ -989,6 +1023,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 +1670,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 +1824,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/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..93d48e6
--- /dev/null
+++ b/src/test/regress/expected/returning_before_after.out
@@ -0,0 +1,144 @@
+--
+-- 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)
+
+-- check views
+CREATE VIEW view_foo AS SELECT * FROM foo;
+UPDATE foo SET bar1=bar1+1 RETURNING before.*, bar1, bar2;
+ bar1 | bar2 | bar1 | bar2 
+------+------+------+------
+    4 | xzab |    5 | xzab
+    5 | yzab |    6 | yzab
+    2 | 15   |    3 | 15
+(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  
+------+------+------+-------+------+-------
+    5 | xzab |    6 | xzabz |   36 | xzabz
+    6 | yzab |    7 | yzabz |   49 | yzabz
+(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..e1b5955
--- /dev/null
+++ b/src/test/regress/sql/returning_before_after.sql
@@ -0,0 +1,85 @@
+--
+-- 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.*, *;
+
+-- check views
+
+CREATE VIEW view_foo AS SELECT * FROM foo;
+
+UPDATE 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;
#54Boszormenyi Zoltan
zb@cybertec.at
In reply to: Karol Trzcionka (#53)
Re: GSOC13 proposal - extend RETURNING syntax

2013-08-19 21:21 keltez�ssel, Karol Trzcionka �rta:

W dniu 19.08.2013 19:56, Boszormenyi Zoltan pisze:

* Does it apply cleanly to the current git master?

No. There's a reject in src/backend/optimizer/plan/initsplan.c

Thank you, merged in attached version.

* Does it include reasonable tests?

Yes but the test fails after trying to fix the rejected chunk of the
patch.

It fails because the "HINT" was changed, fixed.
That version merges some nested "ifs" left over from earlier work.

I tried to compile your v5 patch and I got:

initsplan.c: In function �add_vars_to_targetlist�:
initsplan.c:216:26: warning: �rel� may be used uninitialized in this function
[-Wmaybe-uninitialized]
rel->reltargetlist = lappend(rel->reltargetlist,
^

You shouldn't change the assignment at declaration:

- RelOptInfo *rel = find_base_rel(root, var->varno);
+ RelOptInfo *rel;
...
+ if (root->parse->commandType == CMD_UPDATE)
+ {
... (code using rel)
+ }
+ rel = find_base_rel(root, varno);

Best regards,
Zolt�n B�sz�rm�nyi

Regards,
Karol Trzcionka

--
----------------------------------
Zolt�n B�sz�rm�nyi
Cybertec Sch�nig & Sch�nig GmbH
Gr�hrm�hlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
http://www.postgresql.at/

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Boszormenyi Zoltan
zb@cybertec.at
In reply to: Boszormenyi Zoltan (#54)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

Hi,

2013-08-19 21:52 keltez�ssel, Boszormenyi Zoltan �rta:

2013-08-19 21:21 keltez�ssel, Karol Trzcionka �rta:

W dniu 19.08.2013 19:56, Boszormenyi Zoltan pisze:

* Does it apply cleanly to the current git master?

No. There's a reject in src/backend/optimizer/plan/initsplan.c

Thank you, merged in attached version.

* Does it include reasonable tests?

Yes but the test fails after trying to fix the rejected chunk of the
patch.

It fails because the "HINT" was changed, fixed.
That version merges some nested "ifs" left over from earlier work.

I tried to compile your v5 patch and I got:

initsplan.c: In function �add_vars_to_targetlist�:
initsplan.c:216:26: warning: �rel� may be used uninitialized in this function
[-Wmaybe-uninitialized]
rel->reltargetlist = lappend(rel->reltargetlist,
^

You shouldn't change the assignment at declaration:

- RelOptInfo *rel = find_base_rel(root, var->varno);
+ RelOptInfo *rel;
...
+ if (root->parse->commandType == CMD_UPDATE)
+ {
... (code using rel)
+ }
+ rel = find_base_rel(root, varno);

Let me say it again: the new code in initsplan.c::add_vars_to_targetlist() is fishy.
The compiler says that "rel" used on line 216 may be uninitialized.

Keeping it that way passes "make check", perhaps "rel" was initialized
in a previous iteration of "foreach(temp, vars)", possibly in the
else if (IsA(node, PlaceHolderVar))
branch, which means that "PlaceHolderInfo *phinfo" may be interpreted
as RelOptInfo *, stomping on memory.

Moving the assignment back to the declaration makes "make check"
fail with the attached regression.diffs file.

Initializing it as "RelOptInfo *rel = NULL;" makes the regression check
die with a segfault, obviously.

Change the code to avoid the warning and still produce the wanted effect.

Best regards,
Zolt�n B�sz�rm�nyi

--
----------------------------------
Zolt�n B�sz�rm�nyi
Cybertec Sch�nig & Sch�nig GmbH
Gr�hrm�hlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
http://www.postgresql.at/

Attachments:

regression.diffstext/plain; charset=UTF-8; name=regression.diffsDownload
*** /home/zozo/crosscolumn/review/returning-before-after/postgresql/src/test/regress/expected/returning_before_after.out	2013-08-20 15:01:03.365314482 +0200
--- /home/zozo/crosscolumn/review/returning-before-after/postgresql/src/test/regress/results/returning_before_after.out	2013-08-20 15:30:21.091641454 +0200
***************
*** 6,47 ****
  		);
  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"
--- 6,22 ----
  		);
  INSERT INTO foo VALUES (1, 'x'),(2,'y');
  UPDATE foo SET bar1=bar1+1 RETURNING before.*, bar1, bar2;
! ERROR:  no relation entry for relid 2
  UPDATE foo SET bar1=bar1-1 RETURNING after.bar1, before.bar1*2;
! ERROR:  no relation entry for relid 3
  UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'z' RETURNING before.*, after.*;
! ERROR:  no relation entry for relid 2
  -- check single after
  UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'a' RETURNING after.*;
! ERROR:  no relation entry for relid 3
  -- check single before
  UPDATE foo SET bar1=bar1+1, bar2=bar2 || 'b' RETURNING before.*;
! ERROR:  no relation entry for relid 2
  -- it should fail
  UPDATE foo SET bar1=bar1+before.bar1 RETURNING before.*;
  ERROR:  missing FROM-clause entry for table "before"
***************
*** 53,90 ****
                                   ^
  -- 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)
! 
  -- check views
  CREATE VIEW view_foo AS SELECT * FROM foo;
  UPDATE foo SET bar1=bar1+1 RETURNING before.*, bar1, bar2;
!  bar1 | bar2 | bar1 | bar2 
! ------+------+------+------
!     4 | xzab |    5 | xzab
!     5 | yzab |    6 | yzab
!     2 | 15   |    3 | 15
! (3 rows)
! 
  CREATE TABLE foo3 (bar1 INTEGER, bar4 FLOAT);
  INSERT INTO foo2 VALUES (2, 'asdf', 33);
  INSERT INTO foo3 VALUES (2, 7.77);
--- 28,45 ----
                                   ^
  -- test before/after aliases
  UPDATE foo AS before SET bar1=bar1+1 RETURNING before.*,after.*;
! ERROR:  no relation entry for relid 2
  UPDATE foo AS after SET bar1=bar1-1 RETURNING before.*,after.*;
! ERROR:  no relation entry for relid 2
  -- 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.*, *;
! ERROR:  no relation entry for relid 2
  -- check views
  CREATE VIEW view_foo AS SELECT * FROM foo;
  UPDATE foo SET bar1=bar1+1 RETURNING before.*, bar1, bar2;
! ERROR:  no relation entry for relid 2
  CREATE TABLE foo3 (bar1 INTEGER, bar4 FLOAT);
  INSERT INTO foo2 VALUES (2, 'asdf', 33);
  INSERT INTO foo3 VALUES (2, 7.77);
***************
*** 104,115 ****
  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  
! ------+------+------+-------+------+-------
!     5 | xzab |    6 | xzabz |   36 | xzabz
!     6 | yzab |    7 | yzabz |   49 | yzabz
! (2 rows)
! 
  DROP TABLE foo CASCADE;
  NOTICE:  drop cascades to view view_foo
  DROP TABLE foo3 CASCADE;
--- 59,65 ----
  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.*, *;
! ERROR:  no relation entry for relid 2
  DROP TABLE foo CASCADE;
  NOTICE:  drop cascades to view view_foo
  DROP TABLE foo3 CASCADE;
***************
*** 119,144 ****
  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;
--- 69,79 ----
  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 *;
! ERROR:  no relation entry for relid 2
  -- check UPDATE ... FROM statement
  UPDATE t2 SET x = t1.x+2 FROM t1 WHERE t2.id=t1.id RETURNING after.x, before.x;
! ERROR:  no relation entry for relid 4
  UPDATE t2 SET x = t1.x*2 FROM t1 WHERE t2.id=t1.id RETURNING after.*, before.*;
! ERROR:  no relation entry for relid 4
  DROP TABLE t1;
  DROP TABLE t2;

======================================================================

#56Karol Trzcionka
karlikt@gmail.com
In reply to: Boszormenyi Zoltan (#55)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

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)
Regards,
Karol Trzcionka

Attachments:

before_after_v6.patchtext/x-patch; name=before_after_v6.patchDownload
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..ba70960 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -107,6 +107,7 @@ static void substitute_multiple_relids(Node *node,
 static void fix_append_rel_relids(List *append_rel_list, int varno,
 					  Relids subrelids);
 static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
+static void prepare_returning_before(PlannerInfo *root, List *ret, int varno);
 
 
 /*
@@ -652,6 +653,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?
@@ -757,6 +761,34 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
 	return jtnode;
 }
 
+void prepare_returning_before(PlannerInfo *root, List *ret, int varno)
+{
+	ListCell *v;
+	Var *var;
+	List *rtable = root->parse->rtable;
+	RangeTblEntry *rte;
+	TargetEntry *target;
+	foreach(v,ret)
+	{
+		target = (TargetEntry*)lfirst(v);
+		if(!IsA(target,TargetEntry))
+			continue;
+
+		if(!IsA(target->expr,Var))
+			continue;
+
+		var = (Var*)target->expr;
+		if(var->varno <= list_length(rtable))
+		{
+			rte = (RangeTblEntry*)list_nth(rtable,var->varno-1);
+			if(rte->rtekind == RTE_BEFORE)
+			{
+				var->varno=varno;
+			}
+		}
+	}
+}
+
 /*
  * pull_up_simple_subquery
  *		Attempt to pull up a single simple subquery.
@@ -921,6 +953,8 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	 */
 	parse->targetList = (List *)
 		pullup_replace_vars((Node *) parse->targetList, &rvcontext);
+
+	prepare_returning_before(root,parse->returningList,varno);
 	parse->returningList = (List *)
 		pullup_replace_vars((Node *) parse->returningList, &rvcontext);
 	replace_vars_in_jointree((Node *) parse->jointree, &rvcontext,
@@ -989,6 +1023,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 +1670,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 +1824,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;
#57Karol Trzcionka
karlikt@gmail.com
In reply to: Karol Trzcionka (#56)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

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

Attachments:

before_after_v7.patchtext/x-patch; name=before_after_v7.patchDownload
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;
#58Boszormenyi Zoltan
zb@cybertec.at
In reply to: Karol Trzcionka (#57)
Re: GSOC13 proposal - extend RETURNING syntax

2013-08-20 17:30 keltezéssel, Karol Trzcionka írta:

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. ;)

Here's a new one, for v7:

setrefs.c: In function ‘set_plan_refs’:
setrefs.c:2001:26: warning: ‘before_index’ may be used uninitialized in this function
[-Wmaybe-uninitialized]
bind_returning_variables(rlist, before_index, after_index);
^
setrefs.c:1957:21: note: ‘before_index’ was declared here
int after_index=0, before_index;
^

Best regards,
Zoltán Böszörményi

Regards,
Karol Trzcionka

--
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
http://www.postgresql.at/

#59Karol Trzcionka
karlikt@gmail.com
In reply to: Boszormenyi Zoltan (#58)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

W dniu 20.08.2013 20:55, Boszormenyi Zoltan pisze:

Here's a new one, for v7:

setrefs.c: In function ‘set_plan_refs’:
setrefs.c:2001:26: warning: ‘before_index’ may be used uninitialized
in this function [-Wmaybe-uninitialized]
bind_returning_variables(rlist, before_index, after_index);
^
setrefs.c:1957:21: note: ‘before_index’ was declared here
int after_index=0, before_index;
^

Right, my mistake. Sorry and thanks. Fixed.
Regards,
Karol Trzcionka

Attachments:

before_after_v8.patchtext/x-patch; name=before_after_v8.patchDownload
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..a9bdd97 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=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_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;
#60Boszormenyi Zoltan
zb@cybertec.at
In reply to: Karol Trzcionka (#59)
Re: GSOC13 proposal - extend RETURNING syntax

Hi,

2013-08-20 21:06 keltezéssel, Karol Trzcionka írta:

W dniu 20.08.2013 20:55, Boszormenyi Zoltan pisze:

Here's a new one, for v7:

setrefs.c: In function ‘set_plan_refs’:
setrefs.c:2001:26: warning: ‘before_index’ may be used uninitialized in this function
[-Wmaybe-uninitialized]
bind_returning_variables(rlist, before_index, after_index);
^
setrefs.c:1957:21: note: ‘before_index’ was declared here
int after_index=0, before_index;
^

Right, my mistake. Sorry and thanks. Fixed.
Regards,
Karol Trzcionka

With this fixed, a more complete review:

* Is the patch in a patch format which has context? (eg: context diff format)

Yes.

* Does it apply cleanly to the current git master?

Yes.

* Does it include reasonable tests, necessary doc patches, etc?

There is a new regression test (returning_before_after.sql) covering
this feature. However, I think it should be added to the group
where "returning.sql" resides currently. There is a value in running it
in parallel to other tests. Sometimes a subtle bug is uncovered
because of this and your v2 patch fixed such a bug already.

doc/src/sgml/ref/update.sgml describes this feature.

doc/src/sgml/dml.sgml should also be touched to cover this feature.

* Does the patch actually implement what it's supposed to do?

Yes.

* Do we want that?

Yes.

* Do we already have it?

No.

* Does it follow SQL spec, or the community-agreed behavior?

RETURNING is a PostgreSQL extension, so the SQL-spec part
of the question isn't applicable.

It implements the community-agreed behaviour, according to
the new regression test coverage.

* Does it include pg_dump support (if applicable)?

n/a

* Are there dangers?

I don't think so.

* Have all the bases been covered?

It seems so. I have also tried mixing before/after columns in
different orders and the query didn't fail:

zozo=# create table t1 (id serial primary key, i1 int4, i2 int4, t1 text, t2 text);
CREATE TABLE
zozo=# insert into t1 (i1, i2, t1, t2) values (1, 1, 'a', 'a');
INSERT 0 1
zozo=# insert into t1 (i1, i2, t1, t2) values (2, 2, 'b', 'b');
INSERT 0 1
zozo=# insert into t1 (i1, i2, t1, t2) values (3, 3, 'c', 'c');
INSERT 0 1
zozo=# select * from t1;
id | i1 | i2 | t1 | t2
----+----+----+----+----
1 | 1 | 1 | a | a
2 | 2 | 2 | b | b
3 | 3 | 3 | c | c
(3 rows)

zozo=# begin;
BEGIN
zozo=# update t1 set i2 = i2*2, t2 = t2 || 'x2' where id = 2 returning before.i1,
after.i1, before.i2, after.i2, before.t1, after.t1, before.t2, after.t2;
i1 | i1 | i2 | i2 | t1 | t1 | t2 | t2
----+----+----+----+----+----+----+-----
2 | 2 | 2 | 4 | b | b | b | bx2
(1 row)

UPDATE 1
zozo=# update t1 set i1 = i1 * 3, i2 = i2*2, t1 = t1 || 'x3', t2 = t2 || 'x2' where id = 3
returning before.i1, before.i2, after.i1, after.i2, before.t1, before.t2, after.t1,
after.t2; i1 | i2 | i1 | i2 | t1 | t2 | t1 | t2
----+----+----+----+----+----+-----+-----
3 | 3 | 9 | 6 | c | c | cx3 | cx2
(1 row)

UPDATE 1

* Does the feature work as advertised?

Yes.

* Are there corner cases the author has failed to consider?

I don't know.

* Are there any assertion failures or crashes?

No.

* Does the patch slow down simple tests?

No.

* If it claims to improve performance, does it?

n/a

* Does it slow down other things?

No.

* Does it follow the project coding guidelines?

Mostly.

In the src/test/regress/parallel_schedule contains an extra
new line at the end, it shouldn't.

In b/src/backend/optimizer/plan/setrefs.c:

+static void bind_returning_variables(List *rlist, int bef, int aft);

but later it becomes not public:

+ */
+void bind_returning_variables(List *rlist, int bef, int aft)
+{

Strange, but GCC 4.8.1 -Wall doesn't catch it. But the forward
declaration is not needed, the function is called only from
later functions.

Similar for parse_clause.c:

+extern void addAliases(ParseState *pstate);

+void addAliases(ParseState *pstate)

This external declaration is not needed since it is already
in src/include/parser/parse_clause.h

In setrefs.c, bind_returning_variables() I would also rename
the function arguments, so "before" and "after" are spelled out.
These are not C keywords.

Some assignments, like:

+                       var=(Var*)tle;
and
+       int index_rel=1;

in setrefs.c need spaces.

"if()" statements need a space before the "(" and not after.

Add spaces in the {} list in addAliases():
+ char *aliases[] = {"before","after"};
like this: { "before", "after" }

Spaces are needed here, too:
+ for(i=0 ; i<noal; i++)

This "noal" should be "naliases" or "n_aliases" if you really want
a variable. I would simply use the constant "2" for the two for()
loops in addAliases() instead, its purpose is obvious enough.

In setrefs.c, bind_returning_variables():
+       Var *var = NULL;
+       foreach(temp, rlist){
Add an empty line after the declaration block.

* Are there portability issues?

No.

* Will it work on Windows/BSD etc?

Yes.

* Are the comments sufficient and accurate?

* Does it do what it says, correctly?

Yes.

* Does it produce compiler warnings?

No.

* Can you make it crash?

No.

* Is everything done in a way that fits together coherently with other features/modules?

I think so, mostly. Comments below.

* Are there interdependencies that can cause problems?

I don't think so.

Other comments:

This #define in pg_class:

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 */

The "RELKIND_*" values all show up in the pg_class table except
this new one. I don't think pg_class.h should be modified at all.
addAliases() should use RELKIND_RELATION together with
RTE_BEFORE. Then checks like:

+               if (rte->relkind == RELKIND_BEFORE)
+                       continue;

should become

+               if (rte->relkind == RELKIND_RELATION && rte->rtekind == RTE_BEFORE)
+                       continue;

Speaking of which, RTE_BEFORE is more properly named
RTE_RETURNING_ALIAS or something like that because it
covers both "before" and "after". Someone may have a better
idea for naming this symbol.

I feel like I understand what the code does and it looks sane to me.

One question, though, about this part:

----------------------------------------
@@ -1900,7 +1954,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;
+
+       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;
----------------------------------------

Why is it enough to keep the last before_index and after_index values?
What if there are more than one matching RangeTblEntry for "before"
and/or for "after"? Is it an error condition or of them should be fixed?

I think that's all for now.

Best regards,
Zoltán Böszörményi

--
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
http://www.postgresql.at/

#61Boszormenyi Zoltan
zb@cybertec.at
In reply to: Boszormenyi Zoltan (#60)
Re: GSOC13 proposal - extend RETURNING syntax

2013-08-21 19:17 keltezéssel, Boszormenyi Zoltan írta:

Hi,

2013-08-20 21:06 keltezéssel, Karol Trzcionka írta:

W dniu 20.08.2013 20:55, Boszormenyi Zoltan pisze:

Here's a new one, for v7:

setrefs.c: In function ‘set_plan_refs’:
setrefs.c:2001:26: warning: ‘before_index’ may be used uninitialized in this function
[-Wmaybe-uninitialized]
bind_returning_variables(rlist, before_index, after_index);
^
setrefs.c:1957:21: note: ‘before_index’ was declared here
int after_index=0, before_index;
^

Right, my mistake. Sorry and thanks. Fixed.
Regards,
Karol Trzcionka

With this fixed, a more complete review:

* Is the patch in a patch format which has context? (eg: context diff format)

Yes.

* Does it apply cleanly to the current git master?

Yes.

* Does it include reasonable tests, necessary doc patches, etc?

There is a new regression test (returning_before_after.sql) covering
this feature. However, I think it should be added to the group
where "returning.sql" resides currently. There is a value in running it
in parallel to other tests. Sometimes a subtle bug is uncovered
because of this and your v2 patch fixed such a bug already.

doc/src/sgml/ref/update.sgml describes this feature.

doc/src/sgml/dml.sgml should also be touched to cover this feature.

* Does the patch actually implement what it's supposed to do?

Yes.

* Do we want that?

Yes.

* Do we already have it?

No.

* Does it follow SQL spec, or the community-agreed behavior?

RETURNING is a PostgreSQL extension, so the SQL-spec part
of the question isn't applicable.

It implements the community-agreed behaviour, according to
the new regression test coverage.

* Does it include pg_dump support (if applicable)?

n/a

* Are there dangers?

I don't think so.

* Have all the bases been covered?

It seems so. I have also tried mixing before/after columns in
different orders and the query didn't fail:

zozo=# create table t1 (id serial primary key, i1 int4, i2 int4, t1 text, t2 text);
CREATE TABLE
zozo=# insert into t1 (i1, i2, t1, t2) values (1, 1, 'a', 'a');
INSERT 0 1
zozo=# insert into t1 (i1, i2, t1, t2) values (2, 2, 'b', 'b');
INSERT 0 1
zozo=# insert into t1 (i1, i2, t1, t2) values (3, 3, 'c', 'c');
INSERT 0 1
zozo=# select * from t1;
id | i1 | i2 | t1 | t2
----+----+----+----+----
1 | 1 | 1 | a | a
2 | 2 | 2 | b | b
3 | 3 | 3 | c | c
(3 rows)

zozo=# begin;
BEGIN
zozo=# update t1 set i2 = i2*2, t2 = t2 || 'x2' where id = 2 returning before.i1,
after.i1, before.i2, after.i2, before.t1, after.t1, before.t2, after.t2;
i1 | i1 | i2 | i2 | t1 | t1 | t2 | t2
----+----+----+----+----+----+----+-----
2 | 2 | 2 | 4 | b | b | b | bx2
(1 row)

UPDATE 1
zozo=# update t1 set i1 = i1 * 3, i2 = i2*2, t1 = t1 || 'x3', t2 = t2 || 'x2' where id =
3 returning before.i1, before.i2, after.i1, after.i2, before.t1, before.t2, after.t1,
after.t2; i1 | i2 | i1 | i2 | t1 | t2 | t1 | t2
----+----+----+----+----+----+-----+-----
3 | 3 | 9 | 6 | c | c | cx3 | cx2
(1 row)

UPDATE 1

* Does the feature work as advertised?

Yes.

* Are there corner cases the author has failed to consider?

I don't know.

* Are there any assertion failures or crashes?

No.

* Does the patch slow down simple tests?

No.

* If it claims to improve performance, does it?

n/a

* Does it slow down other things?

No.

* Does it follow the project coding guidelines?

Mostly.

In the src/test/regress/parallel_schedule contains an extra
new line at the end, it shouldn't.

In b/src/backend/optimizer/plan/setrefs.c:

+static void bind_returning_variables(List *rlist, int bef, int aft);

but later it becomes not public:

+ */
+void bind_returning_variables(List *rlist, int bef, int aft)
+{

Strange, but GCC 4.8.1 -Wall doesn't catch it. But the forward
declaration is not needed, the function is called only from
later functions.

Similar for parse_clause.c:

+extern void addAliases(ParseState *pstate);

+void addAliases(ParseState *pstate)

This external declaration is not needed since it is already
in src/include/parser/parse_clause.h

In setrefs.c, bind_returning_variables() I would also rename
the function arguments, so "before" and "after" are spelled out.
These are not C keywords.

Some assignments, like:

+                       var=(Var*)tle;
and
+       int index_rel=1;

in setrefs.c need spaces.

"if()" statements need a space before the "(" and not after.

Add spaces in the {} list in addAliases():
+ char *aliases[] = {"before","after"};
like this: { "before", "after" }

Spaces are needed here, too:
+ for(i=0 ; i<noal; i++)

This "noal" should be "naliases" or "n_aliases" if you really want
a variable. I would simply use the constant "2" for the two for()
loops in addAliases() instead, its purpose is obvious enough.

In setrefs.c, bind_returning_variables():
+       Var *var = NULL;
+       foreach(temp, rlist){
Add an empty line after the declaration block.

* Are there portability issues?

No.

* Will it work on Windows/BSD etc?

Yes.

* Are the comments sufficient and accurate?

There should be more comments, especially regarding
my question at the end.

* Does it do what it says, correctly?

Yes.

* Does it produce compiler warnings?

No.

* Can you make it crash?

No.

* Is everything done in a way that fits together coherently with other features/modules?

I think so, mostly. Comments below.

* Are there interdependencies that can cause problems?

I don't think so.

Other comments:

This #define in pg_class:

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 */

The "RELKIND_*" values all show up in the pg_class table except
this new one. I don't think pg_class.h should be modified at all.
addAliases() should use RELKIND_RELATION together with
RTE_BEFORE. Then checks like:

+               if (rte->relkind == RELKIND_BEFORE)
+                       continue;

should become

+               if (rte->relkind == RELKIND_RELATION && rte->rtekind == RTE_BEFORE)
+                       continue;

Thinking about it more,
if (rte->rtekind == RTE_BEFORE)
would be enough, as no other kinds of rte's can have rtekind == RTE_BEFORE.

Speaking of which, RTE_BEFORE is more properly named
RTE_RETURNING_ALIAS or something like that because it
covers both "before" and "after". Someone may have a better
idea for naming this symbol.

I feel like I understand what the code does and it looks sane to me.

One question, though, about this part:

----------------------------------------
@@ -1900,7 +1954,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;
+
+       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;
----------------------------------------

Why is it enough to keep the last before_index and after_index values?
What if there are more than one matching RangeTblEntry for "before"
and/or for "after"? Is it an error condition or of them should be fixed?

I think that's all for now.

Best regards,
Zoltán Böszörményi

--
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web:http://www.postgresql-support.de
http://www.postgresql.at/

--
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
http://www.postgresql.at/

#62Boszormenyi Zoltan
zb@cybertec.at
In reply to: Boszormenyi Zoltan (#61)
Re: GSOC13 proposal - extend RETURNING syntax

2013-08-21 20:00 keltezéssel, Boszormenyi Zoltan írta:

2013-08-21 19:17 keltezéssel, Boszormenyi Zoltan írta:

Hi,

2013-08-20 21:06 keltezéssel, Karol Trzcionka írta:

W dniu 20.08.2013 20:55, Boszormenyi Zoltan pisze:

Here's a new one, for v7:

setrefs.c: In function ‘set_plan_refs’:
setrefs.c:2001:26: warning: ‘before_index’ may be used uninitialized in this function
[-Wmaybe-uninitialized]
bind_returning_variables(rlist, before_index, after_index);
^
setrefs.c:1957:21: note: ‘before_index’ was declared here
int after_index=0, before_index;
^

Right, my mistake. Sorry and thanks. Fixed.
Regards,
Karol Trzcionka

With this fixed, a more complete review:

* Is the patch in a patch format which has context? (eg: context diff format)

Yes.

* Does it apply cleanly to the current git master?

Yes.

* Does it include reasonable tests, necessary doc patches, etc?

There is a new regression test (returning_before_after.sql) covering
this feature. However, I think it should be added to the group
where "returning.sql" resides currently. There is a value in running it
in parallel to other tests. Sometimes a subtle bug is uncovered
because of this and your v2 patch fixed such a bug already.

doc/src/sgml/ref/update.sgml describes this feature.

doc/src/sgml/dml.sgml should also be touched to cover this feature.

* Does the patch actually implement what it's supposed to do?

Yes.

* Do we want that?

Yes.

* Do we already have it?

No.

* Does it follow SQL spec, or the community-agreed behavior?

RETURNING is a PostgreSQL extension, so the SQL-spec part
of the question isn't applicable.

It implements the community-agreed behaviour, according to
the new regression test coverage.

* Does it include pg_dump support (if applicable)?

n/a

* Are there dangers?

I don't think so.

* Have all the bases been covered?

It seems so. I have also tried mixing before/after columns in
different orders and the query didn't fail:

zozo=# create table t1 (id serial primary key, i1 int4, i2 int4, t1 text, t2 text);
CREATE TABLE
zozo=# insert into t1 (i1, i2, t1, t2) values (1, 1, 'a', 'a');
INSERT 0 1
zozo=# insert into t1 (i1, i2, t1, t2) values (2, 2, 'b', 'b');
INSERT 0 1
zozo=# insert into t1 (i1, i2, t1, t2) values (3, 3, 'c', 'c');
INSERT 0 1
zozo=# select * from t1;
id | i1 | i2 | t1 | t2
----+----+----+----+----
1 | 1 | 1 | a | a
2 | 2 | 2 | b | b
3 | 3 | 3 | c | c
(3 rows)

zozo=# begin;
BEGIN
zozo=# update t1 set i2 = i2*2, t2 = t2 || 'x2' where id = 2 returning before.i1,
after.i1, before.i2, after.i2, before.t1, after.t1, before.t2, after.t2;
i1 | i1 | i2 | i2 | t1 | t1 | t2 | t2
----+----+----+----+----+----+----+-----
2 | 2 | 2 | 4 | b | b | b | bx2
(1 row)

UPDATE 1
zozo=# update t1 set i1 = i1 * 3, i2 = i2*2, t1 = t1 || 'x3', t2 = t2 || 'x2' where id
= 3 returning before.i1, before.i2, after.i1, after.i2, before.t1, before.t2, after.t1,
after.t2; i1 | i2 | i1 | i2 | t1 | t2 | t1 | t2
----+----+----+----+----+----+-----+-----
3 | 3 | 9 | 6 | c | c | cx3 | cx2
(1 row)

UPDATE 1

* Does the feature work as advertised?

Yes.

* Are there corner cases the author has failed to consider?

I don't know.

* Are there any assertion failures or crashes?

No.

* Does the patch slow down simple tests?

No.

* If it claims to improve performance, does it?

n/a

* Does it slow down other things?

No.

* Does it follow the project coding guidelines?

Mostly.

In the src/test/regress/parallel_schedule contains an extra
new line at the end, it shouldn't.

In b/src/backend/optimizer/plan/setrefs.c:

+static void bind_returning_variables(List *rlist, int bef, int aft);

but later it becomes not public:

+ */
+void bind_returning_variables(List *rlist, int bef, int aft)
+{

Strange, but GCC 4.8.1 -Wall doesn't catch it. But the forward
declaration is not needed, the function is called only from
later functions.

Similar for parse_clause.c:

+extern void addAliases(ParseState *pstate);

+void addAliases(ParseState *pstate)

This external declaration is not needed since it is already
in src/include/parser/parse_clause.h

In setrefs.c, bind_returning_variables() I would also rename
the function arguments, so "before" and "after" are spelled out.
These are not C keywords.

Some assignments, like:

+                       var=(Var*)tle;
and
+       int index_rel=1;

in setrefs.c need spaces.

"if()" statements need a space before the "(" and not after.

Add spaces in the {} list in addAliases():
+ char *aliases[] = {"before","after"};
like this: { "before", "after" }

Spaces are needed here, too:
+ for(i=0 ; i<noal; i++)

This "noal" should be "naliases" or "n_aliases" if you really want
a variable. I would simply use the constant "2" for the two for()
loops in addAliases() instead, its purpose is obvious enough.

In setrefs.c, bind_returning_variables():
+       Var *var = NULL;
+       foreach(temp, rlist){
Add an empty line after the declaration block.

* Are there portability issues?

No.

* Will it work on Windows/BSD etc?

Yes.

* Are the comments sufficient and accurate?

There should be more comments, especially regarding
my question at the end.

* Does it do what it says, correctly?

Yes.

* Does it produce compiler warnings?

No.

* Can you make it crash?

No.

* Is everything done in a way that fits together coherently with other features/modules?

I think so, mostly. Comments below.

* Are there interdependencies that can cause problems?

I don't think so.

Other comments:

This #define in pg_class:

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 */

The "RELKIND_*" values all show up in the pg_class table except
this new one. I don't think pg_class.h should be modified at all.
addAliases() should use RELKIND_RELATION together with
RTE_BEFORE. Then checks like:

+               if (rte->relkind == RELKIND_BEFORE)
+                       continue;

should become

+               if (rte->relkind == RELKIND_RELATION && rte->rtekind == RTE_BEFORE)
+                       continue;

Thinking about it more,
if (rte->rtekind == RTE_BEFORE)
would be enough, as no other kinds of rte's can have rtekind == RTE_BEFORE.

Speaking of which, RTE_BEFORE is more properly named
RTE_RETURNING_ALIAS or something like that because it
covers both "before" and "after". Someone may have a better
idea for naming this symbol.

I feel like I understand what the code does and it looks sane to me.

One question, though, about this part:

----------------------------------------
@@ -1900,7 +1954,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;
+
+       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;
----------------------------------------

Why is it enough to keep the last before_index and after_index values?
What if there are more than one matching RangeTblEntry for "before"
and/or for "after"? Is it an error condition or of them should be fixed?

Since addAliases() only adds a single one for each and only for an
UPDATE ... RETURNING query, it is okay. Also, because
set_returning_clause_references() is called separately for each
query with RETURNING. Anyway, a comment before the new
foreach() loop in set_returning_clause_references() should explain
the fact that only one of each ("before" and "after") can occur for
such a query.

I have just tried this:

before as (update t1 set i1 = i1 * 2, i2 = i2 * 3, t1 = t1 || 'x2', t2 = t2 || 'x3'
where id = 1 returning before.i1, after.i1, before.i2, after.i2),
after as (update t1 set i1 = i1 * 2, i2 = i2 * 3, t1 = t1 || 'x2', t2 = t2 || 'x3'
where id = 2 returning before.i1, after.i1, before.i2, after.i2)
select * from (select * from before union all select * from after) as x;

and it gave me the proper results, no crash.

About addAliases():
- it can be moved to parser/analyze.c so it can be static.
- addReturningAliases() may be a better name for the function.

I think that's all for now.

Best regards,
Zoltán Böszörményi

--
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web:http://www.postgresql-support.de
http://www.postgresql.at/

--
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web:http://www.postgresql-support.de
http://www.postgresql.at/

--
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
http://www.postgresql.at/

#63Karol Trzcionka
karlikt@gmail.com
In reply to: Boszormenyi Zoltan (#60)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

W dniu 21.08.2013 19:17, Boszormenyi Zoltan pisze:

With this fixed, a more complete review:

Thanks.

There is a new regression test (returning_before_after.sql) covering
this feature. However, I think it should be added to the group
where "returning.sql" resides currently. There is a value in running it
in parallel to other tests. Sometimes a subtle bug is uncovered
because of this and your v2 patch fixed such a bug already.

Modified to work correct in parallel testing

doc/src/sgml/ref/update.sgml describes this feature.

doc/src/sgml/dml.sgml should also be touched to cover this feature.

I don't think so, there is not any info about returning feature, I think
it shouldn't be part of my patch.

In the src/test/regress/parallel_schedule contains an extra
new line at the end, it shouldn't.

Fixed

In b/src/backend/optimizer/plan/setrefs.c:

+static void bind_returning_variables(List *rlist, int bef, int aft);

but later it becomes not public:

+ */
+void bind_returning_variables(List *rlist, int bef, int aft)
+{

I've change to static in the definition.

+extern void addAliases(ParseState *pstate);

+void addAliases(ParseState *pstate)

This external declaration is not needed since it is already
in src/include/parser/parse_clause.h

Removed.

In setrefs.c, bind_returning_variables() I would also rename
the function arguments, so "before" and "after" are spelled out.
These are not C keywords.

Changed.

Some assignments, like:

+                       var=(Var*)tle;
and
+       int index_rel=1;

in setrefs.c need spaces.

"if()" statements need a space before the "(" and not after.

Add spaces in the {} list in addAliases():
+ char *aliases[] = {"before","after"};
like this: { "before", "after" }

Spaces are needed here, too:
+ for(i=0 ; i<noal; i++)

This "noal" should be "naliases" or "n_aliases" if you really want
a variable. I would simply use the constant "2" for the two for()
loops in addAliases() instead, its purpose is obvious enough.

Added some whitespaces.

In setrefs.c, bind_returning_variables():
+       Var *var = NULL;
+       foreach(temp, rlist){
Add an empty line after the declaration block.

Added.

Other comments:

This #define in pg_class:

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 */

RELKIND_BEFORE removed - it probably left over previous work and/or I
needed it because RTE_BEFORE wasn't available (not included?).

Speaking of which, RTE_BEFORE is more properly named
RTE_RETURNING_ALIAS or something like that because it
covers both "before" and "after". Someone may have a better
idea for naming this symbol.

Renamed to RTE_ALIAS - similar to addAliases (not addReturningAliases)

One question, though, about this part:

----------------------------------------
@@ -1900,7 +1954,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;
+
+       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;
----------------------------------------

Why is it enough to keep the last before_index and after_index values?
What if there are more than one matching RangeTblEntry for "before"
and/or for "after"? Is it an error condition or of them should be fixed?

I think it is safe, it is the first and the last index. On each level of
statement there can be (at most) the only one "before" and one "after"
alias.
Regards,
Karol Trzcionka

Attachments:

before_after_v9.patchtext/x-patch; name=before_after_v9.patchDownload
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..3485bd7 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_ALIAS:
 				/* nothing to do */
 				break;
 			case RTE_SUBQUERY:
@@ -2701,6 +2702,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 cff4734..09138ff 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_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 aad63e5..7e05db6 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_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 c5998b9..905b718 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_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 bcc0d45..66b1658 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->rtekind == RTE_ALIAS)
+			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->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 b78d727..6bf510a 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);
 
 
 /*****************************************************************************
@@ -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_ALIAS)
+					var->varnoold += context->rtoffset;
+			}
 			return (Node *) var;
 		}
 
@@ -1865,6 +1869,58 @@ 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
+ * '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))
+			{
+				if (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
  *
@@ -1900,7 +1956,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;
+
+	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
@@ -1924,6 +2000,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..aff6538 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_ALIAS)
+			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_ALIAS:
 					/* 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_ALIAS:
 						/* 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_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 fb67f9e..268ae79 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_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 2ac215f..a298edb 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,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_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 4a3d5c8..ea9330a 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_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
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..e1e1829 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -84,7 +84,55 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 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,
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 39922d3..1485bdc 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_ALIAS:
 			/* 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_ALIAS:
 			{
 				/* 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_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 9c6c202..3a1f959 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:
@@ -1422,6 +1423,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 2b005d6..c3c5519 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_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 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..3c80585 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_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 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..be175b6
--- /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 fd08e8d..af39d1d 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 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..193ffd9
--- /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;
#64Boszormenyi Zoltan
zb@cybertec.at
In reply to: Karol Trzcionka (#63)
Re: GSOC13 proposal - extend RETURNING syntax

Hi,

2013-08-21 20:52 keltezéssel, Karol Trzcionka írta:

W dniu 21.08.2013 19:17, Boszormenyi Zoltan pisze:

With this fixed, a more complete review:

Thanks.

There is a new regression test (returning_before_after.sql) covering
this feature. However, I think it should be added to the group
where "returning.sql" resides currently. There is a value in running it
in parallel to other tests. Sometimes a subtle bug is uncovered
because of this and your v2 patch fixed such a bug already.

Modified to work correct in parallel testing

doc/src/sgml/ref/update.sgml describes this feature.

doc/src/sgml/dml.sgml should also be touched to cover this feature.

I don't think so, there is not any info about returning feature, I think it shouldn't be
part of my patch.

In the src/test/regress/parallel_schedule contains an extra
new line at the end, it shouldn't.

Fixed

In b/src/backend/optimizer/plan/setrefs.c:

+static void bind_returning_variables(List *rlist, int bef, int aft);

but later it becomes not public:

+ */
+void bind_returning_variables(List *rlist, int bef, int aft)
+{

I've change to static in the definition.

+extern void addAliases(ParseState *pstate);

+void addAliases(ParseState *pstate)

This external declaration is not needed since it is already
in src/include/parser/parse_clause.h

Removed.

In setrefs.c, bind_returning_variables() I would also rename
the function arguments, so "before" and "after" are spelled out.
These are not C keywords.

Changed.

Some assignments, like:

+                       var=(Var*)tle;
and
+       int index_rel=1;

in setrefs.c need spaces.

"if()" statements need a space before the "(" and not after.

Add spaces in the {} list in addAliases():
+ char *aliases[] = {"before","after"};
like this: { "before", "after" }

Spaces are needed here, too:
+ for(i=0 ; i<noal; i++)

This "noal" should be "naliases" or "n_aliases" if you really want
a variable. I would simply use the constant "2" for the two for()
loops in addAliases() instead, its purpose is obvious enough.

Added some whitespaces.

In setrefs.c, bind_returning_variables():
+       Var *var = NULL;
+       foreach(temp, rlist){
Add an empty line after the declaration block.

Added.

Other comments:

This #define in pg_class:

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 */

RELKIND_BEFORE removed - it probably left over previous work and/or I needed it because
RTE_BEFORE wasn't available (not included?).

Speaking of which, RTE_BEFORE is more properly named
RTE_RETURNING_ALIAS or something like that because it
covers both "before" and "after". Someone may have a better
idea for naming this symbol.

Renamed to RTE_ALIAS - similar to addAliases (not addReturningAliases)

Others may have also a word in this topic, but consider that
this is *not* a regular alias and for those, RTE_ALIAS is not used,
like in

UPDATE table AS alias SET ...

Maybe RTE_RETURNING_ALIAS takes a little more typing, but
it becomes obvious when reading and new code won't confuse
it with regular aliases.

One question, though, about this part:

----------------------------------------
@@ -1900,7 +1954,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;
+
+       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;
----------------------------------------

Why is it enough to keep the last before_index and after_index values?
What if there are more than one matching RangeTblEntry for "before"
and/or for "after"? Is it an error condition or of them should be fixed?

I think it is safe, it is the first and the last index. On each level of statement there
can be (at most) the only one "before" and one "after" alias.

I deduced it in the meantime. I still think it worth a comment
in setrefs.c.

I think your v9 patch can be looked at by a more seasoned reviewer
or a committer.

Best regards,
Zoltán Böszörményi

Regards,
Karol Trzcionka

--
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
http://www.postgresql.at/

#65Robert Haas
robertmhaas@gmail.com
In reply to: Boszormenyi Zoltan (#64)
Re: GSOC13 proposal - extend RETURNING syntax

I took a look at this patch today, and I'm pretty skeptical about
whether it's on the right track. It adds a new kind of RTE called
RTE_ALIAS, which doesn't seem particularly descriptive and alias is
used elsewhere to mean something fairly different. More generally,
I'm not convinced that adding a new type of RTE is the right way to
handle this. The changes in pull_up_subqueries_recurse,
pullup_replace_vars_callback, preprocess_targetlist, and
build_joinrel_tlist seem like weird hacks. Those functions aren't
directly related to this feature; why do they need to know about it?

I wonder if we shouldn't be trying to handle resolution of these names
at an earlier processing stage, closer to the processor. I notice
that set_returning_clause_references() seems to have already solved
the hard part of this problem, which is frobbing target list entries
to return values from the new row rather than, as they naturally
would, the old row. In fact, we can already get approximately the
desired effect already:

rhaas=# update foo as after set a = before.a + 1 from foo as before
where before.a = after.a returning before.a, after.a;
a | a
---+---
1 | 2
(1 row)

Now this is a hack, because we don't really want to add an extra
scan/join just to get the behavior we want. But it seems to me
significant that this processing makes Vars that refer to the target
table refer to the new values, and if we got rid of it, they'd refer
to the old values. Can't we contrive to make AFTER.x parse into the
same Var node that x currently does? Then we don't need an RTE for
it. And maybe BEFORE.x ought to parse to the same node that just
plain x does but with some marking, or some other node wrapped around
it (like a TargetEntry with some flag set?) that suppresses this
processing. I'm just shooting from the hip here; that might be wrong
in detail, or even in broad strokes, but it just looks to me like the
additional RTE kind is going to bleed into a lot of places.

This patch also has significant style issues. Conforming to
PostgreSQL coding style is essential; if neither the author nor the
reviewer fixes problems in this area, then that is essentially making
it the committer's job, and the committer may not feel like taking
time to do that. Here's a selection of issues that I noticed while
reading this through: we use spaces around operators; the patch adds
two blank lines that shouldn't be there to the middle of the variable
declarations section; variables should be declared in the innermost
possible scope; single-statement blocks shouldn't have curly braces;
there shouldn't be whitespace before a closing parenthesis; there
should be a space after if and before the subsequent parenthesis;
braces should be uncuddled; code that does non-obvious things isn't
commented.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#66Karol Trzcionka
karlikt@gmail.com
In reply to: Robert Haas (#65)
Re: GSOC13 proposal - extend RETURNING syntax

W dniu 04.10.2013 00:28, Robert Haas pisze:

I wonder if we shouldn't be trying to handle resolution of these names
at an earlier processing stage, closer to the processor.

Maybe it can be done in parser (in flex?) but at now it seems to be more
isolated feature.

In fact, we can already get approximately the
desired effect already:

rhaas=# update foo as after set a = before.a + 1 from foo as before
where before.a = after.a returning before.a, after.a;
a | a
---+---
1 | 2
(1 row)

Compare EXPLAIN ANALYZE VERBOSE on your statement and on "patched"
workflow. I can see significant difference. And your "after" returns the
value after whole the work (after trigger fired) as I know (I don't know
if it is needed or not, I only point at the difference).

Now this is a hack, because we don't really want to add an extra
scan/join just to get the behavior we want. But it seems to me
significant that this processing makes Vars that refer to the target
table refer to the new values, and if we got rid of it, they'd refer
to the old values. Can't we contrive to make AFTER.x parse into the
same Var node that x currently does? Then we don't need an RTE for
it. And maybe BEFORE.x ought to parse to the same node that just
plain x does but with some marking, or some other node wrapped around
it (like a TargetEntry with some flag set?) that suppresses this
processing. I'm just shooting from the hip here; that might be wrong
in detail, or even in broad strokes, but it just looks to me like the
additional RTE kind is going to bleed into a lot of places.

While planning/analyzing the problem there were many ideas about hot to
solve it. I was trying to avoid adding new RTE and referencing to "core"
table. However it makes more and more issues. You can see some PoC on
the
https://github.com/davidfetter/postgresql_projects/compare/returning_before_after
(other ideas I revert w/o commit because I couldn't get expected
result). The other major reason was that we can avoid touching executor
and/or parser's core (flex) this way. One observation: why shouldn't we
use the values computed at the moment (it would be computed again if we
want to do it later, in executor)?
I think we can do it by modify the Var structure (add some kind of flag
while generating the vars in parser?) but I'm not sure if it is good
idea. The major issue is to know if the Var/TargetEntry references to
the real alias "BEFORE" (named with "AS" syntax or even the real
table-name - I can see there is no difference in code) or the virtual
(from feature patch) "BEFORE". Doing it in parser (more "low-level")
would be very awful - we'd need to check in which part of statement
BEFORE/AFTER is placed (it is not allowed to use it in the other places
than in "RETURNING"). We don't want to make "BEFORE" and "AFTER"
restricted keywords.
Now most of the code means "don't touch these because they are not real" :)
If anyone has the fresh idea to it better, please write it by mail, I
don't have more ideas how to solve it.

This patch also has significant style issues.

I'll try to fix it soon.
Regards,
Karol Trzcionka

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#67Robert Haas
robertmhaas@gmail.com
In reply to: Karol Trzcionka (#66)
Re: GSOC13 proposal - extend RETURNING syntax

On Thu, Oct 3, 2013 at 7:54 PM, Karol Trzcionka <karlikt@gmail.com> wrote:

Compare EXPLAIN ANALYZE VERBOSE on your statement and on "patched"
workflow. I can see significant difference. And your "after" returns the
value after whole the work (after trigger fired) as I know (I don't know
if it is needed or not, I only point at the difference).

Sure, I'm not saying we should implement it that way. I'm just
pointing out that the ability already exists, at the executor level,
to return either tuple. So I think the executor itself shouldn't need
to be changed; it's just a matter of getting the correct plan tree to
pop out.

While planning/analyzing the problem there were many ideas about hot to
solve it.

Do you have a link to previous discussion on the mailing list?

I think we can do it by modify the Var structure (add some kind of flag
while generating the vars in parser?) but I'm not sure if it is good
idea. The major issue is to know if the Var/TargetEntry references to
the real alias "BEFORE" (named with "AS" syntax or even the real
table-name - I can see there is no difference in code) or the virtual
(from feature patch) "BEFORE". Doing it in parser (more "low-level")
would be very awful - we'd need to check in which part of statement
BEFORE/AFTER is placed (it is not allowed to use it in the other places
than in "RETURNING"). We don't want to make "BEFORE" and "AFTER"
restricted keywords.

You're right, it can't happen actually in the parser. But maybe it
can happen during parse analysis. I'd spend some time looking at
transformColumnRef(), because that's where we translate things x.y
into Var nodes. I'm not positive there's enough information available
at that stage, but if p_target_rangetblentry is populated at that
point, you should be able to make AFTER.x translate to a Var
referencing that range table entry. It's a bit less clear how we know
that we're inside the returning-list at that point; I'm not sure how
much work it would be to pass that information down. But I think it's
worth looking at.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#68Karol Trzcionka
karlikt@gmail.com
In reply to: Robert Haas (#67)
Re: GSOC13 proposal - extend RETURNING syntax

W dniu 04.10.2013 02:51, Robert Haas pisze:

Do you have a link to previous discussion on the mailing list?

Sorry, most of discussion was at IRC channel.

I'm not positive there's enough information available
at that stage, but if p_target_rangetblentry is populated at that
point, you should be able to make AFTER.x translate to a Var
referencing that range table entry.

It's not enough. Even if we know "where we are", there are more issues.
The main question is: how should we pass information about "hello, I'm
specific Var, don't evaluate me like others"? We can add two fields to
Var structure (flag - normal/before/after and no. column) - however it
needs to modify copyObject for Vars (at now it's done e.g. in
flatten_join_alias_vars_mutator for varoattno and varnoold). If
copyObject is modified, sure code in
flatten_join_alias_vars_mutator/pullup_replace_vars_callback will be
useless. I don't know if modifying pg at the low-level (changing
structure of Var and behaviour of copyObject) is good idea. Yes if the
community really want it but it needs more "votes". There is "medium"
solution: changing Var structure and do the "copy" like now (in mutator
and callback) but w/o the condition statement (for the new fields). I
think it might need to modify more places in code because of "comparing"
vars (maybe we'd need to include new fields while comparision).
Regards,
Karol Trzcionka

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#69Marko Tiikkaja
marko@joh.to
In reply to: Robert Haas (#65)
Re: GSOC13 proposal - extend RETURNING syntax

On 10/4/13 12:28 AM, Robert Haas wrote:

In fact, we can already get approximately the
desired effect already:

rhaas=# update foo as after set a = before.a + 1 from foo as before
where before.a = after.a returning before.a, after.a;
a | a
---+---
1 | 2
(1 row)

Now this is a hack, because we don't really want to add an extra
scan/join just to get the behavior we want. But it seems to me
significant that this processing makes Vars that refer to the target
table refer to the new values, and if we got rid of it, they'd refer
to the old values. Can't we contrive to make AFTER.x parse into the
same Var node that x currently does? Then we don't need an RTE for
it.

This part sounds reasonable from here.

And maybe BEFORE.x ought to parse to the same node that just
plain x does but with some marking, or some other node wrapped around
it (like a TargetEntry with some flag set?) that suppresses this
processing. I'm just shooting from the hip here; that might be wrong
in detail, or even in broad strokes, but it just looks to me like the
additional RTE kind is going to bleed into a lot of places.

I might be completely in the woods here, but I believe something like
this was attempted by Karol earlier, and it failed if two concurrent
transactions did something similar to:

UPDATE foo SET a = a + 1 RETURNING BEFORE.a;

Both of them would see BEFORE.a = 0, because that's what the "a"
evaluated to from the tuple we got before EvalPlanQual.

But maybe what you're suggesting doesn't have this problem?

Regards,
Marko Tiikkaja

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#70Andres Freund
andres@2ndquadrant.com
In reply to: Robert Haas (#67)
Re: GSOC13 proposal - extend RETURNING syntax

On 2013-10-03 20:51:08 -0400, Robert Haas wrote:

On Thu, Oct 3, 2013 at 7:54 PM, Karol Trzcionka <karlikt@gmail.com> wrote:

Compare EXPLAIN ANALYZE VERBOSE on your statement and on "patched"
workflow. I can see significant difference. And your "after" returns the
value after whole the work (after trigger fired) as I know (I don't know
if it is needed or not, I only point at the difference).

Sure, I'm not saying we should implement it that way. I'm just
pointing out that the ability already exists, at the executor level,
to return either tuple. So I think the executor itself shouldn't need
to be changed; it's just a matter of getting the correct plan tree to
pop out.

Note what pullups ExecDelete is doing to return the old tuple
though... So, based on precedent special executor support is not an
unlikely thing to be required for a proper implemenation. As Marko
mentions, any trivial implementation not doing playing dirty like that
will refer to the wrong version of the tuple.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#71Robert Haas
robertmhaas@gmail.com
In reply to: Karol Trzcionka (#68)
Re: GSOC13 proposal - extend RETURNING syntax

On Fri, Oct 4, 2013 at 4:42 AM, Karol Trzcionka <karlikt@gmail.com> wrote:

W dniu 04.10.2013 02:51, Robert Haas pisze:

Do you have a link to previous discussion on the mailing list?

Sorry, most of discussion was at IRC channel.

I'm not positive there's enough information available
at that stage, but if p_target_rangetblentry is populated at that
point, you should be able to make AFTER.x translate to a Var
referencing that range table entry.

It's not enough. Even if we know "where we are", there are more issues.
The main question is: how should we pass information about "hello, I'm
specific Var, don't evaluate me like others"?

My point is that AFTER.x doesn't appear to need any special marking;
it means the same thing as target_table.x. BEFORE.x *does* need some
kind of special marking, and I admit I'm not sure what that should
look like. Maybe an RTE is OK, but letting that RTE get into the join
planning machinery does not seem good; that's going to result in funky
special cases all over the place.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#72Robert Haas
robertmhaas@gmail.com
In reply to: Marko Tiikkaja (#69)
Re: GSOC13 proposal - extend RETURNING syntax

On Fri, Oct 4, 2013 at 5:04 AM, Marko Tiikkaja <marko@joh.to> wrote:

I might be completely in the woods here, but I believe something like this
was attempted by Karol earlier, and it failed if two concurrent transactions
did something similar to:

UPDATE foo SET a = a + 1 RETURNING BEFORE.a;

Both of them would see BEFORE.a = 0, because that's what the "a" evaluated
to from the tuple we got before EvalPlanQual.

But maybe what you're suggesting doesn't have this problem?

Hmm, it probably does. That explains why there are executor changes
here; I guess they need some comments to explain their purpose.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#73David Fetter
david@fetter.org
In reply to: Karol Trzcionka (#68)
Re: GSOC13 proposal - extend RETURNING syntax

On Fri, Oct 04, 2013 at 10:42:32AM +0200, Karol Trzcionka wrote:

W dniu 04.10.2013 02:51, Robert Haas pisze:

Do you have a link to previous discussion on the mailing list?

Sorry, most of discussion was at IRC channel.

I'm not positive there's enough information available
at that stage, but if p_target_rangetblentry is populated at that
point, you should be able to make AFTER.x translate to a Var
referencing that range table entry.

It's not enough. Even if we know "where we are", there are more issues.
The main question is: how should we pass information about "hello, I'm
specific Var, don't evaluate me like others"? We can add two fields to
Var structure (flag - normal/before/after and no. column) - however it
needs to modify copyObject for Vars (at now it's done e.g. in
flatten_join_alias_vars_mutator for varoattno and varnoold). If
copyObject is modified, sure code in
flatten_join_alias_vars_mutator/pullup_replace_vars_callback will be
useless. I don't know if modifying pg at the low-level (changing
structure of Var and behaviour of copyObject) is good idea. Yes if the
community really want it but it needs more "votes". There is "medium"
solution: changing Var structure and do the "copy" like now (in mutator
and callback) but w/o the condition statement (for the new fields). I
think it might need to modify more places in code because of "comparing"
vars (maybe we'd need to include new fields while comparision).
Regards,
Karol Trzcionka

Karol,

Do you plan to continue this work for the current commitfest? A lot
of people really want the feature :)

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#74David Fetter
david@fetter.org
In reply to: Karol Trzcionka (#63)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

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?

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@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

Attachments:

before_after_v10.patchtext/plain; charset=us-asciiDownload
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 194,205 **** 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.
       </para>
      </listitem>
     </varlistentry>
  
--- 194,220 ----
      <term><replaceable class="PARAMETER">output_expression</replaceable></term>
      <listitem>
       <para>
!       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,301 **** 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:
  
  <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;
  </programlisting>
    </para>
  
    <para>
     Use the alternative column-list syntax to do the same update:
  <programlisting>
--- 302,317 ----
    </para>
  
    <para>
!    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 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>
*** 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 (<exprlist>), (<exprlist>), ... */
! 	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 (<exprlist>), (<exprlist>), ... */
! 	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;
#75David Fetter
david@fetter.org
In reply to: David Fetter (#74)
1 attachment(s)
Re: GSOC13 proposal - extend RETURNING syntax

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 <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@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

Attachments:

before_after_v11.patchtext/plain; charset=us-asciiDownload
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;
#76Andres Freund
andres@anarazel.de
In reply to: David Fetter (#75)
Re: GSOC13 proposal - extend RETURNING syntax

Hi,

Some comments about the patch:
* Coding Style:
* multiline comments have both /* and */ on their own lines.
* I think several places indent by two tabs.
* Spaces around operators
* ...
* Many of the new comments would enjoy a bit TLC by a native speaker.

* The way RTE_ALIAS creeps in many place where it doesn't seem to belong
seems to indicate that the design isn't yet ready. I share Robert's
suspicion that this would be better solved by referring to a special
range table entry.

Based on the lack of activity around this and the fact that this needs a
*significant* chunk of work before being committable, I am going to mark
this as returned with feedback.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#77Arthur Axel 'fREW' Schmidt
frew@afoolishmanifesto.com
In reply to: Boszormenyi Zoltan (#54)
Re: GSOC13 proposal - extend RETURNING syntax

Andres Freund <andres <at> anarazel.de> writes:

Hi,

Some comments about the patch:
* Coding Style:
* multiline comments have both /* and */ on their own lines.
* I think several places indent by two tabs.
* Spaces around operators
* ...
* Many of the new comments would enjoy a bit TLC by a native speaker.

* The way RTE_ALIAS creeps in many place where it doesn't seem to belong
seems to indicate that the design isn't yet ready. I share Robert's
suspicion that this would be better solved by referring to a special
range table entry.

Based on the lack of activity around this and the fact that this needs a
*significant* chunk of work before being committable, I am going to mark
this as returned with feedback.

I'm actively working towards converting our software at work to use Pg
instead of SQL Server and before we switch we'll need this feature to be
merged. I'll do what I can to get the verbage and style whipped into shape,
though I doubt I can do much with the actual code. Hopefully I can get
something in soon.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers