Folks,

Please find attached a patch which implements the SQL standard
UNNEST() WITH ORDINALITY.  It doesn't stop there.  Any function call
in a FROM clause can now take WITH ORDINALITY, which appends a counter
(ordinality) column to the columns the function outputs and produce
results like this:

postgres@postgres:5493=# select * FROM pg_ls_dir('.') WITH ORDINALITY AS 
t(ls,n);
       ls        | n  
-----------------+----
 pg_serial       |  1
 pg_twophase     |  2
 postmaster.opts |  3
 pg_notify       |  4
 postgresql.conf |  5
 pg_tblspc       |  6
 logfile         |  7
 base            |  8
 postmaster.pid  |  9
 pg_ident.conf   | 10
 global          | 11
 pg_clog         | 12
 pg_snapshots    | 13
 pg_multixact    | 14
 PG_VERSION      | 15
 pg_xlog         | 16
 pg_hba.conf     | 17
 pg_stat_tmp     | 18
 pg_subtrans     | 19
(19 rows)

TBD: polish the docs, add regression tests, possibly add psql support.

Thanks to Andrew (RhodiumToad) Gierth for the hard work designing and
implementing this feature.

Tom, is there some better way to do this?

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

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 12748,12753 **** postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
--- 12748,12778 ----
         3
         4
  (4 rows)
+ 
+ -- unnest WITH ORDINALITY
+ postgres@postgres:5493=# select * FROM pg_ls_dir('.') WITH ORDINALITY AS 
t(ls,n);
+        ls        | n  
+ -----------------+----
+  pg_serial       |  1
+  pg_twophase     |  2
+  postmaster.opts |  3
+  pg_notify       |  4
+  postgresql.conf |  5
+  pg_tblspc       |  6
+  logfile         |  7
+  base            |  8
+  postmaster.pid  |  9
+  pg_ident.conf   | 10
+  global          | 11
+  pg_clog         | 12
+  pg_snapshots    | 13
+  pg_multixact    | 14
+  PG_VERSION      | 15
+  pg_xlog         | 16
+  pg_hba.conf     | 17
+  pg_stat_tmp     | 18
+  pg_subtrans     | 19
+ (19 rows)
  </programlisting>
    </para>
  
