Re: INSERT ... RETURNING - Mailing list pgsql-patches

From Bruce Momjian
Subject Re: INSERT ... RETURNING
Date
Msg-id 200508121948.j7CJmW114722@candle.pha.pa.us
Whole thread Raw
In response to Re: INSERT ... RETURNING  (Omar Kilani <omar@tinysofa.org>)
List pgsql-patches
This has been saved for the 8.2 release:

    http://momjian.postgresql.org/cgi-bin/pgpatches_hold

---------------------------------------------------------------------------

Omar Kilani wrote:
> Bruce,
>
> > Are you still working on completing this?
>
> Attached is my latest rewrite of this patch.
>
> Basically, it adds RETURNING to INSERT/UPDATE/DELETE, and allows
> arbitrary expressions.
>
> I'm still not sure how to handle DELETE ... USING and UPDATE ... FROM as
> I don't see a way to project the expressions against multiple relations
> with just an EState to work from.
>
> That said, Tom has suggested I redo this using resjunk -- so I'll do
> just that for my next revision.
>
> Regards,
> Omar

> Index: doc/src/sgml/keywords.sgml
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/doc/src/sgml/keywords.sgml,v
> retrieving revision 2.15
> diff -C6 -r2.15 keywords.sgml
> *** doc/src/sgml/keywords.sgml    27 Nov 2004 21:27:06 -0000    2.15
> --- doc/src/sgml/keywords.sgml    30 Jul 2005 06:37:02 -0000
> ***************
> *** 3234,3245 ****
> --- 3234,3252 ----
>       <entry></entry>
>       <entry>non-reserved</entry>
>       <entry>non-reserved</entry>
>       <entry>non-reserved</entry>
>      </row>
>      <row>
> +     <entry><token>RETURNING</token></entry>
> +     <entry>reserved</entry>
> +     <entry></entry>
> +     <entry></entry>
> +     <entry></entry>
> +    </row>
> +    <row>
>       <entry><token>RETURNS</token></entry>
>       <entry>non-reserved</entry>
>       <entry>reserved</entry>
>       <entry>reserved</entry>
>       <entry></entry>
>      </row>
> Index: doc/src/sgml/ref/delete.sgml
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/doc/src/sgml/ref/delete.sgml,v
> retrieving revision 1.24
> diff -C6 -r1.24 delete.sgml
> *** doc/src/sgml/ref/delete.sgml    8 Apr 2005 00:59:58 -0000    1.24
> --- doc/src/sgml/ref/delete.sgml    30 Jul 2005 06:37:02 -0000
> ***************
> *** 20,31 ****
> --- 20,32 ----
>
>    <refsynopsisdiv>
>   <synopsis>
>   DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table</replaceable>
>       [ USING <replaceable class="PARAMETER">usinglist</replaceable> ]
>       [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
> +     [ RETURNING <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
>   </synopsis>
>    </refsynopsisdiv>
>
>    <refsect1>
>     <title>Description</title>
>
> ***************
> *** 102,131 ****
>         A value expression that returns a value of type
>         <type>boolean</type> that determines the rows which are to be
>         deleted.
>        </para>
>       </listitem>
>      </varlistentry>
>     </variablelist>
>    </refsect1>
>
>    <refsect1>
>     <title>Outputs</title>
>
>     <para>
> !    On successful completion, a <command>DELETE</> command returns a command
> !    tag of the form
>   <screen>
>   DELETE <replaceable class="parameter">count</replaceable>
>   </screen>
>      The <replaceable class="parameter">count</replaceable> is the number
>      of rows deleted.  If <replaceable class="parameter">count</replaceable> is
>      0, no rows matched the <replaceable
>      class="parameter">condition</replaceable> (this is not considered
>      an error).
>     </para>
>    </refsect1>
>
>    <refsect1>
>     <title>Notes</title>
>
>     <para>
> --- 103,155 ----
>         A value expression that returns a value of type
>         <type>boolean</type> that determines the rows which are to be
>         deleted.
>        </para>
>       </listitem>
>      </varlistentry>
> +
> +    <varlistentry>
> +     <term>
> +       <literal>RETURNING</literal>
> +       <replaceable class="PARAMETER">expression</replaceable> [, ...]
> +     </term>
> +     <listitem>
> +      <para>
> +        An optional list of expressions to return.
> +      </para>
> +     </listitem>
> +    </varlistentry>
>     </variablelist>
>    </refsect1>
>
>    <refsect1>
>     <title>Outputs</title>
>
>     <para>
> !    On successful completion, a <command>DELETE</> command without a
> !    <literal>RETURNING</literal> clause returns a command tag of the form
>   <screen>
>   DELETE <replaceable class="parameter">count</replaceable>
>   </screen>
>      The <replaceable class="parameter">count</replaceable> is the number
>      of rows deleted.  If <replaceable class="parameter">count</replaceable> is
>      0, no rows matched the <replaceable
>      class="parameter">condition</replaceable> (this is not considered
>      an error).
>     </para>
> +
> +   <indexterm zone="sql-delete">
> +    <primary>RETURNING</primary>
> +    <secondary>DELETE</secondary>
> +   </indexterm>
> +
> +   <para>
> +    If a <literal>RETURNING</literal> clause is present, the expression
> +    specified is evaluated for each deleted row and the result is
> +    returned.
> +   </para>
>    </refsect1>
>
>    <refsect1>
>     <title>Notes</title>
>
>     <para>
> ***************
> *** 158,183 ****
>   <programlisting>
>   DELETE FROM films WHERE kind <> 'Musical';
>   </programlisting>
>     </para>
>
>     <para>
>      Clear the table <literal>films</literal>:
>   <programlisting>
>   DELETE FROM films;
>   </programlisting>
>     </para>
>    </refsect1>
>
>    <refsect1>
>     <title>Compatibility</title>
>
>     <para>
>      This command conforms to the SQL standard, except that the
> !    <literal>USING</> clause and the ability to reference other tables
> !    in the <literal>WHERE</> clause are <productname>PostgreSQL</>
>      extensions.
>     </para>
>    </refsect1>
>   </refentry>
>
>   <!-- Keep this comment at the end of the file
> --- 182,220 ----
>   <programlisting>
>   DELETE FROM films WHERE kind <> 'Musical';
>   </programlisting>
>     </para>
>
>     <para>
> +     Delete all films but comedies returning film titles using the
> +     <literal>RETURNING</literal> clause:
> + <programlisting>
> + DELETE FROM films WHERE kind <> 'Comedy' RETURNING title;
> +
> +   title
> + ---------
> +  Yojimbo
> + </programlisting>
> +   </para>
> +
> +   <para>
>      Clear the table <literal>films</literal>:
>   <programlisting>
>   DELETE FROM films;
>   </programlisting>
>     </para>
>    </refsect1>
>
>    <refsect1>
>     <title>Compatibility</title>
>
>     <para>
>      This command conforms to the SQL standard, except that the
> !    <literal>RETURNING</> clause, the <literal>USING</> clause
> !    and the ability to reference other tables in the
> !    <literal>WHERE</> clause are <productname>PostgreSQL</>
>      extensions.
>     </para>
>    </refsect1>
>   </refentry>
>
>   <!-- Keep this comment at the end of the file
> Index: doc/src/sgml/ref/insert.sgml
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/doc/src/sgml/ref/insert.sgml,v
> retrieving revision 1.29
> diff -C6 -r1.29 insert.sgml
> *** doc/src/sgml/ref/insert.sgml    9 Jan 2005 05:57:45 -0000    1.29
> --- doc/src/sgml/ref/insert.sgml    30 Jul 2005 06:37:02 -0000
> ***************
> *** 19,30 ****
> --- 19,31 ----
>    </indexterm>
>
>    <refsynopsisdiv>
>   <synopsis>
>   INSERT INTO <replaceable class="PARAMETER">table</replaceable> [ ( <replaceable
class="PARAMETER">column</replaceable>[, ...] ) ] 
>       { DEFAULT VALUES | VALUES ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) |
<replaceableclass="PARAMETER">query</replaceable> } 
> +     [ RETURNING <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
>   </synopsis>
>    </refsynopsisdiv>
>
>    <refsect1>
>     <title>Description</title>
>
> ***************
> *** 125,155 ****
>         A query (<command>SELECT</command> statement) that supplies the
>         rows to be inserted.  Refer to the <command>SELECT</command>
>         statement for a description of the syntax.
>        </para>
>       </listitem>
>      </varlistentry>
>     </variablelist>
>    </refsect1>
>
>    <refsect1>
>     <title>Outputs</title>
>
>     <para>
> !    On successful completion, an <command>INSERT</> command returns a command
> !    tag of the form
>   <screen>
>   INSERT <replaceable>oid</replaceable> <replaceable class="parameter">count</replaceable>
>   </screen>
>      The <replaceable class="parameter">count</replaceable> is the number
>      of rows inserted.  If <replaceable class="parameter">count</replaceable>
>      is exactly one, and the target table has OIDs, then
>      <replaceable class="parameter">oid</replaceable> is the
>      <acronym>OID</acronym> assigned to the inserted row.  Otherwise
>      <replaceable class="parameter">oid</replaceable> is zero.
>     </para>
>    </refsect1>
>
>    <refsect1>
>     <title>Examples</title>
>
>     <para>
> --- 126,179 ----
>         A query (<command>SELECT</command> statement) that supplies the
>         rows to be inserted.  Refer to the <command>SELECT</command>
>         statement for a description of the syntax.
>        </para>
>       </listitem>
>      </varlistentry>
> +
> +    <varlistentry>
> +     <term>
> +       <literal>RETURNING</literal>
> +       <replaceable class="PARAMETER">expression</replaceable> [, ...]
> +     </term>
> +     <listitem>
> +      <para>
> +        An optional list of expressions to return.
> +      </para>
> +     </listitem>
> +    </varlistentry>
>     </variablelist>
>    </refsect1>
>
>    <refsect1>
>     <title>Outputs</title>
>
>     <para>
> !    On successful completion, an <command>INSERT</> command without a
> !    <literal>RETURNING</literal> clause returns a command tag of the form
>   <screen>
>   INSERT <replaceable>oid</replaceable> <replaceable class="parameter">count</replaceable>
>   </screen>
>      The <replaceable class="parameter">count</replaceable> is the number
>      of rows inserted.  If <replaceable class="parameter">count</replaceable>
>      is exactly one, and the target table has OIDs, then
>      <replaceable class="parameter">oid</replaceable> is the
>      <acronym>OID</acronym> assigned to the inserted row.  Otherwise
>      <replaceable class="parameter">oid</replaceable> is zero.
>     </para>
> +
> +   <indexterm zone="sql-insert">
> +    <primary>RETURNING</primary>
> +    <secondary>INSERT</secondary>
> +   </indexterm>
> +
> +   <para>
> +    If a <literal>RETURNING</literal> clause is present, the expression
> +    specified is evaluated as per the values derived for each inserted
> +    row and the result is returned.
> +   </para>
>    </refsect1>
>
>    <refsect1>
>     <title>Examples</title>
>
>     <para>
> ***************
> *** 199,210 ****
> --- 223,247 ----
>   <programlisting>
>   INSERT INTO films SELECT * FROM tmp_films WHERE date_prod < '2004-05-07';
>   </programlisting>
>     </para>
>
>     <para>
> +     This example inserts a row into the table <literal>films</literal>,
> +     and returns the value of the <literal>film_id</literal> column using
> +     the <literal>RETURNING</literal> clause.
> + <programlisting>
> + INSERT INTO films (title) VALUES ('Yojimbo') RETURNING film_id;
> +
> +  film_id
> + ---------
> +      123
> + </programlisting>
> +   </para>
> +
> +   <para>
>      This example inserts into array columns:
>
>   <programlisting>
>   -- Create an empty 3x3 gameboard for noughts-and-crosses
>   -- (these commands create the same board)
>   INSERT INTO tictactoe (game, board[1:3][1:3])
> ***************
> *** 216,228 ****
>    </refsect1>
>
>    <refsect1>
>     <title>Compatibility</title>
>
>     <para>
> !    <command>INSERT</command> conforms to the SQL standard.  The case in
>      which a column name list is omitted, but not all the columns are
>      filled from the <literal>VALUES</> clause or <replaceable>query</>,
>      is disallowed by the standard.
>     </para>
>
>     <para>
> --- 253,267 ----
>    </refsect1>
>
>    <refsect1>
>     <title>Compatibility</title>
>
>     <para>
> !    <command>INSERT</command> conforms to the SQL standard except
> !    that the <literal>RETURNING</literal> clause is a
> !    <productname>PostgreSQL</productname> extension. The case in
>      which a column name list is omitted, but not all the columns are
>      filled from the <literal>VALUES</> clause or <replaceable>query</>,
>      is disallowed by the standard.
>     </para>
>
>     <para>
> Index: doc/src/sgml/ref/update.sgml
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/doc/src/sgml/ref/update.sgml,v
> retrieving revision 1.32
> diff -C6 -r1.32 update.sgml
> *** doc/src/sgml/ref/update.sgml    9 Jan 2005 05:57:45 -0000    1.32
> --- doc/src/sgml/ref/update.sgml    30 Jul 2005 06:37:02 -0000
> ***************
> *** 20,31 ****
> --- 20,32 ----
>
>    <refsynopsisdiv>
>   <synopsis>
>   UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> SET <replaceable
class="PARAMETER">column</replaceable>= { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] 
>       [ FROM <replaceable class="PARAMETER">fromlist</replaceable> ]
>       [ WHERE <replaceable class="PARAMETER">condition</replaceable> ]
> +     [ RETURNING <replaceable class="PARAMETER">expression</replaceable> [, ...] ]
>   </synopsis>
>    </refsynopsisdiv>
>
>    <refsect1>
>     <title>Description</title>
>
> ***************
> *** 128,157 ****
>         An expression that returns a value of type <type>boolean</type>.
>         Only rows for which this expression returns <literal>true</>
>         will be updated.
>        </para>
>       </listitem>
>      </varlistentry>
>     </variablelist>
>    </refsect1>
>
>    <refsect1>
>     <title>Outputs</title>
>
>     <para>
> !    On successful completion, an <command>UPDATE</> command returns a command
> !    tag of the form
>   <screen>
>   UPDATE <replaceable class="parameter">count</replaceable>
>   </screen>
>      The <replaceable class="parameter">count</replaceable> is the number
>      of rows updated.  If <replaceable class="parameter">count</replaceable> is
>      0, no rows matched the <replaceable
>      class="parameter">condition</replaceable> (this is not considered
>      an error).
>     </para>
>    </refsect1>
>
>    <refsect1>
>     <title>Notes</title>
>
>     <para>
> --- 129,181 ----
>         An expression that returns a value of type <type>boolean</type>.
>         Only rows for which this expression returns <literal>true</>
>         will be updated.
>        </para>
>       </listitem>
>      </varlistentry>
> +
> +    <varlistentry>
> +     <term>
> +       <literal>RETURNING</literal>
> +       <replaceable class="PARAMETER">expression</replaceable> [, ...]
> +     </term>
> +     <listitem>
> +      <para>
> +        An optional list of expressions to return.
> +      </para>
> +     </listitem>
> +    </varlistentry>
>     </variablelist>
>    </refsect1>
>
>    <refsect1>
>     <title>Outputs</title>
>
>     <para>
> !    On successful completion, an <command>UPDATE</> command without a
> !    <literal>RETURNING</literal> clause returns a command tag of the form
>   <screen>
>   UPDATE <replaceable class="parameter">count</replaceable>
>   </screen>
>      The <replaceable class="parameter">count</replaceable> is the number
>      of rows updated.  If <replaceable class="parameter">count</replaceable> is
>      0, no rows matched the <replaceable
>      class="parameter">condition</replaceable> (this is not considered
>      an error).
>     </para>
> +
> +   <indexterm zone="sql-update">
> +    <primary>RETURNING</primary>
> +    <secondary>UPDATE</secondary>
> +   </indexterm>
> +
> +   <para>
> +    If a <literal>RETURNING</literal> clause is present, the expression
> +    specified is evaluated as per the new values for each updated row
> +    and the result is returned.
> +   </para>
>    </refsect1>
>
>    <refsect1>
>     <title>Notes</title>
>
>     <para>
> ***************
> *** 210,221 ****
> --- 234,255 ----
>      <literal>WHERE</literal> clause:
>   <programlisting>
>   UPDATE employees SET sales_count = sales_count + 1 WHERE id =
>     (SELECT sales_person FROM accounts WHERE name = 'Acme Corporation');
>   </programlisting>
>
> +    Return the sales count of the salesperson after incrementing it using
> +    the <literal>RETURNING</literal> clause:
> + <programlisting>
> + UPDATE employees SET sales_count = sales_count + 1 RETURNING sales_count
> +
> +  sales_count
> + -------------
> +           11
> + </programlisting>
> +
>      Attempt to insert a new stock item along with the quantity of stock. If
>      the item already exists, instead update the stock count of the existing
>      item. To do this without failing the entire transaction, use savepoints.
>   <programlisting>
>   BEGIN;
>   -- other operations
> ***************
> *** 233,246 ****
>
>    <refsect1>
>     <title>Compatibility</title>
>
>     <para>
>      This command conforms to the <acronym>SQL</acronym> standard, except
> !    that the <literal>FROM</literal> clause is a
> !    <productname>PostgreSQL</productname> extension.
>     </para>
>
>     <para>
>      Some other database systems offer a <literal>FROM</> option in which
>      the target table is supposed to be listed again within <literal>FROM</>.
>      That is not how <productname>PostgreSQL</productname> interprets
> --- 267,280 ----
>
>    <refsect1>
>     <title>Compatibility</title>
>
>     <para>
>      This command conforms to the <acronym>SQL</acronym> standard, except
> !    that the <literal>FROM</literal> clause and the <literal>RETURNING</literal>
> !    clause are <productname>PostgreSQL</productname> extensions.
>     </para>
>
>     <para>
>      Some other database systems offer a <literal>FROM</> option in which
>      the target table is supposed to be listed again within <literal>FROM</>.
>      That is not how <productname>PostgreSQL</productname> interprets
> Index: src/backend/access/common/printtup.c
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/backend/access/common/printtup.c,v
> retrieving revision 1.91
> diff -C6 -r1.91 printtup.c
> *** src/backend/access/common/printtup.c    22 Jun 2005 17:45:45 -0000    1.91
> --- src/backend/access/common/printtup.c    30 Jul 2005 06:37:02 -0000
> ***************
> *** 16,27 ****
> --- 16,28 ----
>   #include "postgres.h"
>
>   #include "access/heapam.h"
>   #include "access/printtup.h"
>   #include "libpq/libpq.h"
>   #include "libpq/pqformat.h"
> + #include "executor/executor.h"
>   #include "tcop/pquery.h"
>   #include "utils/lsyscache.h"
>   #include "utils/portal.h"
>
>
>   static void printtup_startup(DestReceiver *self, int operation,
> ***************
> *** 106,117 ****
> --- 107,120 ----
>
>   static void
>   printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
>   {
>       DR_printtup *myState = (DR_printtup *) self;
>       Portal        portal = myState->portal;
> +     List         *returning = ((Query *) linitial(portal->parseTrees))->returning;
> +     bool        withReturning = (returning != NIL);
>
>       if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
>       {
>           /*
>            * Send portal name to frontend (obsolete cruft, gone in proto
>            * 3.0)
> ***************
> *** 131,143 ****
>        * descriptions, then we send back the tuple descriptor of the tuples.
>        */
>       if (operation == CMD_SELECT && myState->sendDescrip)
>           SendRowDescriptionMessage(typeinfo,
>                                     FetchPortalTargetList(portal),
>                                     portal->formats);
> !
>       /* ----------------
>        * We could set up the derived attr info at this time, but we postpone it
>        * until the first call of printtup, for 2 reasons:
>        * 1. We don't waste time (compared to the old way) if there are no
>        *      tuples at all to output.
>        * 2. Checking in printtup allows us to handle the case that the tuples
> --- 134,150 ----
>        * descriptions, then we send back the tuple descriptor of the tuples.
>        */
>       if (operation == CMD_SELECT && myState->sendDescrip)
>           SendRowDescriptionMessage(typeinfo,
>                                     FetchPortalTargetList(portal),
>                                     portal->formats);
> !     else if (withReturning)
> !         SendRowDescriptionMessage(ExecTypeFromTL(returning, false),
> !                                   returning,
> !                                   portal->formats);
> !
>       /* ----------------
>        * We could set up the derived attr info at this time, but we postpone it
>        * until the first call of printtup, for 2 reasons:
>        * 1. We don't waste time (compared to the old way) if there are no
>        *      tuples at all to output.
>        * 2. Checking in printtup allows us to handle the case that the tuples
> ***************
> *** 300,312 ****
>
>       pq_sendint(&buf, natts, 2);
>
>       /*
>        * send the attributes of this tuple
>        */
> !     for (i = 0; i < natts; ++i)
>       {
>           PrinttupAttrInfo *thisState = myState->myinfo + i;
>           Datum        origattr = slot->tts_values[i],
>                       attr;
>
>           if (slot->tts_isnull[i])
> --- 307,319 ----
>
>       pq_sendint(&buf, natts, 2);
>
>       /*
>        * send the attributes of this tuple
>        */
> !     for (i = 0; i < natts; i++)
>       {
>           PrinttupAttrInfo *thisState = myState->myinfo + i;
>           Datum        origattr = slot->tts_values[i],
>                       attr;
>
>           if (slot->tts_isnull[i])
> Index: src/backend/executor/execMain.c
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/backend/executor/execMain.c,v
> retrieving revision 1.251
> diff -C6 -r1.251 execMain.c
> *** src/backend/executor/execMain.c    28 Jun 2005 05:08:55 -0000    1.251
> --- src/backend/executor/execMain.c    30 Jul 2005 06:59:06 -0000
> ***************
> *** 80,97 ****
>               long numberTuples,
>               ScanDirection direction,
>               DestReceiver *dest);
>   static void ExecSelect(TupleTableSlot *slot,
>              DestReceiver *dest,
>              EState *estate);
> ! static void ExecInsert(TupleTableSlot *slot, ItemPointer tupleid,
> !            EState *estate);
> ! static void ExecDelete(TupleTableSlot *slot, ItemPointer tupleid,
> !            EState *estate);
> ! static void ExecUpdate(TupleTableSlot *slot, ItemPointer tupleid,
> !            EState *estate);
>   static TupleTableSlot *EvalPlanQualNext(EState *estate);
>   static void EndEvalPlanQual(EState *estate);
>   static void ExecCheckRTEPerms(RangeTblEntry *rte);
>   static void ExecCheckXactReadOnly(Query *parsetree);
>   static void EvalPlanQualStart(evalPlanQual *epq, EState *estate,
>                     evalPlanQual *priorepq);
> --- 80,97 ----
>               long numberTuples,
>               ScanDirection direction,
>               DestReceiver *dest);
>   static void ExecSelect(TupleTableSlot *slot,
>              DestReceiver *dest,
>              EState *estate);
> ! static void ExecInsert(TupleTableSlot *slot, DestReceiver *dest,
> !            ItemPointer tupleid, EState *estate);
> ! static void ExecDelete(TupleTableSlot *slot, DestReceiver *dest,
> !            ItemPointer tupleid, EState *estate);
> ! static void ExecUpdate(TupleTableSlot *slot, DestReceiver *dest,
> !            ItemPointer tupleid, EState *estate);
>   static TupleTableSlot *EvalPlanQualNext(EState *estate);
>   static void EndEvalPlanQual(EState *estate);
>   static void ExecCheckRTEPerms(RangeTblEntry *rte);
>   static void ExecCheckXactReadOnly(Query *parsetree);
>   static void EvalPlanQualStart(evalPlanQual *epq, EState *estate,
>                     evalPlanQual *priorepq);
> ***************
> *** 155,166 ****
> --- 155,169 ----
>       /*
>        * Copy other important information into the EState
>        */
>       estate->es_snapshot = queryDesc->snapshot;
>       estate->es_crosscheck_snapshot = queryDesc->crosscheck_snapshot;
>       estate->es_instrument = queryDesc->doInstrument;
> +     estate->es_returning =
> +         ExecTransformReturning(queryDesc->parsetree->returning,
> +                                estate);
>
>       /*
>        * Initialize the plan state tree
>        */
>       InitPlan(queryDesc, explainOnly);
>
> ***************
> *** 1252,1274 ****
>                              dest,    /* destination's tuple-receiver obj */
>                              estate);
>                   result = slot;
>                   break;
>
>               case CMD_INSERT:
> !                 ExecInsert(slot, tupleid, estate);
>                   result = NULL;
>                   break;
>
>               case CMD_DELETE:
> !                 ExecDelete(slot, tupleid, estate);
>                   result = NULL;
>                   break;
>
>               case CMD_UPDATE:
> !                 ExecUpdate(slot, tupleid, estate);
>                   result = NULL;
>                   break;
>
>               default:
>                   elog(ERROR, "unrecognized operation code: %d",
>                        (int) operation);
> --- 1255,1277 ----
>                              dest,    /* destination's tuple-receiver obj */
>                              estate);
>                   result = slot;
>                   break;
>
>               case CMD_INSERT:
> !                 ExecInsert(slot, dest, tupleid, estate);
>                   result = NULL;
>                   break;
>
>               case CMD_DELETE:
> !                 ExecDelete(slot, dest, tupleid, estate);
>                   result = NULL;
>                   break;
>
>               case CMD_UPDATE:
> !                 ExecUpdate(slot, dest, tupleid, estate);
>                   result = NULL;
>                   break;
>
>               default:
>                   elog(ERROR, "unrecognized operation code: %d",
>                        (int) operation);
> ***************
> *** 1361,1372 ****
> --- 1364,1376 ----
>    *        the base relation and insert appropriate tuples into the
>    *        index relations.
>    * ----------------------------------------------------------------
>    */
>   static void
>   ExecInsert(TupleTableSlot *slot,
> +            DestReceiver *dest,
>              ItemPointer tupleid,
>              EState *estate)
>   {
>       HeapTuple    tuple;
>       ResultRelInfo *resultRelInfo;
>       Relation    resultRelationDesc;
> ***************
> *** 1419,1430 ****
> --- 1423,1444 ----
>        * insert the tuple
>        */
>       newId = heap_insert(resultRelationDesc, tuple,
>                           estate->es_snapshot->curcid,
>                           true, true);
>
> +     if (estate->es_returning != NULL)
> +     {
> +         TupleTableSlot *retSlot = ExecReturning(slot, estate);
> +         /*
> +          * send the tuple to the destination
> +          */
> +         (*dest->receiveSlot) (retSlot, dest);
> +         ExecClearTuple(retSlot);
> +     }
> +
>       IncrAppended();
>       (estate->es_processed)++;
>       estate->es_lastoid = newId;
>       setLastTid(&(tuple->t_self));
>
>       /*
> ***************
> *** 1448,1459 ****
> --- 1462,1474 ----
>    *        DELETE is like UPDATE, we delete the tuple and its
>    *        index tuples.
>    * ----------------------------------------------------------------
>    */
>   static void
>   ExecDelete(TupleTableSlot *slot,
> +            DestReceiver *dest,
>              ItemPointer tupleid,
>              EState *estate)
>   {
>       ResultRelInfo *resultRelInfo;
>       Relation    resultRelationDesc;
>       ItemPointerData ctid;
> ***************
> *** 1522,1533 ****
> --- 1537,1563 ----
>
>           default:
>               elog(ERROR, "unrecognized heap_delete status: %u", result);
>               return;
>       }
>
> +     if (estate->es_returning != NULL)
> +     {
> +         TupleTableSlot *deletedSlot;
> +         TupleTableSlot *retSlot;
> +
> +         deletedSlot = ExecGetDeletedSlot(tupleid, estate);
> +         retSlot = ExecReturning(deletedSlot, estate);
> +         /*
> +          * send the tuple to the destination
> +          */
> +         (*dest->receiveSlot) (retSlot, dest);
> +         ExecClearTuple(retSlot);
> +         ExecClearTuple(deletedSlot);
> +     }
> +
>       IncrDeleted();
>       (estate->es_processed)++;
>
>       /*
>        * Note: Normally one would think that we have to delete index tuples
>        * associated with the heap tuple now..
> ***************
> *** 1551,1562 ****
> --- 1581,1593 ----
>    *        is, we don't want to get stuck in an infinite loop
>    *        which corrupts your database..
>    * ----------------------------------------------------------------
>    */
>   static void
>   ExecUpdate(TupleTableSlot *slot,
> +            DestReceiver *dest,
>              ItemPointer tupleid,
>              EState *estate)
>   {
>       HeapTuple    tuple;
>       ResultRelInfo *resultRelInfo;
>       Relation    resultRelationDesc;
> ***************
> *** 1666,1677 ****
> --- 1697,1718 ----
>
>           default:
>               elog(ERROR, "unrecognized heap_update status: %u", result);
>               return;
>       }
>
> +     if (estate->es_returning != NULL)
> +     {
> +         TupleTableSlot *retSlot = ExecReturning(slot, estate);
> +         /*
> +          * send the tuple to the destination
> +          */
> +         (*dest->receiveSlot) (retSlot, dest);
> +         ExecClearTuple(retSlot);
> +     }
> +
>       IncrReplaced();
>       (estate->es_processed)++;
>
>       /*
>        * Note: instead of having to update the old index tuples associated
>        * with the heap tuple, all we do is form and insert new index tuples.
> Index: src/backend/executor/execUtils.c
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/backend/executor/execUtils.c,v
> retrieving revision 1.124
> diff -C6 -r1.124 execUtils.c
> *** src/backend/executor/execUtils.c    20 Jun 2005 18:37:01 -0000    1.124
> --- src/backend/executor/execUtils.c    30 Jul 2005 06:41:21 -0000
> ***************
> *** 1062,1067 ****
> --- 1062,1152 ----
>           (*ecxt_callback->function) (ecxt_callback->arg);
>           pfree(ecxt_callback);
>       }
>
>       MemoryContextSwitchTo(oldcontext);
>   }
> +
> + TupleTableSlot *
> + ExecReturning(TupleTableSlot *slot,
> +               EState *estate)
> + {
> +     TupleTableSlot    *retSlot,
> +                     *scanTupleSave;
> +     ExprContext        *returningExprContext;
> +     ProjectionInfo    *retProject;
> +
> +     returningExprContext = (ExprContext *) linitial(estate->es_exprcontexts);
> +
> +     scanTupleSave = returningExprContext->ecxt_scantuple;
> +     returningExprContext->ecxt_scantuple = slot;
> +
> +     retProject = ExecBuildProjectionInfo(estate->es_returning->retExprs,
> +                                          returningExprContext,
> +                                          estate->es_returning->retSlot);
> +
> +     retSlot = ExecProject(retProject, NULL);
> +     returningExprContext->ecxt_scantuple = scanTupleSave;
> +     return retSlot;
> + }
> +
> + ReturningState *
> + ExecTransformReturning(List *returning,
> +                        EState *estate)
> + {
> +     ReturningState    *retState;
> +     List            *retExprs = NIL;
> +     ListCell        *retElem;
> +     int                 i = 1;
> +
> +     if (returning == NIL)
> +         return NULL;
> +
> +     retState = palloc(1 * sizeof(ReturningState));
> +
> +     foreach (retElem, returning)
> +     {
> +         TargetEntry         *tle;
> +         GenericExprState *gstate;
> +
> +         tle = (TargetEntry *) lfirst(retElem);
> +         tle->resno = i++;
> +         gstate = makeNode(GenericExprState);
> +         gstate->xprstate.expr = (Expr *) tle;
> +         gstate->xprstate.evalfunc = NULL;
> +         gstate->arg = ExecPrepareExpr(tle->expr, estate);
> +
> +         retExprs = lappend(retExprs, gstate);
> +     }
> +
> +     retState->retTupleDesc = ExecTypeFromTL(returning, false);
> +     retState->retExprs = retExprs;
> +     retState->retSlot = MakeSingleTupleTableSlot(retState->retTupleDesc);
> +
> +     return retState;
> + }
> +
> + TupleTableSlot *
> + ExecGetDeletedSlot(ItemPointer tupleid,
> +                    EState *estate)
> + {
> +     TupleTableSlot *retSlot = NULL;
> +     HeapTupleData    retTuple;
> +     Buffer            buffer;
> +
> +     retTuple.t_self = *tupleid;
> +
> +     if (heap_fetch(estate->es_result_relation_info->ri_RelationDesc,
> +                    SnapshotNow,
> +                    &retTuple,
> +                    &buffer,
> +                    false,
> +                    NULL))
> +     {
> +         retSlot = MakeSingleTupleTableSlot(estate->es_result_relation_info->ri_RelationDesc->rd_att);
> +         ExecStoreTuple(&retTuple, retSlot, InvalidBuffer, false);
> +         slot_getallattrs(retSlot);
> +         ReleaseBuffer(buffer);
> +     }
> +
> +     return retSlot;
> + }
> Index: src/backend/nodes/copyfuncs.c
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/backend/nodes/copyfuncs.c,v
> retrieving revision 1.312
> diff -C6 -r1.312 copyfuncs.c
> *** src/backend/nodes/copyfuncs.c    26 Jul 2005 16:38:27 -0000    1.312
> --- src/backend/nodes/copyfuncs.c    30 Jul 2005 06:37:02 -0000
> ***************
> *** 1605,1616 ****
> --- 1605,1617 ----
>       COPY_SCALAR_FIELD(hasSubLinks);
>       COPY_NODE_FIELD(rtable);
>       COPY_NODE_FIELD(jointree);
>       COPY_NODE_FIELD(rowMarks);
>       COPY_SCALAR_FIELD(forUpdate);
>       COPY_NODE_FIELD(targetList);
> +     COPY_NODE_FIELD(returning);
>       COPY_NODE_FIELD(groupClause);
>       COPY_NODE_FIELD(havingQual);
>       COPY_NODE_FIELD(distinctClause);
>       COPY_NODE_FIELD(sortClause);
>       COPY_NODE_FIELD(limitOffset);
>       COPY_NODE_FIELD(limitCount);
> ***************
> *** 1626,1649 ****
> --- 1627,1652 ----
>       InsertStmt *newnode = makeNode(InsertStmt);
>
>       COPY_NODE_FIELD(relation);
>       COPY_NODE_FIELD(cols);
>       COPY_NODE_FIELD(targetList);
>       COPY_NODE_FIELD(selectStmt);
> +     COPY_NODE_FIELD(returning);
>
>       return newnode;
>   }
>
>   static DeleteStmt *
>   _copyDeleteStmt(DeleteStmt *from)
>   {
>       DeleteStmt *newnode = makeNode(DeleteStmt);
>
>       COPY_NODE_FIELD(relation);
>       COPY_NODE_FIELD(whereClause);
>       COPY_NODE_FIELD(usingClause);
> +     COPY_NODE_FIELD(returning);
>
>       return newnode;
>   }
>
>   static UpdateStmt *
>   _copyUpdateStmt(UpdateStmt *from)
> ***************
> *** 1651,1662 ****
> --- 1654,1666 ----
>       UpdateStmt *newnode = makeNode(UpdateStmt);
>
>       COPY_NODE_FIELD(relation);
>       COPY_NODE_FIELD(targetList);
>       COPY_NODE_FIELD(whereClause);
>       COPY_NODE_FIELD(fromClause);
> +     COPY_NODE_FIELD(returning);
>
>       return newnode;
>   }
>
>   static SelectStmt *
>   _copySelectStmt(SelectStmt *from)
> Index: src/backend/nodes/equalfuncs.c
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/backend/nodes/equalfuncs.c,v
> retrieving revision 1.249
> diff -C6 -r1.249 equalfuncs.c
> *** src/backend/nodes/equalfuncs.c    26 Jul 2005 16:38:27 -0000    1.249
> --- src/backend/nodes/equalfuncs.c    30 Jul 2005 06:37:02 -0000
> ***************
> *** 642,653 ****
> --- 642,654 ----
>       COMPARE_SCALAR_FIELD(hasSubLinks);
>       COMPARE_NODE_FIELD(rtable);
>       COMPARE_NODE_FIELD(jointree);
>       COMPARE_NODE_FIELD(rowMarks);
>       COMPARE_SCALAR_FIELD(forUpdate);
>       COMPARE_NODE_FIELD(targetList);
> +     COMPARE_NODE_FIELD(returning);
>       COMPARE_NODE_FIELD(groupClause);
>       COMPARE_NODE_FIELD(havingQual);
>       COMPARE_NODE_FIELD(distinctClause);
>       COMPARE_NODE_FIELD(sortClause);
>       COMPARE_NODE_FIELD(limitOffset);
>       COMPARE_NODE_FIELD(limitCount);
> ***************
> *** 661,693 ****
> --- 662,697 ----
>   _equalInsertStmt(InsertStmt *a, InsertStmt *b)
>   {
>       COMPARE_NODE_FIELD(relation);
>       COMPARE_NODE_FIELD(cols);
>       COMPARE_NODE_FIELD(targetList);
>       COMPARE_NODE_FIELD(selectStmt);
> +     COMPARE_NODE_FIELD(returning);
>
>       return true;
>   }
>
>   static bool
>   _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b)
>   {
>       COMPARE_NODE_FIELD(relation);
>       COMPARE_NODE_FIELD(whereClause);
>       COMPARE_NODE_FIELD(usingClause);
> +     COMPARE_NODE_FIELD(returning);
>
>       return true;
>   }
>
>   static bool
>   _equalUpdateStmt(UpdateStmt *a, UpdateStmt *b)
>   {
>       COMPARE_NODE_FIELD(relation);
>       COMPARE_NODE_FIELD(targetList);
>       COMPARE_NODE_FIELD(whereClause);
>       COMPARE_NODE_FIELD(fromClause);
> +     COMPARE_NODE_FIELD(returning);
>
>       return true;
>   }
>
>   static bool
>   _equalSelectStmt(SelectStmt *a, SelectStmt *b)
> Index: src/backend/nodes/outfuncs.c
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/backend/nodes/outfuncs.c,v
> retrieving revision 1.258
> diff -C6 -r1.258 outfuncs.c
> *** src/backend/nodes/outfuncs.c    2 Jul 2005 23:00:39 -0000    1.258
> --- src/backend/nodes/outfuncs.c    30 Jul 2005 06:37:02 -0000
> ***************
> *** 1460,1471 ****
> --- 1460,1472 ----
>       WRITE_BOOL_FIELD(hasSubLinks);
>       WRITE_NODE_FIELD(rtable);
>       WRITE_NODE_FIELD(jointree);
>       WRITE_NODE_FIELD(rowMarks);
>       WRITE_BOOL_FIELD(forUpdate);
>       WRITE_NODE_FIELD(targetList);
> +     WRITE_NODE_FIELD(returning);
>       WRITE_NODE_FIELD(groupClause);
>       WRITE_NODE_FIELD(havingQual);
>       WRITE_NODE_FIELD(distinctClause);
>       WRITE_NODE_FIELD(sortClause);
>       WRITE_NODE_FIELD(limitOffset);
>       WRITE_NODE_FIELD(limitCount);
> Index: src/backend/nodes/readfuncs.c
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/backend/nodes/readfuncs.c,v
> retrieving revision 1.180
> diff -C6 -r1.180 readfuncs.c
> *** src/backend/nodes/readfuncs.c    28 Jun 2005 05:08:57 -0000    1.180
> --- src/backend/nodes/readfuncs.c    30 Jul 2005 06:37:02 -0000
> ***************
> *** 144,155 ****
> --- 144,156 ----
>       READ_BOOL_FIELD(hasSubLinks);
>       READ_NODE_FIELD(rtable);
>       READ_NODE_FIELD(jointree);
>       READ_NODE_FIELD(rowMarks);
>       READ_BOOL_FIELD(forUpdate);
>       READ_NODE_FIELD(targetList);
> +     READ_NODE_FIELD(returning);
>       READ_NODE_FIELD(groupClause);
>       READ_NODE_FIELD(havingQual);
>       READ_NODE_FIELD(distinctClause);
>       READ_NODE_FIELD(sortClause);
>       READ_NODE_FIELD(limitOffset);
>       READ_NODE_FIELD(limitCount);
> Index: src/backend/parser/analyze.c
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/backend/parser/analyze.c,v
> retrieving revision 1.323
> diff -C6 -r1.323 analyze.c
> *** src/backend/parser/analyze.c    28 Jul 2005 22:27:00 -0000    1.323
> --- src/backend/parser/analyze.c    30 Jul 2005 06:37:02 -0000
> ***************
> *** 100,111 ****
> --- 100,113 ----
>                 List **extras_before, List **extras_after);
>   static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt,
>                     List **extras_before, List **extras_after);
>   static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
>   static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
>                       List **extras_before, List **extras_after);
> + static List *transformReturningList(ParseState *pstate, RangeVar *relation, List *returning);
> +
>   static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt);
>   static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt,
>                     List **extras_before, List **extras_after);
>   static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
>   static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
>   static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
> ***************
> *** 486,497 ****
> --- 488,506 ----
>        */
>       transformFromClause(pstate, stmt->usingClause);
>
>       /* fix where clause */
>       qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
>
> +     /*
> +      * Transform any RETURNING values to form a targetlist.
> +      */
> +
> +     qry->returning = transformReturningList(pstate, stmt->relation,
> +                                             stmt->returning);
> +
>       /* done building the range table and jointree */
>       qry->rtable = pstate->p_rtable;
>       qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
>
>       qry->hasSubLinks = pstate->p_hasSubLinks;
>       qry->hasAggs = pstate->p_hasAggs;
> ***************
> *** 663,674 ****
> --- 672,690 ----
>            * form a targetlist for the INSERT.
>            */
>           qry->targetList = transformTargetList(pstate, stmt->targetList);
>       }
>
>       /*
> +      * Transform any RETURNING values to form a targetlist.
> +      */
> +
> +     qry->returning = transformReturningList(pstate, stmt->relation,
> +                                             stmt->returning);
> +
> +     /*
>        * Now we are done with SELECT-like processing, and can get on with
>        * transforming the target list to match the INSERT target columns.
>        */
>
>       /* Prepare to assign non-conflicting resnos to resjunk attributes */
>       if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts)
> ***************
> *** 722,733 ****
> --- 738,776 ----
>       if (pstate->p_hasAggs)
>           parseCheckAggregates(pstate, qry);
>
>       return qry;
>   }
>
> + static List *
> + transformReturningList(ParseState *pstate, RangeVar *relation, List *returning)
> + {
> +     List *ret = NIL;
> +     RangeTblEntry *retrte;
> +
> +     if (returning != NIL)
> +     {
> +         /*
> +          * Add the RTE to the pstate if we don't have any already.
> +          * This will usually happen for INSERT.
> +          */
> +         if (pstate->p_varnamespace == NIL)
> +         {
> +             retrte = addRangeTableEntry(pstate, relation,
> +                                         makeAlias("*RETURNING*", NIL),
> +                                         false, false);
> +             addRTEtoQuery(pstate, retrte, false, true, true);
> +         }
> +
> +         ret = transformTargetList(pstate, returning);
> +         if (ret != NIL)
> +             markTargetListOrigins(pstate, ret);
> +     }
> +     return ret;
> + }
> +
>   /*
>    * transformCreateStmt -
>    *      transforms the "create table" statement
>    *      SQL92 allows constraints to be scattered all over, so thumb through
>    *       the columns and collect all constraints into one place.
>    *      If there are any implied indices (e.g. UNIQUE or PRIMARY KEY)
> ***************
> *** 2337,2348 ****
> --- 2380,2398 ----
>        * do this with REPLACE in POSTQUEL so we keep the feature.
>        */
>       transformFromClause(pstate, stmt->fromClause);
>
>       qry->targetList = transformTargetList(pstate, stmt->targetList);
>
> +     /*
> +      * Transform any RETURNING values to form a targetlist.
> +      */
> +
> +     qry->returning = transformReturningList(pstate, stmt->relation,
> +                                             stmt->returning);
> +
>       qual = transformWhereClause(pstate, stmt->whereClause, "WHERE");
>
>       qry->rtable = pstate->p_rtable;
>       qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
>
>       qry->hasSubLinks = pstate->p_hasSubLinks;
> Index: src/backend/parser/gram.y
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/backend/parser/gram.y,v
> retrieving revision 2.504
> diff -C6 -r2.504 gram.y
> *** src/backend/parser/gram.y    26 Jul 2005 22:37:50 -0000    2.504
> --- src/backend/parser/gram.y    30 Jul 2005 06:37:02 -0000
> ***************
> *** 239,250 ****
> --- 239,252 ----
>   %type <withoids> OptWithOids WithOidsAs
>
>   %type <list>    for_locking_clause opt_for_locking_clause
>                   update_list
>   %type <boolean>    opt_all
>
> + %type <list>    opt_returning_list
> +
>   %type <node>    join_outer join_qual
>   %type <jtype>    join_type
>
>   %type <list>    extract_list overlay_list position_list
>   %type <list>    substr_list trim_list
>   %type <ival>    opt_interval
> ***************
> *** 386,398 ****
>       PRECISION PRESERVE PREPARE PREPARED PRIMARY
>       PRIOR PRIVILEGES PROCEDURAL PROCEDURE
>
>       QUOTE
>
>       READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME
> !     REPEATABLE REPLACE RESET RESTART RESTRICT RETURNS REVOKE RIGHT
>       ROLE ROLLBACK ROW ROWS RULE
>
>       SAVEPOINT SCHEMA SCROLL SECOND_P SECURITY SELECT SEQUENCE
>       SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE
>       SHOW SIMILAR SIMPLE SMALLINT SOME STABLE START STATEMENT
>       STATISTICS STDIN STDOUT STORAGE STRICT_P SUBSTRING SUPERUSER_P SYMMETRIC
> --- 388,400 ----
>       PRECISION PRESERVE PREPARE PREPARED PRIMARY
>       PRIOR PRIVILEGES PROCEDURAL PROCEDURE
>
>       QUOTE
>
>       READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME
> !     REPEATABLE REPLACE RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT
>       ROLE ROLLBACK ROW ROWS RULE
>
>       SAVEPOINT SCHEMA SCROLL SECOND_P SECURITY SELECT SEQUENCE
>       SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE
>       SHOW SIMILAR SIMPLE SMALLINT SOME STABLE START STATEMENT
>       STATISTICS STDIN STDOUT STORAGE STRICT_P SUBSTRING SUPERUSER_P SYMMETRIC
> ***************
> *** 4879,4893 ****
>    *        QUERY:
>    *                INSERT STATEMENTS
>    *
>    *****************************************************************************/
>
>   InsertStmt:
> !             INSERT INTO qualified_name insert_rest
>                   {
>                       $4->relation = $3;
>                       $$ = (Node *) $4;
>                   }
>           ;
>
>   insert_rest:
>               VALUES '(' insert_target_list ')'
> --- 4881,4896 ----
>    *        QUERY:
>    *                INSERT STATEMENTS
>    *
>    *****************************************************************************/
>
>   InsertStmt:
> !             INSERT INTO qualified_name insert_rest opt_returning_list
>                   {
>                       $4->relation = $3;
> +                     $4->returning = $5;
>                       $$ = (Node *) $4;
>                   }
>           ;
>
>   insert_rest:
>               VALUES '(' insert_target_list ')'
> ***************
> *** 4941,4966 ****
>                       $$->name = $1;
>                       $$->indirection = $2;
>                       $$->val = NULL;
>                   }
>           ;
>
>
>   /*****************************************************************************
>    *
>    *        QUERY:
>    *                DELETE STATEMENTS
>    *
>    *****************************************************************************/
>
> ! DeleteStmt: DELETE_P FROM relation_expr using_clause where_clause
>                   {
>                       DeleteStmt *n = makeNode(DeleteStmt);
>                       n->relation = $3;
>                       n->usingClause = $4;
>                       n->whereClause = $5;
>                       $$ = (Node *)n;
>                   }
>           ;
>
>   using_clause:
>                   USING from_list                        { $$ = $2; }
> --- 4944,4972 ----
>                       $$->name = $1;
>                       $$->indirection = $2;
>                       $$->val = NULL;
>                   }
>           ;
>
> + opt_returning_list:    RETURNING target_list { $$ = $2; }
> +                 | /*EMPTY*/    { $$ = NIL; }
>
>   /*****************************************************************************
>    *
>    *        QUERY:
>    *                DELETE STATEMENTS
>    *
>    *****************************************************************************/
>
> ! DeleteStmt: DELETE_P FROM relation_expr using_clause where_clause opt_returning_list
>                   {
>                       DeleteStmt *n = makeNode(DeleteStmt);
>                       n->relation = $3;
>                       n->usingClause = $4;
>                       n->whereClause = $5;
> +                     n->returning = $6;
>                       $$ = (Node *)n;
>                   }
>           ;
>
>   using_clause:
>                   USING from_list                        { $$ = $2; }
> ***************
> *** 5005,5022 ****
> --- 5011,5030 ----
>    *****************************************************************************/
>
>   UpdateStmt: UPDATE relation_expr
>               SET update_target_list
>               from_clause
>               where_clause
> +             opt_returning_list
>                   {
>                       UpdateStmt *n = makeNode(UpdateStmt);
>                       n->relation = $2;
>                       n->targetList = $4;
>                       n->fromClause = $5;
>                       n->whereClause = $6;
> +                     n->returning = $7;
>                       $$ = (Node *)n;
>                   }
>           ;
>
>
>   /*****************************************************************************
> ***************
> *** 7673,7685 ****
>                       $$->name = NULL;
>                       $$->indirection = NIL;
>                       $$->val = (Node *) makeNode(SetToDefault);
>                   }
>           ;
>
> -
>   /*****************************************************************************
>    *
>    *    Names and constants
>    *
>    *****************************************************************************/
>
> --- 7681,7692 ----
> ***************
> *** 8278,8289 ****
> --- 8285,8297 ----
>               | ONLY
>               | OR
>               | ORDER
>               | PLACING
>               | PRIMARY
>               | REFERENCES
> +             | RETURNING
>               | SELECT
>               | SESSION_USER
>               | SOME
>               | SYMMETRIC
>               | TABLE
>               | THEN
> Index: src/backend/parser/keywords.c
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/backend/parser/keywords.c,v
> retrieving revision 1.163
> diff -C6 -r1.163 keywords.c
> *** src/backend/parser/keywords.c    26 Jul 2005 16:38:27 -0000    1.163
> --- src/backend/parser/keywords.c    30 Jul 2005 06:37:02 -0000
> ***************
> *** 273,284 ****
> --- 273,285 ----
>       {"rename", RENAME},
>       {"repeatable", REPEATABLE},
>       {"replace", REPLACE},
>       {"reset", RESET},
>       {"restart", RESTART},
>       {"restrict", RESTRICT},
> +     {"returning", RETURNING},
>       {"returns", RETURNS},
>       {"revoke", REVOKE},
>       {"right", RIGHT},
>       {"role", ROLE},
>       {"rollback", ROLLBACK},
>       {"row", ROW},
> Index: src/include/executor/executor.h
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/include/executor/executor.h,v
> retrieving revision 1.118
> diff -C6 -r1.118 executor.h
> *** src/include/executor/executor.h    16 Apr 2005 20:07:35 -0000    1.118
> --- src/include/executor/executor.h    30 Jul 2005 06:37:02 -0000
> ***************
> *** 240,246 ****
> --- 240,253 ----
>                               ExprContextCallbackFunction function,
>                               Datum arg);
>   extern void UnregisterExprContextCallback(ExprContext *econtext,
>                                 ExprContextCallbackFunction function,
>                                 Datum arg);
>
> + extern TupleTableSlot *ExecReturning(TupleTableSlot *slot,
> +                                      EState *estate);
> + extern ReturningState *ExecTransformReturning(List *returning,
> +                                               EState *estate);
> + extern TupleTableSlot *ExecGetDeletedSlot(ItemPointer tupleid,
> +                                           EState *estate);
> +
>   #endif   /* EXECUTOR_H  */
> Index: src/include/nodes/execnodes.h
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/include/nodes/execnodes.h,v
> retrieving revision 1.136
> diff -C6 -r1.136 execnodes.h
> *** src/include/nodes/execnodes.h    26 Jun 2005 22:05:41 -0000    1.136
> --- src/include/nodes/execnodes.h    30 Jul 2005 06:47:11 -0000
> ***************
> *** 279,290 ****
> --- 279,297 ----
>       FmgrInfo   *ri_TrigFunctions;
>       struct Instrumentation *ri_TrigInstrument;
>       List      **ri_ConstraintExprs;
>       JunkFilter *ri_junkFilter;
>   } ResultRelInfo;
>
> + typedef struct ReturningState
> + {
> +     TupleDesc         retTupleDesc;
> +     List            *retExprs;
> +     TupleTableSlot    *retSlot;
> + } ReturningState;
> +
>   /* ----------------
>    *      EState information
>    *
>    * Master working state for an Executor invocation
>    * ----------------
>    */
> ***************
> *** 322,333 ****
> --- 329,341 ----
>       List       *es_rowMark;        /* not good place, but there is no other */
>       bool        es_forUpdate;    /* was it FOR UPDATE or FOR SHARE */
>
>       bool        es_instrument;    /* true requests runtime instrumentation */
>       bool        es_select_into; /* true if doing SELECT INTO */
>       bool        es_into_oids;    /* true to generate OIDs in SELECT INTO */
> +     ReturningState *es_returning;    /* list of expressions to return */
>
>       List       *es_exprcontexts;    /* List of ExprContexts within EState */
>
>       /*
>        * this ExprContext is for per-output-tuple operations, such as
>        * constraint checks and index-value computations.    It will be reset
> Index: src/include/nodes/parsenodes.h
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/include/nodes/parsenodes.h,v
> retrieving revision 1.286
> diff -C6 -r1.286 parsenodes.h
> *** src/include/nodes/parsenodes.h    26 Jul 2005 16:38:28 -0000    1.286
> --- src/include/nodes/parsenodes.h    30 Jul 2005 06:37:02 -0000
> ***************
> *** 95,106 ****
> --- 95,108 ----
>
>       bool        forUpdate;        /* true if rowMarks are FOR UPDATE,
>                                    * false if they are FOR SHARE */
>
>       List       *targetList;        /* target list (of TargetEntry) */
>
> +     List       *returning;        /* list of columns to return */
> +
>       List       *groupClause;    /* a list of GroupClause's */
>
>       Node       *havingQual;        /* qualifications applied to groups */
>
>       List       *distinctClause; /* a list of SortClause's */
>
> ***************
> *** 597,620 ****
> --- 599,624 ----
>        * An INSERT statement has *either* VALUES or SELECT, never both. If
>        * VALUES, a targetList is supplied (empty for DEFAULT VALUES). If
>        * SELECT, a complete SelectStmt (or set-operation tree) is supplied.
>        */
>       List       *targetList;        /* the target list (of ResTarget) */
>       Node       *selectStmt;        /* the source SELECT */
> +     List       *returning;        /* List of columns to return */
>   } InsertStmt;
>
>   /* ----------------------
>    *        Delete Statement
>    * ----------------------
>    */
>   typedef struct DeleteStmt
>   {
>       NodeTag        type;
>       RangeVar   *relation;        /* relation to delete from */
>       Node       *whereClause;    /* qualifications */
>       List       *usingClause;    /* optional using clause for more tables */
> +     List       *returning;        /* List of columns to return */
>   } DeleteStmt;
>
>   /* ----------------------
>    *        Update Statement
>    * ----------------------
>    */
> ***************
> *** 622,633 ****
> --- 626,638 ----
>   {
>       NodeTag        type;
>       RangeVar   *relation;        /* relation to update */
>       List       *targetList;        /* the target list (of ResTarget) */
>       Node       *whereClause;    /* qualifications */
>       List       *fromClause;        /* optional from clause for more tables */
> +     List       *returning;        /* List of columns to return */
>   } UpdateStmt;
>
>   /* ----------------------
>    *        Select Statement
>    *
>    * A "simple" SELECT is represented in the output of gram.y by a single
> Index: src/test/regress/expected/insert.out
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/test/regress/expected/insert.out,v
> retrieving revision 1.7
> diff -C6 -r1.7 insert.out
> *** src/test/regress/expected/insert.out    25 Sep 2003 06:58:06 -0000    1.7
> --- src/test/regress/expected/insert.out    30 Jul 2005 06:37:02 -0000
> ***************
> *** 5,24 ****
>   insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT, DEFAULT);
>   ERROR:  null value in column "col2" violates not-null constraint
>   insert into inserttest (col2, col3) values (3, DEFAULT);
>   insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
>   insert into inserttest values (DEFAULT, 5, 'test');
>   insert into inserttest values (DEFAULT, 7);
>   select * from inserttest;
>    col1 | col2 |  col3
>   ------+------+---------
>         |    3 | testing
>         |    5 | testing
>         |    5 | test
>         |    7 | testing
> ! (4 rows)
>
>   --
>   -- insert with similar expression / target_list values (all fail)
>   --
>   insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT);
>   ERROR:  INSERT has more target columns than expressions
> --- 5,31 ----
>   insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT, DEFAULT);
>   ERROR:  null value in column "col2" violates not-null constraint
>   insert into inserttest (col2, col3) values (3, DEFAULT);
>   insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
>   insert into inserttest values (DEFAULT, 5, 'test');
>   insert into inserttest values (DEFAULT, 7);
> + insert into inserttest (col2, col3) values (3, DEFAULT) returning col3, col1, col2, col2 * 5, least(col2, col2 *
5);
> +   col3   | col1 | col2 | ?column? | least
> + ---------+------+------+----------+-------
> +  testing |      |    3 |       15 |     3
> + (1 row)
> +
>   select * from inserttest;
>    col1 | col2 |  col3
>   ------+------+---------
>         |    3 | testing
>         |    5 | testing
>         |    5 | test
>         |    7 | testing
> !       |    3 | testing
> ! (5 rows)
>
>   --
>   -- insert with similar expression / target_list values (all fail)
>   --
>   insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT);
>   ERROR:  INSERT has more target columns than expressions
> ***************
> *** 32,40 ****
>    col1 | col2 |  col3
>   ------+------+---------
>         |    3 | testing
>         |    5 | testing
>         |    5 | test
>         |    7 | testing
> ! (4 rows)
>
>   drop table inserttest;
> --- 39,48 ----
>    col1 | col2 |  col3
>   ------+------+---------
>         |    3 | testing
>         |    5 | testing
>         |    5 | test
>         |    7 | testing
> !       |    3 | testing
> ! (5 rows)
>
>   drop table inserttest;
> Index: src/test/regress/expected/join.out
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/test/regress/expected/join.out,v
> retrieving revision 1.25
> diff -C6 -r1.25 join.out
> *** src/test/regress/expected/join.out    22 Jul 2005 19:12:02 -0000    1.25
> --- src/test/regress/expected/join.out    30 Jul 2005 07:09:34 -0000
> ***************
> *** 2181,2186 ****
> --- 2181,2204 ----
>   DELETE FROM t3 USING t3 t3_other WHERE t3.x = t3_other.x AND t3.y = t3_other.y;
>   SELECT * FROM t3;
>    x | y
>   ---+---
>   (0 rows)
>
> + INSERT INTO t3 VALUES (5, 20);
> + INSERT INTO t3 VALUES (6, 7);
> + INSERT INTO t3 VALUES (7, 8);
> + INSERT INTO t3 VALUES (500, 100);
> + DELETE FROM t3 USING t3 t3_other WHERE t3.x = t3_other.x AND t3.y = t3_other.y RETURNING t3.y, t3.x, t3.y +
t3_other.xAS sum; 
> +   y  |  x  | sum
> + -----+-----+-----
> +    7 |   6 |  13
> +    8 |   7 |  15
> +   20 |   5 |  25
> +  100 | 500 | 600
> + (4 rows)
> +
> + SELECT * FROM t3;
> +  x | y
> + ---+---
> + (0 rows)
> +
> Index: src/test/regress/expected/update.out
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/test/regress/expected/update.out,v
> retrieving revision 1.1
> diff -C6 -r1.1 update.out
> *** src/test/regress/expected/update.out    26 Aug 2003 18:32:23 -0000    1.1
> --- src/test/regress/expected/update.out    30 Jul 2005 06:37:02 -0000
> ***************
> *** 19,25 ****
> --- 19,39 ----
>    a  | b
>   ----+---
>    10 |
>    10 |
>   (2 rows)
>
> + UPDATE update_test SET a = 5, b = 10 RETURNING b, a, a * 2 + b, greatest(a, b);
> +  b  | a | ?column? | greatest
> + ----+---+----------+----------
> +  10 | 5 |       20 |       10
> +  10 | 5 |       20 |       10
> + (2 rows)
> +
> + SELECT * FROM update_test;
> +  a | b
> + ---+----
> +  5 | 10
> +  5 | 10
> + (2 rows)
> +
>   DROP TABLE update_test;
> Index: src/test/regress/sql/insert.sql
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/test/regress/sql/insert.sql,v
> retrieving revision 1.2
> diff -C6 -r1.2 insert.sql
> *** src/test/regress/sql/insert.sql    24 Apr 2002 02:22:54 -0000    1.2
> --- src/test/regress/sql/insert.sql    30 Jul 2005 06:37:02 -0000
> ***************
> *** 4,15 ****
> --- 4,16 ----
>   create table inserttest (col1 int4, col2 int4 NOT NULL, col3 text default 'testing');
>   insert into inserttest (col1, col2, col3) values (DEFAULT, DEFAULT, DEFAULT);
>   insert into inserttest (col2, col3) values (3, DEFAULT);
>   insert into inserttest (col1, col2, col3) values (DEFAULT, 5, DEFAULT);
>   insert into inserttest values (DEFAULT, 5, 'test');
>   insert into inserttest values (DEFAULT, 7);
> + insert into inserttest (col2, col3) values (3, DEFAULT) returning col3, col1, col2, col2 * 5, least(col2, col2 *
5);
>
>   select * from inserttest;
>
>   --
>   -- insert with similar expression / target_list values (all fail)
>   --
> Index: src/test/regress/sql/join.sql
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/test/regress/sql/join.sql,v
> retrieving revision 1.17
> diff -C6 -r1.17 join.sql
> *** src/test/regress/sql/join.sql    7 Apr 2005 15:23:06 -0000    1.17
> --- src/test/regress/sql/join.sql    30 Jul 2005 07:07:32 -0000
> ***************
> *** 370,375 ****
> --- 370,383 ----
>   DELETE FROM t3 USING t1 table1 WHERE t3.x = table1.a;
>   SELECT * FROM t3;
>   DELETE FROM t3 USING t1 JOIN t2 USING (a) WHERE t3.x > t1.a;
>   SELECT * FROM t3;
>   DELETE FROM t3 USING t3 t3_other WHERE t3.x = t3_other.x AND t3.y = t3_other.y;
>   SELECT * FROM t3;
> +
> + INSERT INTO t3 VALUES (5, 20);
> + INSERT INTO t3 VALUES (6, 7);
> + INSERT INTO t3 VALUES (7, 8);
> + INSERT INTO t3 VALUES (500, 100);
> +
> + DELETE FROM t3 USING t3 t3_other WHERE t3.x = t3_other.x AND t3.y = t3_other.y RETURNING t3.y, t3.x, t3.y +
t3_other.xAS sum; 
> + SELECT * FROM t3;
> Index: src/test/regress/sql/update.sql
> ===================================================================
> RCS file: /home/omar/cvs/cvs/pgsql/src/test/regress/sql/update.sql,v
> retrieving revision 1.2
> diff -C6 -r1.2 update.sql
> *** src/test/regress/sql/update.sql    7 Apr 2005 15:23:06 -0000    1.2
> --- src/test/regress/sql/update.sql    30 Jul 2005 06:37:02 -0000
> ***************
> *** 13,19 ****
> --- 13,23 ----
>   SELECT * FROM update_test;
>
>   UPDATE update_test SET a = DEFAULT, b = DEFAULT;
>
>   SELECT * FROM update_test;
>
> + UPDATE update_test SET a = 5, b = 10 RETURNING b, a, a * 2 + b, greatest(a, b);
> +
> + SELECT * FROM update_test;
> +
>   DROP TABLE update_test;

>
> ---------------------------(end of broadcast)---------------------------
> TIP 9: In versions below 8.0, the planner will ignore your desire to
>        choose an index scan if your joining column's datatypes do not
>        match

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

pgsql-patches by date:

Previous
From: Bruce Momjian
Date:
Subject: Re: PATCH to allow concurrent VACUUMs to not lock each
Next
From: Tom Lane
Date:
Subject: Re: Bug in canonicalize_path()