*** a/doc/src/sgml/ref/select.sgml
--- b/doc/src/sgml/ref/select.sgml
***************
*** 52,58 **** SELECT [ ALL | DISTINCT [ ON ( <replaceable 
class="parameter">expression</replac
      [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [ 
[ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable 
class="parameter">column_alias</replaceable> [, ...] ) ] ]
      [ LATERAL ] ( <replaceable class="parameter">select</replaceable> ) [ AS 
] <replaceable class="parameter">alias</replaceable> [ ( <replaceable 
class="parameter">column_alias</replaceable> [, ...] ) ]
      <replaceable class="parameter">with_query_name</replaceable> [ [ AS ] 
<replaceable class="parameter">alias</replaceable> [ ( <replaceable 
class="parameter">column_alias</replaceable> [, ...] ) ] ]
!     [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( 
[ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ] 
<replaceable class="parameter">alias</replaceable> [ ( <replaceable 
class="parameter">column_alias</replaceable> [, ...] | <replaceable 
class="parameter">column_definition</replaceable> [, ...] ) ]
      [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( 
[ <replaceable class="parameter">argument</replaceable> [, ...] ] ) AS ( 
<replaceable class="parameter">column_definition</replaceable> [, ...] )
      <replaceable class="parameter">from_item</replaceable> [ NATURAL ] 
<replaceable class="parameter">join_type</replaceable> <replaceable 
class="parameter">from_item</replaceable> [ ON <replaceable 
class="parameter">join_condition</replaceable> | USING ( <replaceable 
class="parameter">join_column</replaceable> [, ...] ) ]
  
--- 52,59 ----
      [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [ 
[ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable 
class="parameter">column_alias</replaceable> [, ...] ) ] ]
      [ LATERAL ] ( <replaceable class="parameter">select</replaceable> ) [ AS 
] <replaceable class="parameter">alias</replaceable> [ ( <replaceable 
class="parameter">column_alias</replaceable> [, ...] ) ]
      <replaceable class="parameter">with_query_name</replaceable> [ [ AS ] 
<replaceable class="parameter">alias</replaceable> [ ( <replaceable 
class="parameter">column_alias</replaceable> [, ...] ) ] ]
!     [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( 
[ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ WITH 
ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( 
<replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
!     [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( 
[ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ] 
<replaceable class="parameter">alias</replaceable> ( <replaceable 
class="parameter">column_definition</replaceable> [, ...] )
      [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( 
[ <replaceable class="parameter">argument</replaceable> [, ...] ] ) AS ( 
<replaceable class="parameter">column_definition</replaceable> [, ...] )
      <replaceable class="parameter">from_item</replaceable> [ NATURAL ] 
<replaceable class="parameter">join_type</replaceable> <replaceable 
class="parameter">from_item</replaceable> [ ON <replaceable 
class="parameter">join_condition</replaceable> | USING ( <replaceable 
class="parameter">join_column</replaceable> [, ...] ) ]
  
***************
*** 366,383 **** TABLE [ ONLY ] <replaceable 
class="parameter">table_name</replaceable> [ * ]
          clause.  (This is especially useful for functions that return
          result sets, but any function can be used.)  This acts as
          though its output were created as a temporary table for the
!         duration of this single <command>SELECT</command> command. An
!         alias can also be used. If an alias is written, a column alias
!         list can also be written to provide substitute names for one
!         or more attributes of the function's composite return type. If
!         the function has been defined as returning the <type>record</>
!         data type, then an alias or the key word <literal>AS</> must
!         be present, followed by a column definition list in the form
!         <literal>( <replaceable
          class="parameter">column_name</replaceable> <replaceable
!         class="parameter">data_type</replaceable> <optional>, ... </>
!         )</literal>.  The column definition list must match the actual
!         number and types of columns returned by the function.
         </para>
        </listitem>
       </varlistentry>
--- 367,401 ----
          clause.  (This is especially useful for functions that return
          result sets, but any function can be used.)  This acts as
          though its output were created as a temporary table for the
!         duration of this single <command>SELECT</command> command.
!         When the optional <command>WITH ORDINALITY</command> is
!         appended to the function call, a new column is appended after
!         all the function call's columns with numbering for each row.
!         For example:
! <programlisting>
! SELECT * FROM unnest(ARRAY['a','b','c','d','e','f']) WITH ORDINALITY;
!  unnest | ?column? 
! --------+----------
!  a      |        1
!  b      |        2
!  c      |        3
!  d      |        4
!  e      |        5
!  f      |        6
! (6 rows)
! </programlisting>
!         An alias can also be used. If an alias is written, a column
!         alias list can also be written to provide substitute names for
!         one or more attributes of the function's composite return
!         type. If the function has been defined as returning the
!         <type>record</> data type, then an alias or the key word
!         <literal>AS</> must be present, followed by a column
!         definition list in the form <literal>( <replaceable
          class="parameter">column_name</replaceable> <replaceable
!         class="parameter">data_type</replaceable> <optional>, ...
!         </>)</literal>.  The column definition list must match the
!         actual number and types of columns returned by the function.
! 
         </para>
        </listitem>
       </varlistentry>
*** a/src/backend/access/common/tupdesc.c
--- b/src/backend/access/common/tupdesc.c
***************
*** 158,163 **** CreateTupleDescCopy(TupleDesc tupdesc)
--- 158,192 ----
  }
  
  /*
+  * CreateTupleDescCopyExtend
+  *            This function creates a new TupleDesc by copying from an 
existing
+  *            TupleDesc, but adding space for more columns. The new tupdesc is
+  *      not regarded as the same record type as the old one.
+  *
+  * !!! Constraints and defaults are not copied !!!
+  */
+ TupleDesc
+ CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts)
+ {
+       TupleDesc       desc;
+       int                     i;
+       int         src_natts = tupdesc->natts;
+ 
+       Assert(moreatts >= 0);
+ 
+       desc = CreateTemplateTupleDesc(src_natts + moreatts, tupdesc->tdhasoid);
+ 
+       for (i = 0; i < src_natts; i++)
+       {
+               memcpy(desc->attrs[i], tupdesc->attrs[i], 
ATTRIBUTE_FIXED_PART_SIZE);
+               desc->attrs[i]->attnotnull = false;
+               desc->attrs[i]->atthasdef = false;
+       }
+ 
+       return desc;
+ }
+ 
+ /*
   * CreateTupleDescCopyConstr
   *            This function creates a new TupleDesc by copying from an 
existing
   *            TupleDesc (including its constraints and defaults).
*** a/src/backend/executor/nodeFunctionscan.c
--- b/src/backend/executor/nodeFunctionscan.c
***************
*** 25,31 ****
  #include "executor/nodeFunctionscan.h"
  #include "funcapi.h"
  #include "nodes/nodeFuncs.h"
! 
  
  static TupleTableSlot *FunctionNext(FunctionScanState *node);
  
--- 25,31 ----
  #include "executor/nodeFunctionscan.h"
  #include "funcapi.h"
  #include "nodes/nodeFuncs.h"
! #include "catalog/pg_type.h"
  
  static TupleTableSlot *FunctionNext(FunctionScanState *node);
  
***************
*** 42,51 **** static TupleTableSlot *FunctionNext(FunctionScanState *node);
  static TupleTableSlot *
  FunctionNext(FunctionScanState *node)
  {
-       TupleTableSlot *slot;
        EState     *estate;
        ScanDirection direction;
        Tuplestorestate *tuplestorestate;
  
        /*
         * get information from the estate and scan state
--- 42,68 ----
  static TupleTableSlot *
  FunctionNext(FunctionScanState *node)
  {
        EState     *estate;
        ScanDirection direction;
        Tuplestorestate *tuplestorestate;
+       TupleTableSlot *scanslot;
+       TupleTableSlot *funcslot;
+ 
+       if (node->func_slot)
+       {
+               /*
+                * ORDINALITY case: FUNCSLOT is the function return,
+                * SCANSLOT the scan result
+                */
+ 
+               funcslot = node->func_slot;
+               scanslot = node->ss.ss_ScanTupleSlot;
+       }
+       else
+       {
+               funcslot = node->ss.ss_ScanTupleSlot;
+               scanslot = NULL;
+       }
  
        /*
         * get information from the estate and scan state
***************
*** 64,82 **** FunctionNext(FunctionScanState *node)
                node->tuplestorestate = tuplestorestate =
                        ExecMakeTableFunctionResult(node->funcexpr,
                                                                                
node->ss.ps.ps_ExprContext,
!                                                                               
node->tupdesc,
                                                                                
node->eflags & EXEC_FLAG_BACKWARD);
        }
  
        /*
         * Get the next tuple from tuplestore. Return NULL if no more tuples.
         */
-       slot = node->ss.ss_ScanTupleSlot;
        (void) tuplestore_gettupleslot(tuplestorestate,
                                                                   
ScanDirectionIsForward(direction),
                                                                   false,
!                                                                  slot);
!       return slot;
  }
  
  /*
--- 81,132 ----
                node->tuplestorestate = tuplestorestate =
                        ExecMakeTableFunctionResult(node->funcexpr,
                                                                                
node->ss.ps.ps_ExprContext,
!                                                                               
node->func_tupdesc,
                                                                                
node->eflags & EXEC_FLAG_BACKWARD);
        }
  
        /*
         * Get the next tuple from tuplestore. Return NULL if no more tuples.
         */
        (void) tuplestore_gettupleslot(tuplestorestate,
                                                                   
ScanDirectionIsForward(direction),
                                                                   false,
!                                                                  funcslot);
! 
!       if (!scanslot)
!               return funcslot;
! 
!       /*
!        * we're doing ordinality, so we copy the values from the function 
return
!        * slot to the (distinct) scan slot. We can do this because the 
lifetimes
!        * of the values in each slot are the same; until we reset the scan or
!        * fetch the next tuple, both will be valid.
!        */
! 
!       ExecClearTuple(scanslot);
! 
!       if (!TupIsNull(funcslot))
!       {
!               int     natts = funcslot->tts_tupleDescriptor->natts;
!               int     i;
! 
!               slot_getallattrs(funcslot);
! 
!               for (i = 0; i < natts; ++i)
!               {
!                       scanslot->tts_values[i] = funcslot->tts_values[i];
!                       scanslot->tts_isnull[i] = funcslot->tts_isnull[i];
!               }
! 
!               node->ordinal++;
! 
!               scanslot->tts_values[natts] = Int64GetDatumFast(node->ordinal);
!               scanslot->tts_isnull[natts] = false;
! 
!               ExecStoreVirtualTuple(scanslot);
!       }
! 
!       return scanslot;
  }
  
  /*
***************
*** 116,122 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int 
eflags)
        FunctionScanState *scanstate;
        Oid                     funcrettype;
        TypeFuncClass functypclass;
!       TupleDesc       tupdesc = NULL;
  
        /* check for unsupported flags */
        Assert(!(eflags & EXEC_FLAG_MARK));
--- 166,173 ----
        FunctionScanState *scanstate;
        Oid                     funcrettype;
        TypeFuncClass functypclass;
!       TupleDesc       func_tupdesc = NULL;
!       TupleDesc       scan_tupdesc = NULL;
  
        /* check for unsupported flags */
        Assert(!(eflags & EXEC_FLAG_MARK));
***************
*** 148,153 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int 
eflags)
--- 199,209 ----
        ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
        ExecInitScanTupleSlot(estate, &scanstate->ss);
  
+       if (node->funcordinality)
+               scanstate->func_slot = ExecInitExtraTupleSlot(estate);
+       else
+               scanstate->func_slot = NULL;
+ 
        /*
         * initialize child expressions
         */
***************
*** 164,200 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int 
eflags)
         */
        functypclass = get_expr_result_type(node->funcexpr,
                                                                                
&funcrettype,
!                                                                               
&tupdesc);
  
        if (functypclass == TYPEFUNC_COMPOSITE)
        {
                /* Composite data type, e.g. a table's row type */
!               Assert(tupdesc);
                /* Must copy it out of typcache for safety */
!               tupdesc = CreateTupleDescCopy(tupdesc);
        }
        else if (functypclass == TYPEFUNC_SCALAR)
        {
                /* Base data type, i.e. scalar */
                char       *attname = strVal(linitial(node->funccolnames));
  
!               tupdesc = CreateTemplateTupleDesc(1, false);
!               TupleDescInitEntry(tupdesc,
                                                   (AttrNumber) 1,
                                                   attname,
                                                   funcrettype,
                                                   -1,
                                                   0);
!               TupleDescInitEntryCollation(tupdesc,
                                                                        
(AttrNumber) 1,
                                                                        
exprCollation(node->funcexpr));
        }
        else if (functypclass == TYPEFUNC_RECORD)
        {
!               tupdesc = BuildDescFromLists(node->funccolnames,
!                                                                        
node->funccoltypes,
!                                                                        
node->funccoltypmods,
!                                                                        
node->funccolcollations);
        }
        else
        {
--- 220,267 ----
         */
        functypclass = get_expr_result_type(node->funcexpr,
                                                                                
&funcrettype,
!                                                                               
&func_tupdesc);
  
        if (functypclass == TYPEFUNC_COMPOSITE)
        {
                /* Composite data type, e.g. a table's row type */
!               Assert(func_tupdesc);
! 
!               /*
!                * XXX
!                * Existing behaviour is a bit inconsistent with regard to 
aliases and
!                * whole-row Vars of the function result. If the function 
returns a
!                * composite type, then the whole-row Var will refer to this 
tupdesc,
!                * which has the type's own column names rather than the alias 
column
!                * names given in the query. This affects the output of 
constructs like
!                * row_to_json which read the column names from the passed-in 
values.
!                */
! 
                /* Must copy it out of typcache for safety */
!               func_tupdesc = CreateTupleDescCopy(func_tupdesc);
        }
        else if (functypclass == TYPEFUNC_SCALAR)
        {
                /* Base data type, i.e. scalar */
                char       *attname = strVal(linitial(node->funccolnames));
  
!               func_tupdesc = CreateTemplateTupleDesc(1, false);
!               TupleDescInitEntry(func_tupdesc,
                                                   (AttrNumber) 1,
                                                   attname,
                                                   funcrettype,
                                                   -1,
                                                   0);
!               TupleDescInitEntryCollation(func_tupdesc,
                                                                        
(AttrNumber) 1,
                                                                        
exprCollation(node->funcexpr));
        }
        else if (functypclass == TYPEFUNC_RECORD)
        {
!               func_tupdesc = BuildDescFromLists(node->funccolnames,
!                                                                               
  node->funccoltypes,
!                                                                               
  node->funccoltypmods,
!                                                                               
  node->funccolcollations);
        }
        else
        {
***************
*** 207,220 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int 
eflags)
         * function should do this for itself, but let's cover things in case it
         * doesn't.)
         */
!       BlessTupleDesc(tupdesc);
  
!       scanstate->tupdesc = tupdesc;
!       ExecAssignScanType(&scanstate->ss, tupdesc);
  
        /*
         * Other node-specific setup
         */
        scanstate->tuplestorestate = NULL;
        scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
                                                                           
(PlanState *) scanstate);
--- 274,309 ----
         * function should do this for itself, but let's cover things in case it
         * doesn't.)
         */
!       BlessTupleDesc(func_tupdesc);
  
!       if (node->funcordinality)
!       {
!               int natts = func_tupdesc->natts;
! 
!               scan_tupdesc = CreateTupleDescCopyExtend(func_tupdesc, 1);
! 
!               TupleDescInitEntry(scan_tupdesc,
!                                                  natts + 1,
!                                                  
strVal(llast(node->funccolnames)),
!                                                  INT8OID,
!                                                  -1,
!                                                  0);
! 
!               BlessTupleDesc(scan_tupdesc);
! 
!               ExecSetSlotDescriptor(scanstate->func_slot, func_tupdesc);
!       }
!       else
!               scan_tupdesc = func_tupdesc;
! 
!       scanstate->scan_tupdesc = scan_tupdesc;
!       scanstate->func_tupdesc = func_tupdesc;
!       ExecAssignScanType(&scanstate->ss, scan_tupdesc);
  
        /*
         * Other node-specific setup
         */
+       scanstate->ordinal = 0;
        scanstate->tuplestorestate = NULL;
        scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
                                                                           
(PlanState *) scanstate);
***************
*** 249,254 **** ExecEndFunctionScan(FunctionScanState *node)
--- 338,345 ----
         */
        ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
        ExecClearTuple(node->ss.ss_ScanTupleSlot);
+       if (node->func_slot)
+               ExecClearTuple(node->func_slot);
  
        /*
         * Release tuplestore resources
***************
*** 268,276 **** void
--- 359,371 ----
  ExecReScanFunctionScan(FunctionScanState *node)
  {
        ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
+       if (node->func_slot)
+               ExecClearTuple(node->func_slot);
  
        ExecScanReScan(&node->ss);
  
+       node->ordinal = 0;
+ 
        /*
         * If we haven't materialized yet, just return.
         */
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 503,508 **** _copyFunctionScan(const FunctionScan *from)
--- 503,509 ----
         * copy remainder of node
         */
        COPY_NODE_FIELD(funcexpr);
+       COPY_SCALAR_FIELD(funcordinality);
        COPY_NODE_FIELD(funccolnames);
        COPY_NODE_FIELD(funccoltypes);
        COPY_NODE_FIELD(funccoltypmods);
***************
*** 1989,1994 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 1990,1996 ----
        COPY_NODE_FIELD(alias);
        COPY_NODE_FIELD(eref);
        COPY_SCALAR_FIELD(lateral);
+       COPY_SCALAR_FIELD(ordinality);
        COPY_SCALAR_FIELD(inh);
        COPY_SCALAR_FIELD(inFromCl);
        COPY_SCALAR_FIELD(requiredPerms);
***************
*** 2279,2284 **** _copyRangeFunction(const RangeFunction *from)
--- 2281,2287 ----
        RangeFunction *newnode = makeNode(RangeFunction);
  
        COPY_SCALAR_FIELD(lateral);
+       COPY_SCALAR_FIELD(ordinality);
        COPY_NODE_FIELD(funccallnode);
        COPY_NODE_FIELD(alias);
        COPY_NODE_FIELD(coldeflist);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2123,2128 **** static bool
--- 2123,2129 ----
  _equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
  {
        COMPARE_SCALAR_FIELD(lateral);
+       COMPARE_SCALAR_FIELD(ordinality);
        COMPARE_NODE_FIELD(funccallnode);
        COMPARE_NODE_FIELD(alias);
        COMPARE_NODE_FIELD(coldeflist);
***************
*** 2241,2246 **** _equalRangeTblEntry(const RangeTblEntry *a, const 
RangeTblEntry *b)
--- 2242,2248 ----
        COMPARE_NODE_FIELD(alias);
        COMPARE_NODE_FIELD(eref);
        COMPARE_SCALAR_FIELD(lateral);
+       COMPARE_SCALAR_FIELD(ordinality);
        COMPARE_SCALAR_FIELD(inh);
        COMPARE_SCALAR_FIELD(inFromCl);
        COMPARE_SCALAR_FIELD(requiredPerms);
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 153,164 **** makeWholeRowVar(RangeTblEntry *rte,
                        break;
                case RTE_FUNCTION:
                        toid = exprType(rte->funcexpr);
!                       if (type_is_rowtype(toid))
                        {
                                /* func returns composite; same as relation 
case */
                                result = makeVar(varno,
                                                                 
InvalidAttrNumber,
!                                                                toid,
                                                                 -1,
                                                                 InvalidOid,
                                                                 varlevelsup);
--- 153,164 ----
                        break;
                case RTE_FUNCTION:
                        toid = exprType(rte->funcexpr);
!                       if (type_is_rowtype(toid) || rte->ordinality)
                        {
                                /* func returns composite; same as relation 
case */
                                result = makeVar(varno,
                                                                 
InvalidAttrNumber,
!                                                                
(rte->ordinality) ? RECORDOID : toid,
                                                                 -1,
                                                                 InvalidOid,
                                                                 varlevelsup);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 515,520 **** _outFunctionScan(StringInfo str, const FunctionScan *node)
--- 515,521 ----
        _outScanInfo(str, (const Scan *) node);
  
        WRITE_NODE_FIELD(funcexpr);
+       WRITE_BOOL_FIELD(funcordinality);
        WRITE_NODE_FIELD(funccolnames);
        WRITE_NODE_FIELD(funccoltypes);
        WRITE_NODE_FIELD(funccoltypmods);
***************
*** 2384,2389 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2385,2391 ----
        }
  
        WRITE_BOOL_FIELD(lateral);
+       WRITE_BOOL_FIELD(ordinality);
        WRITE_BOOL_FIELD(inh);
        WRITE_BOOL_FIELD(inFromCl);
        WRITE_UINT_FIELD(requiredPerms);
***************
*** 2598,2603 **** _outRangeFunction(StringInfo str, const RangeFunction *node)
--- 2600,2606 ----
        WRITE_NODE_TYPE("RANGEFUNCTION");
  
        WRITE_BOOL_FIELD(lateral);
+       WRITE_BOOL_FIELD(ordinality);
        WRITE_NODE_FIELD(funccallnode);
        WRITE_NODE_FIELD(alias);
        WRITE_NODE_FIELD(coldeflist);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1224,1229 **** _readRangeTblEntry(void)
--- 1224,1230 ----
        }
  
        READ_BOOL_FIELD(lateral);
+       READ_BOOL_FIELD(ordinality);
        READ_BOOL_FIELD(inh);
        READ_BOOL_FIELD(inFromCl);
        READ_UINT_FIELD(requiredPerms);
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 114,121 **** static BitmapHeapScan *make_bitmap_heapscan(List *qptlist,
  static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
                         List *tidquals);
  static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
!                                 Index scanrelid, Node *funcexpr, List 
*funccolnames,
!                                 List *funccoltypes, List *funccoltypmods,
                                  List *funccolcollations);
  static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
                                Index scanrelid, List *values_lists);
--- 114,121 ----
  static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
                         List *tidquals);
  static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
!                                 Index scanrelid, Node *funcexpr, bool 
ordinality,
!                   List *funccolnames, List *funccoltypes, List 
*funccoltypmods,
                                  List *funccolcollations);
  static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
                                Index scanrelid, List *values_lists);
***************
*** 1723,1728 **** create_functionscan_plan(PlannerInfo *root, Path *best_path,
--- 1723,1729 ----
  
        scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
                                                                  funcexpr,
+                                                                 
rte->ordinality,
                                                                  
rte->eref->colnames,
                                                                  
rte->funccoltypes,
                                                                  
rte->funccoltypmods,
***************
*** 3356,3361 **** make_functionscan(List *qptlist,
--- 3357,3363 ----
                                  List *qpqual,
                                  Index scanrelid,
                                  Node *funcexpr,
+                                 bool ordinality,
                                  List *funccolnames,
                                  List *funccoltypes,
                                  List *funccoltypmods,
***************
*** 3371,3376 **** make_functionscan(List *qptlist,
--- 3373,3379 ----
        plan->righttree = NULL;
        node->scan.scanrelid = scanrelid;
        node->funcexpr = funcexpr;
+       node->funcordinality = ordinality;
        node->funccolnames = funccolnames;
        node->funccoltypes = funccoltypes;
        node->funccoltypmods = funccoltypmods;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 562,568 **** static void processCASbits(int cas_bits, int location, const 
char *constrType,
        NULLS_P NUMERIC
  
        OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
!       ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
  
        PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
        PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
--- 562,568 ----
        NULLS_P NUMERIC
  
        OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
!       ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
  
        PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
        PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
***************
*** 591,597 **** static void processCASbits(int cas_bits, int location, const 
char *constrType,
        VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
        VERBOSE VERSION_P VIEW VOLATILE
  
!       WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
  
        XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
        XMLPI XMLROOT XMLSERIALIZE
--- 591,597 ----
        VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
        VERBOSE VERSION_P VIEW VOLATILE
  
!       WHEN WHERE WHITESPACE_P WINDOW WITH WITH_ORDINALITY WITHOUT WORK 
WRAPPER WRITE
  
        XML_P XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
        XMLPI XMLROOT XMLSERIALIZE
***************
*** 9391,9410 **** table_ref:   relation_expr opt_alias_clause
--- 9391,9432 ----
                                {
                                        RangeFunction *n = 
makeNode(RangeFunction);
                                        n->lateral = false;
+                                       n->ordinality = false;
                                        n->funccallnode = $1;
                                        n->alias = linitial($2);
                                        n->coldeflist = lsecond($2);
                                        $$ = (Node *) n;
                                }
+                       | func_table WITH_ORDINALITY func_alias_clause
+                               {
+                                       RangeFunction *n = 
makeNode(RangeFunction);
+                                       n->lateral = false;
+                                       n->ordinality = true;
+                                       n->funccallnode = $1;
+                                       n->alias = linitial($3);
+                                       n->coldeflist = lsecond($3);
+                                       $$ = (Node *) n;
+                               }
                        | LATERAL_P func_table func_alias_clause
                                {
                                        RangeFunction *n = 
makeNode(RangeFunction);
                                        n->lateral = true;
+                                       n->ordinality = false;
                                        n->funccallnode = $2;
                                        n->alias = linitial($3);
                                        n->coldeflist = lsecond($3);
                                        $$ = (Node *) n;
                                }
+                       | LATERAL_P func_table WITH_ORDINALITY func_alias_clause
+                               {
+                                       RangeFunction *n = 
makeNode(RangeFunction);
+                                       n->lateral = true;
+                                       n->ordinality = true;
+                                       n->funccallnode = $2;
+                                       n->alias = linitial($4);
+                                       n->coldeflist = lsecond($4);
+                                       $$ = (Node *) n;
+                               }
                        | select_with_parens opt_alias_clause
                                {
                                        RangeSubselect *n = 
makeNode(RangeSubselect);
***************
*** 12583,12588 **** unreserved_keyword:
--- 12605,12611 ----
                        | OPERATOR
                        | OPTION
                        | OPTIONS
+                       | ORDINALITY
                        | OWNED
                        | OWNER
                        | PARSER
*** a/src/backend/parser/parse_clause.c
--- b/src/backend/parser/parse_clause.c
***************
*** 536,543 **** transformRangeFunction(ParseState *pstate, RangeFunction *r)
        /*
         * OK, build an RTE for the function.
         */
!       rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr,
!                                                                               
r, r->lateral, true);
  
        /*
         * If a coldeflist was supplied, ensure it defines a legal set of names
--- 536,542 ----
        /*
         * OK, build an RTE for the function.
         */
!       rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr, r, 
true);
  
        /*
         * If a coldeflist was supplied, ensure it defines a legal set of names
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
***************
*** 796,802 **** markVarForSelectPriv(ParseState *pstate, Var *var, 
RangeTblEntry *rte)
   * physical column numbers.
   */
  static void
! buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
  {
        int                     maxattrs = tupdesc->natts;
        ListCell   *aliaslc;
--- 796,802 ----
   * physical column numbers.
   */
  static void
! buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool 
ordinality)
  {
        int                     maxattrs = tupdesc->natts;
        ListCell   *aliaslc;
***************
*** 848,853 **** buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias 
*eref)
--- 848,872 ----
                eref->colnames = lappend(eref->colnames, attrname);
        }
  
+       /* tack on the ordinality column at the end */
+       if (ordinality)
+       {
+               Value *attrname;
+ 
+               if (aliaslc)
+               {
+                       attrname = (Value *) lfirst(aliaslc);
+                       aliaslc = lnext(aliaslc);
+                       alias->colnames = lappend(alias->colnames, attrname);
+               }
+               else
+               {
+                       attrname = makeString(pstrdup("?column?"));
+               }
+ 
+               eref->colnames = lappend(eref->colnames, attrname);
+       }
+ 
        /* Too many user-supplied aliases? */
        if (aliaslc)
                ereport(ERROR,
***************
*** 870,912 **** buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias 
*eref)
   */
  static void
  buildScalarFunctionAlias(Node *funcexpr, char *funcname,
!                                                Alias *alias, Alias *eref)
  {
-       char       *pname;
- 
        Assert(eref->colnames == NIL);
  
        /* Use user-specified column alias if there is one. */
        if (alias && alias->colnames != NIL)
        {
!               if (list_length(alias->colnames) != 1)
                        ereport(ERROR,
                                        
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
                                  errmsg("too many column aliases specified for 
function %s",
                                                 funcname)));
                eref->colnames = copyObject(alias->colnames);
-               return;
        }
! 
!       /*
!        * If the expression is a simple function call, and the function has a
!        * single OUT parameter that is named, use the parameter's name.
!        */
!       if (funcexpr && IsA(funcexpr, FuncExpr))
        {
!               pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid);
!               if (pname)
!               {
!                       eref->colnames = list_make1(makeString(pname));
!                       return;
!               }
        }
  
!       /*
!        * Otherwise use the previously-determined alias (not necessarily the
!        * function name!)
!        */
!       eref->colnames = list_make1(makeString(eref->aliasname));
  }
  
  /*
--- 889,930 ----
   */
  static void
  buildScalarFunctionAlias(Node *funcexpr, char *funcname,
!                                                Alias *alias, Alias *eref, 
bool ordinality)
  {
        Assert(eref->colnames == NIL);
  
        /* Use user-specified column alias if there is one. */
        if (alias && alias->colnames != NIL)
        {
!               if (list_length(alias->colnames) > (ordinality ? 2 : 1))
                        ereport(ERROR,
                                        
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
                                  errmsg("too many column aliases specified for 
function %s",
                                                 funcname)));
+ 
                eref->colnames = copyObject(alias->colnames);
        }
!       else
        {
!               char       *pname = NULL;
! 
!               /*
!                * If the expression is a simple function call, and the 
function has a
!                * single OUT parameter that is named, use the parameter's name.
!                */
!               if (funcexpr && IsA(funcexpr, FuncExpr))
!                       pname = get_func_result_name(((FuncExpr *) 
funcexpr)->funcid);
! 
!               if (!pname)
!                       pname = eref->aliasname;
! 
!               eref->colnames = list_make1(makeString(pname));
        }
  
!       if (ordinality && list_length(eref->colnames) < 2)
!               eref->colnames = lappend(eref->colnames, 
makeString(pstrdup("?column?")));
! 
!       return;
  }
  
  /*
***************
*** 1002,1008 **** addRangeTableEntry(ParseState *pstate,
         * and/or actual column names.
         */
        rte->eref = makeAlias(refname, NIL);
!       buildRelationAliases(rel->rd_att, alias, rte->eref);
  
        /*
         * Drop the rel refcount, but keep the access lock till end of 
transaction
--- 1020,1026 ----
         * and/or actual column names.
         */
        rte->eref = makeAlias(refname, NIL);
!       buildRelationAliases(rel->rd_att, alias, rte->eref, false);
  
        /*
         * Drop the rel refcount, but keep the access lock till end of 
transaction
***************
*** 1062,1068 **** addRangeTableEntryForRelation(ParseState *pstate,
         * and/or actual column names.
         */
        rte->eref = makeAlias(refname, NIL);
!       buildRelationAliases(rel->rd_att, alias, rte->eref);
  
        /*
         * Set flags and access permissions.
--- 1080,1086 ----
         * and/or actual column names.
         */
        rte->eref = makeAlias(refname, NIL);
!       buildRelationAliases(rel->rd_att, alias, rte->eref, false);
  
        /*
         * Set flags and access permissions.
***************
*** 1177,1183 **** addRangeTableEntryForFunction(ParseState *pstate,
                                                          char *funcname,
                                                          Node *funcexpr,
                                                          RangeFunction 
*rangefunc,
-                                                         bool lateral,
                                                          bool inFromCl)
  {
        RangeTblEntry *rte = makeNode(RangeTblEntry);
--- 1195,1200 ----
***************
*** 1233,1249 **** addRangeTableEntryForFunction(ParseState *pstate,
                /* Composite data type, e.g. a table's row type */
                Assert(tupdesc);
                /* Build the column alias list */
!               buildRelationAliases(tupdesc, alias, eref);
        }
        else if (functypclass == TYPEFUNC_SCALAR)
        {
                /* Base data type, i.e. scalar */
!               buildScalarFunctionAlias(funcexpr, funcname, alias, eref);
        }
        else if (functypclass == TYPEFUNC_RECORD)
        {
                ListCell   *col;
  
                /*
                 * Use the column definition list to form the alias list and
                 * funccoltypes/funccoltypmods/funccolcollations lists.
--- 1250,1272 ----
                /* Composite data type, e.g. a table's row type */
                Assert(tupdesc);
                /* Build the column alias list */
!               buildRelationAliases(tupdesc, alias, eref, 
rangefunc->ordinality);
        }
        else if (functypclass == TYPEFUNC_SCALAR)
        {
                /* Base data type, i.e. scalar */
!               buildScalarFunctionAlias(funcexpr, funcname, alias, eref, 
rangefunc->ordinality);
        }
        else if (functypclass == TYPEFUNC_RECORD)
        {
                ListCell   *col;
  
+               if (rangefunc->ordinality)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("WITH ORDINALITY is not 
supported for functions returning \"record\""),
+                                        parser_errposition(pstate, 
exprLocation(funcexpr))));
+ 
                /*
                 * Use the column definition list to form the alias list and
                 * funccoltypes/funccoltypmods/funccolcollations lists.
***************
*** 1285,1291 **** addRangeTableEntryForFunction(ParseState *pstate,
         * Functions are never checked for access rights (at least, not by the 
RTE
         * permissions mechanism).
         */
!       rte->lateral = lateral;
        rte->inh = false;                       /* never true for functions */
        rte->inFromCl = inFromCl;
  
--- 1308,1315 ----
         * Functions are never checked for access rights (at least, not by the 
RTE
         * permissions mechanism).
         */
!       rte->lateral = rangefunc->lateral;
!       rte->ordinality = rangefunc->ordinality;
        rte->inh = false;                       /* never true for functions */
        rte->inFromCl = inFromCl;
  
***************
*** 1709,1714 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1733,1739 ----
                                TypeFuncClass functypclass;
                                Oid                     funcrettype;
                                TupleDesc       tupdesc;
+                               int         ordattno = 0;
  
                                functypclass = 
get_expr_result_type(rte->funcexpr,
                                                                                
                        &funcrettype,
***************
*** 1720,1725 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1745,1751 ----
                                        expandTupleDesc(tupdesc, rte->eref,
                                                                        
rtindex, sublevels_up, location,
                                                                        
include_dropped, colnames, colvars);
+                                       ordattno = tupdesc->natts + 1;
                                }
                                else if (functypclass == TYPEFUNC_SCALAR)
                                {
***************
*** 1740,1745 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1766,1773 ----
  
                                                *colvars = lappend(*colvars, 
varnode);
                                        }
+ 
+                                       ordattno = 2;
                                }
                                else if (functypclass == TYPEFUNC_RECORD)
                                {
***************
*** 1778,1783 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1806,1829 ----
                                        /* addRangeTableEntryForFunction 
should've caught this */
                                        elog(ERROR, "function in FROM has 
unsupported return type");
                                }
+ 
+                               /* tack on the extra ordinality column if 
present */
+                               if (rte->ordinality)
+                               {
+                                       if (colnames)
+                                               *colnames = lappend(*colnames, 
llast(rte->eref->colnames));
+ 
+                                       if (colvars)
+                                       {
+                                               Var *varnode = makeVar(rtindex,
+                                                                               
           ordattno,
+                                                                               
           INT8OID,
+                                                                               
           -1,
+                                                                               
           InvalidOid,
+                                                                               
           sublevels_up);
+                                               *colvars = lappend(*colvars, 
varnode);
+                                       }
+                               }
                        }
                        break;
                case RTE_VALUES:
*** a/src/backend/parser/parser.c
--- b/src/backend/parser/parser.c
***************
*** 133,139 **** base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t 
yyscanner)
                case WITH:
  
                        /*
!                        * WITH TIME must be reduced to one token
                         */
                        cur_yylval = lvalp->core_yystype;
                        cur_yylloc = *llocp;
--- 133,139 ----
                case WITH:
  
                        /*
!                        * WITH TIME and WITH ORDINALITY must each be reduced 
to one token
                         */
                        cur_yylval = lvalp->core_yystype;
                        cur_yylloc = *llocp;
***************
*** 143,148 **** base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t 
yyscanner)
--- 143,151 ----
                                case TIME:
                                        cur_token = WITH_TIME;
                                        break;
+                               case ORDINALITY:
+                                       cur_token = WITH_ORDINALITY;
+                                       break;
                                default:
                                        /* save the lookahead token for next 
time */
                                        yyextra->lookahead_token = next_token;
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 7938,7943 **** get_from_clause_item(Node *jtnode, Query *query, 
deparse_context *context)
--- 7938,7945 ----
                        case RTE_FUNCTION:
                                /* Function RTE */
                                get_rule_expr(rte->funcexpr, context, true);
+                               if (rte->ordinality)
+                                       appendStringInfoString(buf, " WITH 
ORDINALITY");
                                break;
                        case RTE_VALUES:
                                /* Values list RTE */
*** a/src/include/access/tupdesc.h
--- b/src/include/access/tupdesc.h
***************
*** 87,92 **** extern TupleDesc CreateTupleDesc(int natts, bool hasoid,
--- 87,93 ----
                                Form_pg_attribute *attrs);
  
  extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
+ extern TupleDesc CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts);
  
  extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
  
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 1393,1399 **** typedef struct FunctionScanState
  {
        ScanState       ss;                             /* its first field is 
NodeTag */
        int                     eflags;
!       TupleDesc       tupdesc;
        Tuplestorestate *tuplestorestate;
        ExprState  *funcexpr;
  } FunctionScanState;
--- 1393,1402 ----
  {
        ScanState       ss;                             /* its first field is 
NodeTag */
        int                     eflags;
!       int64       ordinal;
!       TupleDesc       scan_tupdesc;
!       TupleDesc       func_tupdesc;
!       TupleTableSlot *func_slot;
        Tuplestorestate *tuplestorestate;
        ExprState  *funcexpr;
  } FunctionScanState;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 463,468 **** typedef struct RangeFunction
--- 463,469 ----
  {
        NodeTag         type;
        bool            lateral;                /* does it have LATERAL prefix? 
*/
+       bool            ordinality;             /* does it have WITH ORDINALITY 
suffix? */
        Node       *funccallnode;       /* untransformed function call tree */
        Alias      *alias;                      /* table alias & optional 
column aliases */
        List       *coldeflist;         /* list of ColumnDef nodes to describe 
result
***************
*** 759,764 **** typedef struct RangeTblEntry
--- 760,766 ----
        Alias      *alias;                      /* user-written alias clause, 
if any */
        Alias      *eref;                       /* expanded reference names */
        bool            lateral;                /* subquery, function, or 
values is LATERAL? */
+       bool            ordinality;             /* function WITH ORDINALITY? */
        bool            inh;                    /* inheritance requested? */
        bool            inFromCl;               /* present in FROM clause? */
        AclMode         requiredPerms;  /* bitmask of required access 
permissions */
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 423,428 **** typedef struct FunctionScan
--- 423,429 ----
  {
        Scan            scan;
        Node       *funcexpr;           /* expression tree for func call */
+       bool        funcordinality;
        List       *funccolnames;       /* output column names (string Value 
nodes) */
        List       *funccoltypes;       /* OID list of column type OIDs */
        List       *funccoltypmods; /* integer list of column typmods */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 267,272 **** PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD)
--- 267,273 ----
  PG_KEYWORD("options", OPTIONS, UNRESERVED_KEYWORD)
  PG_KEYWORD("or", OR, RESERVED_KEYWORD)
  PG_KEYWORD("order", ORDER, RESERVED_KEYWORD)
+ PG_KEYWORD("ordinality", ORDINALITY, UNRESERVED_KEYWORD)
  PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD)
  PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD)
  PG_KEYWORD("over", OVER, TYPE_FUNC_NAME_KEYWORD)
*** a/src/include/parser/parse_relation.h
--- b/src/include/parser/parse_relation.h
***************
*** 61,67 **** extern RangeTblEntry *addRangeTableEntryForFunction(ParseState 
*pstate,
                                                          char *funcname,
                                                          Node *funcexpr,
                                                          RangeFunction 
*rangefunc,
-                                                         bool lateral,
                                                          bool inFromCl);
  extern RangeTblEntry *addRangeTableEntryForValues(ParseState *pstate,
                                                        List *exprs,
--- 61,66 ----
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to