Patch: UNNEST (and other functions) WITH ORDINALITY

Started by David Fetteralmost 13 years ago8 messages
#1David Fetter
david@fetter.org
1 attachment(s)

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 <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

Attachments:

ordinality_20130122_2220.patchtext/plain; charset=us-asciiDownload
*** 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 ----
#2David Fetter
david@fetter.org
In reply to: David Fetter (#1)
Re: Patch: UNNEST (and other functions) WITH ORDINALITY

On Tue, Jan 22, 2013 at 10:29:43PM -0800, David Fetter wrote:

Folks,

Please find attached a patch which implements the SQL standard
UNNEST() WITH ORDINALITY.

Added to CF4.

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

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

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

#3Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: David Fetter (#2)
Re: Patch: UNNEST (and other functions) WITH ORDINALITY

David Fetter wrote:

On Tue, Jan 22, 2013 at 10:29:43PM -0800, David Fetter wrote:

Folks,

Please find attached a patch which implements the SQL standard
UNNEST() WITH ORDINALITY.

Added to CF4.

Surely you meant CF 2013-Next (i.e. first commit of 9.4 cycle).

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

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

#4David Fetter
david@fetter.org
In reply to: Alvaro Herrera (#3)
Re: Patch: UNNEST (and other functions) WITH ORDINALITY

On Wed, Jan 23, 2013 at 03:12:37PM -0300, Alvaro Herrera wrote:

David Fetter wrote:

On Tue, Jan 22, 2013 at 10:29:43PM -0800, David Fetter wrote:

Folks,

Please find attached a patch which implements the SQL standard
UNNEST() WITH ORDINALITY.

Added to CF4.

Surely you meant CF 2013-Next (i.e. first commit of 9.4 cycle).

I see that that's what I did, but given that this is a pretty small
feature with low impact, I'm wondering whether it should be on CF4.

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

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

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

#5Bruce Momjian
bruce@momjian.us
In reply to: David Fetter (#4)
Re: Patch: UNNEST (and other functions) WITH ORDINALITY

On Wed, Jan 23, 2013 at 10:15:27AM -0800, David Fetter wrote:

On Wed, Jan 23, 2013 at 03:12:37PM -0300, Alvaro Herrera wrote:

David Fetter wrote:

On Tue, Jan 22, 2013 at 10:29:43PM -0800, David Fetter wrote:

Folks,

Please find attached a patch which implements the SQL standard
UNNEST() WITH ORDINALITY.

Added to CF4.

Surely you meant CF 2013-Next (i.e. first commit of 9.4 cycle).

I see that that's what I did, but given that this is a pretty small
feature with low impact, I'm wondering whether it should be on CF4.

The diff is 1.2k and has no discussion. It should be in CF 2013-Next.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ It's impossible for everything to be true. +

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

#6David Fetter
david@fetter.org
In reply to: Bruce Momjian (#5)
Re: Patch: UNNEST (and other functions) WITH ORDINALITY

On Wed, Jan 23, 2013 at 02:40:45PM -0500, Bruce Momjian wrote:

On Wed, Jan 23, 2013 at 10:15:27AM -0800, David Fetter wrote:

On Wed, Jan 23, 2013 at 03:12:37PM -0300, Alvaro Herrera wrote:

David Fetter wrote:

On Tue, Jan 22, 2013 at 10:29:43PM -0800, David Fetter wrote:

Folks,

Please find attached a patch which implements the SQL standard
UNNEST() WITH ORDINALITY.

Added to CF4.

Surely you meant CF 2013-Next (i.e. first commit of 9.4 cycle).

I see that that's what I did, but given that this is a pretty small
feature with low impact, I'm wondering whether it should be on CF4.

The diff is 1.2k and has no discussion.

It's been up less than a day ;)

It should be in CF 2013-Next.

OK :)

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

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

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

#7David Fetter
david@fetter.org
In reply to: David Fetter (#1)
1 attachment(s)
Re: Patch: UNNEST (and other functions) WITH ORDINALITY

On Tue, Jan 22, 2013 at 10:29:43PM -0800, David Fetter wrote:

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:

Next revision of the patch, now with more stability. Thanks, Andrew!

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

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

Attachments:

ordinality_20130208_1242.patchtext/plain; charset=us-asciiDownload
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 12762,12767 **** postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
--- 12762,12792 ----
         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> [, ...] ) ]
  
***************
*** 368,385 **** 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>
--- 369,403 ----
          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/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 4497,4502 **** inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
--- 4497,4506 ----
  	 */
  	check_stack_depth();
  
+ 	/* Fail if the caller wanted ORDINALITY - we don't implement that here. */
+ 	if (rte->ordinality)
+ 		return NULL;
+ 
  	/* Fail if FROM item isn't a simple FuncExpr */
  	fexpr = (FuncExpr *) rte->funcexpr;
  	if (fexpr == NULL || !IsA(fexpr, FuncExpr))
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 564,570 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  	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
--- 564,570 ----
  	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
***************
*** 593,599 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  	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
--- 593,599 ----
  	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
***************
*** 9417,9436 **** table_ref:	relation_expr opt_alias_clause
--- 9417,9458 ----
  				{
  					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);
***************
*** 12632,12637 **** unreserved_keyword:
--- 12654,12660 ----
  			| OPERATOR
  			| OPTION
  			| OPTIONS
+ 			| ORDINALITY
  			| OWNED
  			| OWNER
  			| PARSER
*** 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.
***************
*** 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.
--- 1251,1273 ----
  		/* 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.
***************
*** 1286,1291 **** addRangeTableEntryForFunction(ParseState *pstate,
--- 1310,1316 ----
  	 * permissions mechanism).
  	 */
  	rte->lateral = 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,
--- 1734,1740 ----
  				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,
--- 1746,1752 ----
  					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,
--- 1767,1774 ----
  
  						*colvars = lappend(*colvars, varnode);
  					}
+ 
+ 					ordattno = 2;
  				}
  				else if (functypclass == TYPEFUNC_RECORD)
  				{
***************
*** 1778,1783 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1807,1830 ----
  					/* 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:
***************
*** 2172,2207 **** get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
  					Form_pg_attribute att_tup;
  
  					Assert(tupdesc);
- 					/* this is probably a can't-happen case */
- 					if (attnum < 1 || attnum > tupdesc->natts)
- 						ereport(ERROR,
- 								(errcode(ERRCODE_UNDEFINED_COLUMN),
- 						errmsg("column %d of relation \"%s\" does not exist",
- 							   attnum,
- 							   rte->eref->aliasname)));
  
! 					att_tup = tupdesc->attrs[attnum - 1];
  
! 					/*
! 					 * If dropped column, pretend it ain't there.  See notes
! 					 * in scanRTEForColumn.
! 					 */
! 					if (att_tup->attisdropped)
! 						ereport(ERROR,
! 								(errcode(ERRCODE_UNDEFINED_COLUMN),
! 								 errmsg("column \"%s\" of relation \"%s\" does not exist",
! 										NameStr(att_tup->attname),
! 										rte->eref->aliasname)));
! 					*vartype = att_tup->atttypid;
! 					*vartypmod = att_tup->atttypmod;
! 					*varcollid = att_tup->attcollation;
  				}
  				else if (functypclass == TYPEFUNC_SCALAR)
  				{
! 					/* Base data type, i.e. scalar */
! 					*vartype = funcrettype;
! 					*vartypmod = -1;
! 					*varcollid = exprCollation(rte->funcexpr);
  				}
  				else if (functypclass == TYPEFUNC_RECORD)
  				{
--- 2219,2275 ----
  					Form_pg_attribute att_tup;
  
  					Assert(tupdesc);
  
! 					if (rte->ordinality && attnum == (tupdesc->natts + 1))
! 					{
! 						*vartype = INT8OID;
! 						*vartypmod = -1;
! 						*varcollid = InvalidOid;
! 					}
! 					else
! 					{
! 						/* this is probably a can't-happen case */
! 						if (attnum < 1 || attnum > tupdesc->natts)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_UNDEFINED_COLUMN),
! 									 errmsg("column %d of relation \"%s\" does not exist",
! 											attnum,
! 											rte->eref->aliasname)));
  
! 						att_tup = tupdesc->attrs[attnum - 1];
! 
! 						/*
! 						 * If dropped column, pretend it ain't there.  See notes
! 						 * in scanRTEForColumn.
! 						 */
! 						if (att_tup->attisdropped)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_UNDEFINED_COLUMN),
! 									 errmsg("column \"%s\" of relation \"%s\" does not exist",
! 											NameStr(att_tup->attname),
! 											rte->eref->aliasname)));
! 						*vartype = att_tup->atttypid;
! 						*vartypmod = att_tup->atttypmod;
! 						*varcollid = att_tup->attcollation;
! 					}
  				}
  				else if (functypclass == TYPEFUNC_SCALAR)
  				{
! 					if (rte->ordinality && attnum == 2)
! 					{
! 						*vartype = INT8OID;
! 						*vartypmod = -1;
! 						*varcollid = InvalidOid;
! 					} 
! 					else
! 					{
! 						Assert(attnum == 1);
! 
! 						/* Base data type, i.e. scalar */
! 						*vartype = funcrettype;
! 						*vartypmod = -1;
! 						*varcollid = exprCollation(rte->funcexpr);
! 					}
  				}
  				else if (functypclass == TYPEFUNC_RECORD)
  				{
***************
*** 2321,2327 **** get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
  				Oid			funcrettype = exprType(rte->funcexpr);
  				Oid			funcrelid = typeidTypeRelid(funcrettype);
  
! 				if (OidIsValid(funcrelid))
  				{
  					/*
  					 * Composite data type, i.e. a table's row type
--- 2389,2399 ----
  				Oid			funcrettype = exprType(rte->funcexpr);
  				Oid			funcrelid = typeidTypeRelid(funcrettype);
  
! 				if (rte->ordinality && attnum == list_length(rte->eref->colnames))
! 				{
! 					result = false;
! 				}
! 				else if (OidIsValid(funcrelid))
  				{
  					/*
  					 * Composite data type, i.e. a table's row type
*** 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
***************
*** 7967,7972 **** get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
--- 7967,7974 ----
  			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
***************
*** 769,774 **** typedef struct RangeTblEntry
--- 770,776 ----
  	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/test/regress/expected/rangefuncs.out
--- b/src/test/regress/expected/rangefuncs.out
***************
*** 18,24 **** CREATE TABLE foo2(fooid int, f2 int);
  INSERT INTO foo2 VALUES(1, 11);
  INSERT INTO foo2 VALUES(2, 22);
  INSERT INTO foo2 VALUES(1, 111);
! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1;' LANGUAGE SQL;
  -- function with implicit LATERAL
  select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
   fooid | f2  | fooid | f2  
--- 18,92 ----
  INSERT INTO foo2 VALUES(1, 11);
  INSERT INTO foo2 VALUES(2, 22);
  INSERT INTO foo2 VALUES(1, 111);
! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1 ORDER BY f2;' LANGUAGE SQL;
! -- function with ORDINALITY
! select * from foot(1) with ordinality as z(a,b,ord);
!  a |  b  | ord 
! ---+-----+-----
!  1 |  11 |   1
!  1 | 111 |   2
! (2 rows)
! 
! select * from foot(1) with ordinality as z(a,b,ord) where b > 100;   -- ordinal 2, not 1
!  a |  b  | ord 
! ---+-----+-----
!  1 | 111 |   2
! (1 row)
! 
! -- ordinality vs. column names and types
! select a,b,ord from foot(1) with ordinality as z(a,b,ord);
!  a |  b  | ord 
! ---+-----+-----
!  1 |  11 |   1
!  1 | 111 |   2
! (2 rows)
! 
! select a,ord from unnest(array['a','b']) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  a |   1
!  b |   2
! (2 rows)
! 
! select * from unnest(array['a','b']) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  a |   1
!  b |   2
! (2 rows)
! 
! select a,ord from unnest(array[1.0::float8]) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  1 |   1
! (1 row)
! 
! select * from unnest(array[1.0::float8]) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  1 |   1
! (1 row)
! 
! -- ordinality vs. views
! create temporary view vw_ord as select * from (values (1)) v(n) join foot(1) with ordinality as z(a,b,ord) on (n=ord);
! select * from vw_ord;
!  n | a | b  | ord 
! ---+---+----+-----
!  1 | 1 | 11 |   1
! (1 row)
! 
! select definition from pg_views where viewname='vw_ord';
!                             definition                             
! -------------------------------------------------------------------
!   SELECT v.n,                                                     +
!      z.a,                                                         +
!      z.b,                                                         +
!      z.ord                                                        +
!     FROM (( VALUES (1)) v(n)                                      +
!     JOIN foot(1) WITH ORDINALITY z(a, b, ord) ON ((v.n = z.ord)));
! (1 row)
! 
! drop view vw_ord;
  -- function with implicit LATERAL
  select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
   fooid | f2  | fooid | f2  
***************
*** 28,33 **** select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
--- 96,110 ----
       1 | 111 |     1 | 111
  (3 rows)
  
+ -- function with implicit LATERAL and explicit ORDINALITY
+ select * from foo2, foot(foo2.fooid) with ordinality as z(fooid,f2,ord) where foo2.f2 = z.f2;
+  fooid | f2  | fooid | f2  | ord 
+ -------+-----+-------+-----+-----
+      1 |  11 |     1 |  11 |   1
+      2 |  22 |     2 |  22 |   1
+      1 | 111 |     1 | 111 |   2
+ (3 rows)
+ 
  -- function in subselect
  select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = foo2.fooid) ORDER BY 1,2;
   fooid | f2  
***************
*** 73,78 **** SELECT * FROM getfoo(1) AS t1;
--- 150,161 ----
    1
  (1 row)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+  v | o 
+ ---+---
+  1 | 1
+ (1 row)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   getfoo 
***************
*** 80,85 **** SELECT * FROM vw_getfoo;
--- 163,176 ----
        1
  (1 row)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY as t1(v,o);
+ SELECT * FROM vw_getfoo;
+  v | o 
+ ---+---
+  1 | 1
+ (1 row)
+ 
  -- sql, proretset = t, prorettype = b
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 91,96 **** SELECT * FROM getfoo(1) AS t1;
--- 182,194 ----
    1
  (2 rows)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+  v | o 
+ ---+---
+  1 | 1
+  1 | 2
+ (2 rows)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   getfoo 
***************
*** 99,104 **** SELECT * FROM vw_getfoo;
--- 197,211 ----
        1
  (2 rows)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
+  v | o 
+ ---+---
+  1 | 1
+  1 | 2
+ (2 rows)
+ 
  -- sql, proretset = t, prorettype = b
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 110,115 **** SELECT * FROM getfoo(1) AS t1;
--- 217,229 ----
   Ed
  (2 rows)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+   v  | o 
+ -----+---
+  Joe | 1
+  Ed  | 2
+ (2 rows)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   getfoo 
***************
*** 118,123 **** SELECT * FROM vw_getfoo;
--- 232,246 ----
   Ed
  (2 rows)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
+   v  | o 
+ -----+---
+  Joe | 1
+  Ed  | 2
+ (2 rows)
+ 
  -- sql, proretset = f, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 128,133 **** SELECT * FROM getfoo(1) AS t1;
--- 251,262 ----
       1 |        1 | Joe
  (1 row)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+ (1 row)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   fooid | foosubid | fooname 
***************
*** 135,140 **** SELECT * FROM vw_getfoo;
--- 264,277 ----
       1 |        1 | Joe
  (1 row)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+ (1 row)
+ 
  -- sql, proretset = t, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 146,151 **** SELECT * FROM getfoo(1) AS t1;
--- 283,295 ----
       1 |        2 | Ed
  (2 rows)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+  1 | 2 | Ed  | 2
+ (2 rows)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   fooid | foosubid | fooname 
***************
*** 154,159 **** SELECT * FROM vw_getfoo;
--- 298,313 ----
       1 |        2 | Ed
  (2 rows)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+  1 | 2 | Ed  | 2
+ (2 rows)
+ 
+ -- ordinality not supported for returns record yet
  -- sql, proretset = f, prorettype = record
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 202,207 **** SELECT * FROM getfoo(1) AS t1;
--- 356,367 ----
    1
  (1 row)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+  v | o 
+ ---+---
+  1 | 1
+ (1 row)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   getfoo 
***************
*** 209,214 **** SELECT * FROM vw_getfoo;
--- 369,382 ----
        1
  (1 row)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
+  v | o 
+ ---+---
+  1 | 1
+ (1 row)
+ 
  -- plpgsql, proretset = f, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 219,224 **** SELECT * FROM getfoo(1) AS t1;
--- 387,398 ----
       1 |        1 | Joe
  (1 row)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+ (1 row)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   fooid | foosubid | fooname 
***************
*** 227,407 **** SELECT * FROM vw_getfoo;
  (1 row)
  
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  DROP FUNCTION foot(int);
  DROP TABLE foo2;
  DROP TABLE foo;
  -- Rescan tests --
! CREATE TABLE foorescan (fooid int, foosubid int, fooname text, primary key(fooid,foosubid));
! INSERT INTO foorescan values(5000,1,'abc.5000.1');
! INSERT INTO foorescan values(5001,1,'abc.5001.1');
! INSERT INTO foorescan values(5002,1,'abc.5002.1');
! INSERT INTO foorescan values(5003,1,'abc.5003.1');
! INSERT INTO foorescan values(5004,1,'abc.5004.1');
! INSERT INTO foorescan values(5005,1,'abc.5005.1');
! INSERT INTO foorescan values(5006,1,'abc.5006.1');
! INSERT INTO foorescan values(5007,1,'abc.5007.1');
! INSERT INTO foorescan values(5008,1,'abc.5008.1');
! INSERT INTO foorescan values(5009,1,'abc.5009.1');
! INSERT INTO foorescan values(5000,2,'abc.5000.2');
! INSERT INTO foorescan values(5001,2,'abc.5001.2');
! INSERT INTO foorescan values(5002,2,'abc.5002.2');
! INSERT INTO foorescan values(5003,2,'abc.5003.2');
! INSERT INTO foorescan values(5004,2,'abc.5004.2');
! INSERT INTO foorescan values(5005,2,'abc.5005.2');
! INSERT INTO foorescan values(5006,2,'abc.5006.2');
! INSERT INTO foorescan values(5007,2,'abc.5007.2');
! INSERT INTO foorescan values(5008,2,'abc.5008.2');
! INSERT INTO foorescan values(5009,2,'abc.5009.2');
! INSERT INTO foorescan values(5000,3,'abc.5000.3');
! INSERT INTO foorescan values(5001,3,'abc.5001.3');
! INSERT INTO foorescan values(5002,3,'abc.5002.3');
! INSERT INTO foorescan values(5003,3,'abc.5003.3');
! INSERT INTO foorescan values(5004,3,'abc.5004.3');
! INSERT INTO foorescan values(5005,3,'abc.5005.3');
! INSERT INTO foorescan values(5006,3,'abc.5006.3');
! INSERT INTO foorescan values(5007,3,'abc.5007.3');
! INSERT INTO foorescan values(5008,3,'abc.5008.3');
! INSERT INTO foorescan values(5009,3,'abc.5009.3');
! INSERT INTO foorescan values(5000,4,'abc.5000.4');
! INSERT INTO foorescan values(5001,4,'abc.5001.4');
! INSERT INTO foorescan values(5002,4,'abc.5002.4');
! INSERT INTO foorescan values(5003,4,'abc.5003.4');
! INSERT INTO foorescan values(5004,4,'abc.5004.4');
! INSERT INTO foorescan values(5005,4,'abc.5005.4');
! INSERT INTO foorescan values(5006,4,'abc.5006.4');
! INSERT INTO foorescan values(5007,4,'abc.5007.4');
! INSERT INTO foorescan values(5008,4,'abc.5008.4');
! INSERT INTO foorescan values(5009,4,'abc.5009.4');
! INSERT INTO foorescan values(5000,5,'abc.5000.5');
! INSERT INTO foorescan values(5001,5,'abc.5001.5');
! INSERT INTO foorescan values(5002,5,'abc.5002.5');
! INSERT INTO foorescan values(5003,5,'abc.5003.5');
! INSERT INTO foorescan values(5004,5,'abc.5004.5');
! INSERT INTO foorescan values(5005,5,'abc.5005.5');
! INSERT INTO foorescan values(5006,5,'abc.5006.5');
! INSERT INTO foorescan values(5007,5,'abc.5007.5');
! INSERT INTO foorescan values(5008,5,'abc.5008.5');
! INSERT INTO foorescan values(5009,5,'abc.5009.5');
! CREATE FUNCTION foorescan(int,int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid >= $1 and fooid < $2 ;' LANGUAGE SQL;
! --invokes ExecReScanFunctionScan
! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;
!  fooid | foosubid |  fooname   
! -------+----------+------------
!   5002 |        1 | abc.5002.1
!   5002 |        2 | abc.5002.2
!   5002 |        3 | abc.5002.3
!   5002 |        4 | abc.5002.4
!   5002 |        5 | abc.5002.5
!   5003 |        1 | abc.5003.1
!   5003 |        2 | abc.5003.2
!   5003 |        3 | abc.5003.3
!   5003 |        4 | abc.5003.4
!   5003 |        5 | abc.5003.5
  (10 rows)
  
! CREATE VIEW vw_foorescan AS SELECT * FROM foorescan(5002,5004);
! --invokes ExecReScanFunctionScan
! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2;
!  fooid | foosubid |  fooname   
! -------+----------+------------
!   5002 |        1 | abc.5002.1
!   5002 |        2 | abc.5002.2
!   5002 |        3 | abc.5002.3
!   5002 |        4 | abc.5002.4
!   5002 |        5 | abc.5002.5
!   5003 |        1 | abc.5003.1
!   5003 |        2 | abc.5003.2
!   5003 |        3 | abc.5003.3
!   5003 |        4 | abc.5003.4
!   5003 |        5 | abc.5003.5
  (10 rows)
  
! CREATE TABLE barrescan (fooid int primary key);
! INSERT INTO barrescan values(5003);
! INSERT INTO barrescan values(5004);
! INSERT INTO barrescan values(5005);
! INSERT INTO barrescan values(5006);
! INSERT INTO barrescan values(5007);
! INSERT INTO barrescan values(5008);
! CREATE FUNCTION foorescan(int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid = $1;' LANGUAGE SQL;
! --invokes ExecReScanFunctionScan with chgParam != NULL
! SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2;
!  fooid | foosubid |  fooname   
! -------+----------+------------
!   5003 |        1 | abc.5003.1
!   5003 |        2 | abc.5003.2
!   5003 |        3 | abc.5003.3
!   5003 |        4 | abc.5003.4
!   5003 |        5 | abc.5003.5
!   5004 |        1 | abc.5004.1
!   5004 |        2 | abc.5004.2
!   5004 |        3 | abc.5004.3
!   5004 |        4 | abc.5004.4
!   5004 |        5 | abc.5004.5
!   5005 |        1 | abc.5005.1
!   5005 |        2 | abc.5005.2
!   5005 |        3 | abc.5005.3
!   5005 |        4 | abc.5005.4
!   5005 |        5 | abc.5005.5
!   5006 |        1 | abc.5006.1
!   5006 |        2 | abc.5006.2
!   5006 |        3 | abc.5006.3
!   5006 |        4 | abc.5006.4
!   5006 |        5 | abc.5006.5
!   5007 |        1 | abc.5007.1
!   5007 |        2 | abc.5007.2
!   5007 |        3 | abc.5007.3
!   5007 |        4 | abc.5007.4
!   5007 |        5 | abc.5007.5
!   5008 |        1 | abc.5008.1
!   5008 |        2 | abc.5008.2
!   5008 |        3 | abc.5008.3
!   5008 |        4 | abc.5008.4
!   5008 |        5 | abc.5008.5
! (30 rows)
! 
! SELECT b.fooid, max(f.foosubid) FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2;
!  fooid | max 
! -------+-----
!   5003 |   5
!   5004 |   5
!   5005 |   5
!   5006 |   5
!   5007 |   5
!   5008 |   5
  (6 rows)
  
! CREATE VIEW fooview1 AS SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2;
! SELECT * FROM fooview1 AS fv WHERE fv.fooid = 5004;
!  fooid | foosubid |  fooname   
! -------+----------+------------
!   5004 |        1 | abc.5004.1
!   5004 |        2 | abc.5004.2
!   5004 |        3 | abc.5004.3
!   5004 |        4 | abc.5004.4
!   5004 |        5 | abc.5004.5
! (5 rows)
! 
! CREATE VIEW fooview2 AS SELECT b.fooid, max(f.foosubid) AS maxsubid FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2;
! SELECT * FROM fooview2 AS fv WHERE fv.maxsubid = 5;
!  fooid | maxsubid 
! -------+----------
!   5003 |        5
!   5004 |        5
!   5005 |        5
!   5006 |        5
!   5007 |        5
!   5008 |        5
  (6 rows)
  
! DROP VIEW vw_foorescan;
! DROP VIEW fooview1;
! DROP VIEW fooview2;
! DROP FUNCTION foorescan(int,int);
! DROP FUNCTION foorescan(int);
! DROP TABLE foorescan;
! DROP TABLE barrescan;
  --
  -- Test cases involving OUT parameters
  --
--- 401,1022 ----
  (1 row)
  
  DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+ (1 row)
+ 
+ DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  DROP FUNCTION foot(int);
  DROP TABLE foo2;
  DROP TABLE foo;
  -- Rescan tests --
! CREATE TEMPORARY SEQUENCE foo_rescan_seq;
! CREATE TYPE foo_rescan_t AS (i integer, s bigint);
! CREATE FUNCTION foo_sql(int,int) RETURNS setof foo_rescan_t AS 'SELECT i, nextval(''foo_rescan_seq'') FROM generate_series($1,$2) i;' LANGUAGE SQL;
! -- plpgsql functions use materialize mode
! CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''foo_rescan_seq'')); end loop; end;' LANGUAGE plpgsql;
! --invokes ExecReScanFunctionScan - all these cases should materialize the function only once
! -- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function
! -- is on the inner path of a nestloop join
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100;
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 11 | 1
!  2 | 12 | 2
!  2 | 13 | 3
!  3 | 11 | 1
!  3 | 12 | 2
!  3 | 13 | 3
! (9 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 11 | 1 | 1
!  2 | 12 | 2 | 2
!  2 | 13 | 3 | 3
!  3 | 11 | 1 | 1
!  3 | 12 | 2 | 2
!  3 | 13 | 3 | 3
! (9 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100;
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 11 | 1
!  2 | 12 | 2
!  2 | 13 | 3
!  3 | 11 | 1
!  3 | 12 | 2
!  3 | 13 | 3
! (9 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 11 | 1 | 1
!  2 | 12 | 2 | 2
!  2 | 13 | 3 | 3
!  3 | 11 | 1 | 1
!  3 | 12 | 2 | 2
!  3 | 13 | 3 | 3
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100;
!  r | i  
! ---+----
!  1 | 11
!  1 | 12
!  1 | 13
!  2 | 11
!  2 | 12
!  2 | 13
!  3 | 11
!  3 | 12
!  3 | 13
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
!  r | i  | o 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 11 | 1
!  2 | 12 | 2
!  2 | 13 | 3
!  3 | 11 | 1
!  3 | 12 | 2
!  3 | 13 | 3
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) f(i) ON (r+i)<100;
!  r | i  
! ---+----
!  1 | 10
!  1 | 20
!  1 | 30
!  2 | 10
!  2 | 20
!  2 | 30
!  3 | 10
!  3 | 20
!  3 | 30
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
!  r | i  | o 
! ---+----+---
!  1 | 10 | 1
!  1 | 20 | 2
!  1 | 30 | 3
!  2 | 10 | 1
!  2 | 20 | 2
!  2 | 30 | 3
!  3 | 10 | 1
!  3 | 20 | 2
!  3 | 30 | 3
! (9 rows)
! 
! --invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 12 | 4
!  2 | 13 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 12 | 4 | 1
!  2 | 13 | 5 | 2
!  3 | 13 | 6 | 1
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  2 | 11 | 2
!  2 | 12 | 3
!  3 | 11 | 4
!  3 | 12 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  2 | 11 | 2 | 1
!  2 | 12 | 3 | 2
!  3 | 11 | 4 | 1
!  3 | 12 | 5 | 2
!  3 | 13 | 6 | 3
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2);
!  r1 | r2 | i  | s  
! ----+----+----+----
!  11 | 12 | 11 |  1
!  11 | 12 | 12 |  2
!  13 | 15 | 13 |  3
!  13 | 15 | 14 |  4
!  13 | 15 | 15 |  5
!  16 | 20 | 16 |  6
!  16 | 20 | 17 |  7
!  16 | 20 | 18 |  8
!  16 | 20 | 19 |  9
!  16 | 20 | 20 | 10
  (10 rows)
  
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORDINALITY AS f(i,s,o);
!  r1 | r2 | i  | s  | o 
! ----+----+----+----+---
!  11 | 12 | 11 |  1 | 1
!  11 | 12 | 12 |  2 | 2
!  13 | 15 | 13 |  3 | 1
!  13 | 15 | 14 |  4 | 2
!  13 | 15 | 15 |  5 | 3
!  16 | 20 | 16 |  6 | 1
!  16 | 20 | 17 |  7 | 2
!  16 | 20 | 18 |  8 | 3
!  16 | 20 | 19 |  9 | 4
!  16 | 20 | 20 | 10 | 5
  (10 rows)
  
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 12 | 4
!  2 | 13 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 12 | 4 | 1
!  2 | 13 | 5 | 2
!  3 | 13 | 6 | 1
  (6 rows)
  
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  2 | 11 | 2
!  2 | 12 | 3
!  3 | 11 | 4
!  3 | 12 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  2 | 11 | 2 | 1
!  2 | 12 | 3 | 2
!  3 | 11 | 4 | 1
!  3 | 12 | 5 | 2
!  3 | 13 | 6 | 3
  (6 rows)
  
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2);
!  r1 | r2 | i  | s  
! ----+----+----+----
!  11 | 12 | 11 |  1
!  11 | 12 | 12 |  2
!  13 | 15 | 13 |  3
!  13 | 15 | 14 |  4
!  13 | 15 | 15 |  5
!  16 | 20 | 16 |  6
!  16 | 20 | 17 |  7
!  16 | 20 | 18 |  8
!  16 | 20 | 19 |  9
!  16 | 20 | 20 | 10
! (10 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORDINALITY AS f(i,s,o);
!  r1 | r2 | i  | s  | o 
! ----+----+----+----+---
!  11 | 12 | 11 |  1 | 1
!  11 | 12 | 12 |  2 | 2
!  13 | 15 | 13 |  3 | 1
!  13 | 15 | 14 |  4 | 2
!  13 | 15 | 15 |  5 | 3
!  16 | 20 | 16 |  6 | 1
!  16 | 20 | 17 |  7 | 2
!  16 | 20 | 18 |  8 | 3
!  16 | 20 | 19 |  9 | 4
!  16 | 20 | 20 | 10 | 5
! (10 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i);
!  r | i  
! ---+----
!  1 | 11
!  1 | 12
!  1 | 13
!  1 | 14
!  1 | 15
!  1 | 16
!  1 | 17
!  1 | 18
!  1 | 19
!  2 | 12
!  2 | 13
!  2 | 14
!  2 | 15
!  2 | 16
!  2 | 17
!  2 | 18
!  3 | 13
!  3 | 14
!  3 | 15
!  3 | 16
!  3 | 17
! (21 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o);
!  r | i  | o 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  1 | 14 | 4
!  1 | 15 | 5
!  1 | 16 | 6
!  1 | 17 | 7
!  1 | 18 | 8
!  1 | 19 | 9
!  2 | 12 | 1
!  2 | 13 | 2
!  2 | 14 | 3
!  2 | 15 | 4
!  2 | 16 | 5
!  2 | 17 | 6
!  2 | 18 | 7
!  3 | 13 | 1
!  3 | 14 | 2
!  3 | 15 | 3
!  3 | 16 | 4
!  3 | 17 | 5
! (21 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) f(i);
!  r | i  
! ---+----
!  1 | 10
!  1 | 20
!  1 | 30
!  2 | 20
!  2 | 40
!  2 | 60
!  3 | 30
!  3 | 60
!  3 | 90
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) WITH ORDINALITY AS f(i,o);
!  r | i  | o 
! ---+----+---
!  1 | 10 | 1
!  1 | 20 | 2
!  1 | 30 | 3
!  2 | 20 | 1
!  2 | 40 | 2
!  2 | 60 | 3
!  3 | 30 | 1
!  3 | 60 | 2
!  3 | 90 | 3
! (9 rows)
! 
! -- deep nesting
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(21,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i  
! ----+----+----+----
!   1 |  1 | 10 | 21
!   1 |  1 | 10 | 22
!   1 |  1 | 10 | 23
!   1 |  1 | 20 | 21
!   1 |  1 | 20 | 22
!   1 |  1 | 20 | 23
!   1 |  1 | 30 | 21
!   1 |  1 | 30 | 22
!   1 |  1 | 30 | 23
!   2 |  2 | 10 | 21
!   2 |  2 | 10 | 22
!   2 |  2 | 10 | 23
!   2 |  2 | 20 | 21
!   2 |  2 | 20 | 22
!   2 |  2 | 20 | 23
!   2 |  2 | 30 | 21
!   2 |  2 | 30 | 22
!   2 |  2 | 30 | 23
!   3 |  3 | 10 | 21
!   3 |  3 | 10 | 22
!   3 |  3 | 10 | 23
!   3 |  3 | 20 | 21
!   3 |  3 | 20 | 22
!   3 |  3 | 20 | 23
!   3 |  3 | 30 | 21
!   3 |  3 | 30 | 22
!   3 |  3 | 30 | 23
! (27 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(20+r1,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i  
! ----+----+----+----
!   1 |  1 | 10 | 21
!   1 |  1 | 10 | 22
!   1 |  1 | 10 | 23
!   1 |  1 | 20 | 21
!   1 |  1 | 20 | 22
!   1 |  1 | 20 | 23
!   1 |  1 | 30 | 21
!   1 |  1 | 30 | 22
!   1 |  1 | 30 | 23
!   2 |  2 | 10 | 22
!   2 |  2 | 10 | 23
!   2 |  2 | 20 | 22
!   2 |  2 | 20 | 23
!   2 |  2 | 30 | 22
!   2 |  2 | 30 | 23
!   3 |  3 | 10 | 23
!   3 |  3 | 20 | 23
!   3 |  3 | 30 | 23
! (18 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(r2,r2+3) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i  
! ----+----+----+----
!   1 |  1 | 10 | 10
!   1 |  1 | 10 | 11
!   1 |  1 | 10 | 12
!   1 |  1 | 10 | 13
!   1 |  1 | 20 | 20
!   1 |  1 | 20 | 21
!   1 |  1 | 20 | 22
!   1 |  1 | 20 | 23
!   1 |  1 | 30 | 30
!   1 |  1 | 30 | 31
!   1 |  1 | 30 | 32
!   1 |  1 | 30 | 33
!   2 |  2 | 10 | 10
!   2 |  2 | 10 | 11
!   2 |  2 | 10 | 12
!   2 |  2 | 10 | 13
!   2 |  2 | 20 | 20
!   2 |  2 | 20 | 21
!   2 |  2 | 20 | 22
!   2 |  2 | 20 | 23
!   2 |  2 | 30 | 30
!   2 |  2 | 30 | 31
!   2 |  2 | 30 | 32
!   2 |  2 | 30 | 33
!   3 |  3 | 10 | 10
!   3 |  3 | 10 | 11
!   3 |  3 | 10 | 12
!   3 |  3 | 10 | 13
!   3 |  3 | 20 | 20
!   3 |  3 | 20 | 21
!   3 |  3 | 20 | 22
!   3 |  3 | 20 | 23
!   3 |  3 | 30 | 30
!   3 |  3 | 30 | 31
!   3 |  3 | 30 | 32
!   3 |  3 | 30 | 33
! (36 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(r1,2+r2/5) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i 
! ----+----+----+---
!   1 |  1 | 10 | 1
!   1 |  1 | 10 | 2
!   1 |  1 | 10 | 3
!   1 |  1 | 10 | 4
!   1 |  1 | 20 | 1
!   1 |  1 | 20 | 2
!   1 |  1 | 20 | 3
!   1 |  1 | 20 | 4
!   1 |  1 | 20 | 5
!   1 |  1 | 20 | 6
!   1 |  1 | 30 | 1
!   1 |  1 | 30 | 2
!   1 |  1 | 30 | 3
!   1 |  1 | 30 | 4
!   1 |  1 | 30 | 5
!   1 |  1 | 30 | 6
!   1 |  1 | 30 | 7
!   1 |  1 | 30 | 8
!   2 |  2 | 10 | 2
!   2 |  2 | 10 | 3
!   2 |  2 | 10 | 4
!   2 |  2 | 20 | 2
!   2 |  2 | 20 | 3
!   2 |  2 | 20 | 4
!   2 |  2 | 20 | 5
!   2 |  2 | 20 | 6
!   2 |  2 | 30 | 2
!   2 |  2 | 30 | 3
!   2 |  2 | 30 | 4
!   2 |  2 | 30 | 5
!   2 |  2 | 30 | 6
!   2 |  2 | 30 | 7
!   2 |  2 | 30 | 8
!   3 |  3 | 10 | 3
!   3 |  3 | 10 | 4
!   3 |  3 | 20 | 3
!   3 |  3 | 20 | 4
!   3 |  3 | 20 | 5
!   3 |  3 | 20 | 6
!   3 |  3 | 30 | 3
!   3 |  3 | 30 | 4
!   3 |  3 | 30 | 5
!   3 |  3 | 30 | 6
!   3 |  3 | 30 | 7
!   3 |  3 | 30 | 8
! (45 rows)
! 
! DROP FUNCTION foo_sql(int,int);
! DROP FUNCTION foo_mat(int,int);
! DROP SEQUENCE foo_rescan_seq;
  --
  -- Test cases involving OUT parameters
  --
***************
*** 877,882 **** SELECT * FROM get_users();
--- 1492,1504 ----
   id2    | email2 | t
  (2 rows)
  
+ SELECT * FROM get_users() WITH ORDINALITY;   -- make sure ordinality copes
+  userid | email  | enabled | ?column? 
+ --------+--------+---------+----------
+  id     | email  | t       |        1
+  id2    | email2 | t       |        2
+ (2 rows)
+ 
  drop function get_first_user();
  drop function get_users();
  drop table users;
*** a/src/test/regress/sql/rangefuncs.sql
--- b/src/test/regress/sql/rangefuncs.sql
***************
*** 5,15 **** INSERT INTO foo2 VALUES(1, 11);
  INSERT INTO foo2 VALUES(2, 22);
  INSERT INTO foo2 VALUES(1, 111);
  
! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1;' LANGUAGE SQL;
  
  -- function with implicit LATERAL
  select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
  
  -- function in subselect
  select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = foo2.fooid) ORDER BY 1,2;
  
--- 5,33 ----
  INSERT INTO foo2 VALUES(2, 22);
  INSERT INTO foo2 VALUES(1, 111);
  
! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1 ORDER BY f2;' LANGUAGE SQL;
! 
! -- function with ORDINALITY
! select * from foot(1) with ordinality as z(a,b,ord);
! select * from foot(1) with ordinality as z(a,b,ord) where b > 100;   -- ordinal 2, not 1
! -- ordinality vs. column names and types
! select a,b,ord from foot(1) with ordinality as z(a,b,ord);
! select a,ord from unnest(array['a','b']) with ordinality as z(a,ord);
! select * from unnest(array['a','b']) with ordinality as z(a,ord);
! select a,ord from unnest(array[1.0::float8]) with ordinality as z(a,ord);
! select * from unnest(array[1.0::float8]) with ordinality as z(a,ord);
! -- ordinality vs. views
! create temporary view vw_ord as select * from (values (1)) v(n) join foot(1) with ordinality as z(a,b,ord) on (n=ord);
! select * from vw_ord;
! select definition from pg_views where viewname='vw_ord';
! drop view vw_ord;
  
  -- function with implicit LATERAL
  select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
  
+ -- function with implicit LATERAL and explicit ORDINALITY
+ select * from foo2, foot(foo2.fooid) with ordinality as z(fooid,f2,ord) where foo2.f2 = z.f2;
+ 
  -- function in subselect
  select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = foo2.fooid) ORDER BY 1,2;
  
***************
*** 30,70 **** INSERT INTO foo VALUES(2,1,'Mary');
--- 48,109 ----
  -- sql, proretset = f, prorettype = b
  CREATE FUNCTION getfoo(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY as t1(v,o);
+ SELECT * FROM vw_getfoo;
  
  -- sql, proretset = t, prorettype = b
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
  
  -- sql, proretset = t, prorettype = b
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
  
  -- sql, proretset = f, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
  
  -- sql, proretset = t, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
  
+ -- ordinality not supported for returns record yet
  -- sql, proretset = f, prorettype = record
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 88,103 **** DROP VIEW vw_getfoo;
--- 127,150 ----
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
  
  -- plpgsql, proretset = f, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
  
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 106,204 **** DROP TABLE foo2;
  DROP TABLE foo;
  
  -- Rescan tests --
! CREATE TABLE foorescan (fooid int, foosubid int, fooname text, primary key(fooid,foosubid));
! INSERT INTO foorescan values(5000,1,'abc.5000.1');
! INSERT INTO foorescan values(5001,1,'abc.5001.1');
! INSERT INTO foorescan values(5002,1,'abc.5002.1');
! INSERT INTO foorescan values(5003,1,'abc.5003.1');
! INSERT INTO foorescan values(5004,1,'abc.5004.1');
! INSERT INTO foorescan values(5005,1,'abc.5005.1');
! INSERT INTO foorescan values(5006,1,'abc.5006.1');
! INSERT INTO foorescan values(5007,1,'abc.5007.1');
! INSERT INTO foorescan values(5008,1,'abc.5008.1');
! INSERT INTO foorescan values(5009,1,'abc.5009.1');
  
! INSERT INTO foorescan values(5000,2,'abc.5000.2');
! INSERT INTO foorescan values(5001,2,'abc.5001.2');
! INSERT INTO foorescan values(5002,2,'abc.5002.2');
! INSERT INTO foorescan values(5003,2,'abc.5003.2');
! INSERT INTO foorescan values(5004,2,'abc.5004.2');
! INSERT INTO foorescan values(5005,2,'abc.5005.2');
! INSERT INTO foorescan values(5006,2,'abc.5006.2');
! INSERT INTO foorescan values(5007,2,'abc.5007.2');
! INSERT INTO foorescan values(5008,2,'abc.5008.2');
! INSERT INTO foorescan values(5009,2,'abc.5009.2');
  
! INSERT INTO foorescan values(5000,3,'abc.5000.3');
! INSERT INTO foorescan values(5001,3,'abc.5001.3');
! INSERT INTO foorescan values(5002,3,'abc.5002.3');
! INSERT INTO foorescan values(5003,3,'abc.5003.3');
! INSERT INTO foorescan values(5004,3,'abc.5004.3');
! INSERT INTO foorescan values(5005,3,'abc.5005.3');
! INSERT INTO foorescan values(5006,3,'abc.5006.3');
! INSERT INTO foorescan values(5007,3,'abc.5007.3');
! INSERT INTO foorescan values(5008,3,'abc.5008.3');
! INSERT INTO foorescan values(5009,3,'abc.5009.3');
  
! INSERT INTO foorescan values(5000,4,'abc.5000.4');
! INSERT INTO foorescan values(5001,4,'abc.5001.4');
! INSERT INTO foorescan values(5002,4,'abc.5002.4');
! INSERT INTO foorescan values(5003,4,'abc.5003.4');
! INSERT INTO foorescan values(5004,4,'abc.5004.4');
! INSERT INTO foorescan values(5005,4,'abc.5005.4');
! INSERT INTO foorescan values(5006,4,'abc.5006.4');
! INSERT INTO foorescan values(5007,4,'abc.5007.4');
! INSERT INTO foorescan values(5008,4,'abc.5008.4');
! INSERT INTO foorescan values(5009,4,'abc.5009.4');
  
! INSERT INTO foorescan values(5000,5,'abc.5000.5');
! INSERT INTO foorescan values(5001,5,'abc.5001.5');
! INSERT INTO foorescan values(5002,5,'abc.5002.5');
! INSERT INTO foorescan values(5003,5,'abc.5003.5');
! INSERT INTO foorescan values(5004,5,'abc.5004.5');
! INSERT INTO foorescan values(5005,5,'abc.5005.5');
! INSERT INTO foorescan values(5006,5,'abc.5006.5');
! INSERT INTO foorescan values(5007,5,'abc.5007.5');
! INSERT INTO foorescan values(5008,5,'abc.5008.5');
! INSERT INTO foorescan values(5009,5,'abc.5009.5');
  
! CREATE FUNCTION foorescan(int,int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid >= $1 and fooid < $2 ;' LANGUAGE SQL;
  
! --invokes ExecReScanFunctionScan
! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;
  
! CREATE VIEW vw_foorescan AS SELECT * FROM foorescan(5002,5004);
  
! --invokes ExecReScanFunctionScan
! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2;
  
! CREATE TABLE barrescan (fooid int primary key);
! INSERT INTO barrescan values(5003);
! INSERT INTO barrescan values(5004);
! INSERT INTO barrescan values(5005);
! INSERT INTO barrescan values(5006);
! INSERT INTO barrescan values(5007);
! INSERT INTO barrescan values(5008);
  
! CREATE FUNCTION foorescan(int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid = $1;' LANGUAGE SQL;
  
! --invokes ExecReScanFunctionScan with chgParam != NULL
! SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2;
! SELECT b.fooid, max(f.foosubid) FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2;
  
! CREATE VIEW fooview1 AS SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2;
! SELECT * FROM fooview1 AS fv WHERE fv.fooid = 5004;
  
! CREATE VIEW fooview2 AS SELECT b.fooid, max(f.foosubid) AS maxsubid FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2;
! SELECT * FROM fooview2 AS fv WHERE fv.maxsubid = 5;
  
! DROP VIEW vw_foorescan;
! DROP VIEW fooview1;
! DROP VIEW fooview2;
! DROP FUNCTION foorescan(int,int);
! DROP FUNCTION foorescan(int);
! DROP TABLE foorescan;
! DROP TABLE barrescan;
  
  --
  -- Test cases involving OUT parameters
--- 153,237 ----
  DROP TABLE foo;
  
  -- Rescan tests --
! CREATE TEMPORARY SEQUENCE foo_rescan_seq;
! CREATE TYPE foo_rescan_t AS (i integer, s bigint);
  
! CREATE FUNCTION foo_sql(int,int) RETURNS setof foo_rescan_t AS 'SELECT i, nextval(''foo_rescan_seq'') FROM generate_series($1,$2) i;' LANGUAGE SQL;
! -- plpgsql functions use materialize mode
! CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''foo_rescan_seq'')); end loop; end;' LANGUAGE plpgsql;
  
! --invokes ExecReScanFunctionScan - all these cases should materialize the function only once
! -- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function
! -- is on the inner path of a nestloop join
  
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100;
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
  
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100;
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
  
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100;
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
  
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) f(i) ON (r+i)<100;
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
  
! --invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
  
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i,s,o);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i,s,o);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORDINALITY AS f(i,s,o);
  
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i,s,o);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORDINALITY AS f(i,s,o);
  
! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o);
  
! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) f(i);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) WITH ORDINALITY AS f(i,o);
  
! -- deep nesting
  
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(21,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(20+r1,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(r2,r2+3) f(i) ON ((r2+i)<100) OFFSET 0) s1;
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(r1,2+r2/5) f(i) ON ((r2+i)<100) OFFSET 0) s1;
  
! DROP FUNCTION foo_sql(int,int);
! DROP FUNCTION foo_mat(int,int);
! DROP SEQUENCE foo_rescan_seq;
  
  --
  -- Test cases involving OUT parameters
***************
*** 414,419 **** language sql stable;
--- 447,453 ----
  
  SELECT get_users();
  SELECT * FROM get_users();
+ SELECT * FROM get_users() WITH ORDINALITY;   -- make sure ordinality copes
  
  drop function get_first_user();
  drop function get_users();
#8David Fetter
david@fetter.org
In reply to: David Fetter (#7)
1 attachment(s)
Re: Patch: UNNEST (and other functions) WITH ORDINALITY

On Fri, Feb 08, 2013 at 01:12:15PM -0800, David Fetter wrote:

On Tue, Jan 22, 2013 at 10:29:43PM -0800, David Fetter wrote:

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:

Next revision of the patch, now with more stability. Thanks, Andrew!

Rebased vs. git master.

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

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

Attachments:

ordinality_07.difftext/plain; charset=us-asciiDownload
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 13273,13278 **** postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
--- 13273,13303 ----
         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> [, ...] ) ]
  
***************
*** 368,385 **** 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>
--- 369,403 ----
          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
***************
*** 504,509 **** _copyFunctionScan(const FunctionScan *from)
--- 504,510 ----
  	 * copy remainder of node
  	 */
  	COPY_NODE_FIELD(funcexpr);
+ 	COPY_SCALAR_FIELD(funcordinality);
  	COPY_NODE_FIELD(funccolnames);
  	COPY_NODE_FIELD(funccoltypes);
  	COPY_NODE_FIELD(funccoltypmods);
***************
*** 1991,1996 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 1992,1998 ----
  	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);
***************
*** 2281,2286 **** _copyRangeFunction(const RangeFunction *from)
--- 2283,2289 ----
  	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
***************
*** 2121,2126 **** static bool
--- 2121,2127 ----
  _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);
***************
*** 2239,2244 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 2240,2246 ----
  	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
***************
*** 516,521 **** _outFunctionScan(StringInfo str, const FunctionScan *node)
--- 516,522 ----
  	_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);
***************
*** 2386,2391 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2387,2393 ----
  	}
  
  	WRITE_BOOL_FIELD(lateral);
+ 	WRITE_BOOL_FIELD(ordinality);
  	WRITE_BOOL_FIELD(inh);
  	WRITE_BOOL_FIELD(inFromCl);
  	WRITE_UINT_FIELD(requiredPerms);
***************
*** 2600,2605 **** _outRangeFunction(StringInfo str, const RangeFunction *node)
--- 2602,2608 ----
  	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
***************
*** 1225,1230 **** _readRangeTblEntry(void)
--- 1225,1231 ----
  	}
  
  	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
***************
*** 115,122 **** 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);
--- 115,122 ----
  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);
***************
*** 1733,1738 **** create_functionscan_plan(PlannerInfo *root, Path *best_path,
--- 1733,1739 ----
  
  	scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
  								  funcexpr,
+ 								  rte->ordinality,
  								  rte->eref->colnames,
  								  rte->funccoltypes,
  								  rte->funccoltypmods,
***************
*** 3366,3371 **** make_functionscan(List *qptlist,
--- 3367,3373 ----
  				  List *qpqual,
  				  Index scanrelid,
  				  Node *funcexpr,
+ 				  bool ordinality,
  				  List *funccolnames,
  				  List *funccoltypes,
  				  List *funccoltypmods,
***************
*** 3381,3386 **** make_functionscan(List *qptlist,
--- 3383,3389 ----
  	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/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 4497,4502 **** inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
--- 4497,4506 ----
  	 */
  	check_stack_depth();
  
+ 	/* Fail if the caller wanted ORDINALITY - we don't implement that here. */
+ 	if (rte->ordinality)
+ 		return NULL;
+ 
  	/* Fail if FROM item isn't a simple FuncExpr */
  	fexpr = (FuncExpr *) rte->funcexpr;
  	if (fexpr == NULL || !IsA(fexpr, FuncExpr))
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 563,569 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  	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
--- 563,569 ----
  	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
***************
*** 592,598 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  	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
--- 592,598 ----
  	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
***************
*** 9570,9589 **** table_ref:	relation_expr opt_alias_clause
--- 9570,9611 ----
  				{
  					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);
***************
*** 12794,12799 **** unreserved_keyword:
--- 12816,12822 ----
  			| OPERATOR
  			| OPTION
  			| OPTIONS
+ 			| ORDINALITY
  			| OWNED
  			| OWNER
  			| PARSER
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
***************
*** 797,803 **** 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;
--- 797,803 ----
   * physical column numbers.
   */
  static void
! buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality)
  {
  	int			maxattrs = tupdesc->natts;
  	ListCell   *aliaslc;
***************
*** 849,854 **** buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
--- 849,873 ----
  		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,
***************
*** 871,913 **** 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));
  }
  
  /*
--- 890,931 ----
   */
  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;
  }
  
  /*
***************
*** 1003,1009 **** 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
--- 1021,1027 ----
  	 * 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
***************
*** 1063,1069 **** 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.
--- 1081,1087 ----
  	 * and/or actual column names.
  	 */
  	rte->eref = makeAlias(refname, NIL);
! 	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
  
  	/*
  	 * Set flags and access permissions.
***************
*** 1234,1250 **** 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.
--- 1252,1274 ----
  		/* 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.
***************
*** 1287,1292 **** addRangeTableEntryForFunction(ParseState *pstate,
--- 1311,1317 ----
  	 * permissions mechanism).
  	 */
  	rte->lateral = lateral;
+ 	rte->ordinality = rangefunc->ordinality;
  	rte->inh = false;			/* never true for functions */
  	rte->inFromCl = inFromCl;
  
***************
*** 1710,1715 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1735,1741 ----
  				TypeFuncClass functypclass;
  				Oid			funcrettype;
  				TupleDesc	tupdesc;
+ 				int         ordattno = 0;
  
  				functypclass = get_expr_result_type(rte->funcexpr,
  													&funcrettype,
***************
*** 1721,1726 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1747,1753 ----
  					expandTupleDesc(tupdesc, rte->eref,
  									rtindex, sublevels_up, location,
  									include_dropped, colnames, colvars);
+ 					ordattno = tupdesc->natts + 1;
  				}
  				else if (functypclass == TYPEFUNC_SCALAR)
  				{
***************
*** 1741,1746 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1768,1775 ----
  
  						*colvars = lappend(*colvars, varnode);
  					}
+ 
+ 					ordattno = 2;
  				}
  				else if (functypclass == TYPEFUNC_RECORD)
  				{
***************
*** 1779,1784 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1808,1831 ----
  					/* 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:
***************
*** 2173,2208 **** get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
  					Form_pg_attribute att_tup;
  
  					Assert(tupdesc);
- 					/* this is probably a can't-happen case */
- 					if (attnum < 1 || attnum > tupdesc->natts)
- 						ereport(ERROR,
- 								(errcode(ERRCODE_UNDEFINED_COLUMN),
- 						errmsg("column %d of relation \"%s\" does not exist",
- 							   attnum,
- 							   rte->eref->aliasname)));
  
! 					att_tup = tupdesc->attrs[attnum - 1];
  
! 					/*
! 					 * If dropped column, pretend it ain't there.  See notes
! 					 * in scanRTEForColumn.
! 					 */
! 					if (att_tup->attisdropped)
! 						ereport(ERROR,
! 								(errcode(ERRCODE_UNDEFINED_COLUMN),
! 								 errmsg("column \"%s\" of relation \"%s\" does not exist",
! 										NameStr(att_tup->attname),
! 										rte->eref->aliasname)));
! 					*vartype = att_tup->atttypid;
! 					*vartypmod = att_tup->atttypmod;
! 					*varcollid = att_tup->attcollation;
  				}
  				else if (functypclass == TYPEFUNC_SCALAR)
  				{
! 					/* Base data type, i.e. scalar */
! 					*vartype = funcrettype;
! 					*vartypmod = -1;
! 					*varcollid = exprCollation(rte->funcexpr);
  				}
  				else if (functypclass == TYPEFUNC_RECORD)
  				{
--- 2220,2276 ----
  					Form_pg_attribute att_tup;
  
  					Assert(tupdesc);
  
! 					if (rte->ordinality && attnum == (tupdesc->natts + 1))
! 					{
! 						*vartype = INT8OID;
! 						*vartypmod = -1;
! 						*varcollid = InvalidOid;
! 					}
! 					else
! 					{
! 						/* this is probably a can't-happen case */
! 						if (attnum < 1 || attnum > tupdesc->natts)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_UNDEFINED_COLUMN),
! 									 errmsg("column %d of relation \"%s\" does not exist",
! 											attnum,
! 											rte->eref->aliasname)));
  
! 						att_tup = tupdesc->attrs[attnum - 1];
! 
! 						/*
! 						 * If dropped column, pretend it ain't there.  See notes
! 						 * in scanRTEForColumn.
! 						 */
! 						if (att_tup->attisdropped)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_UNDEFINED_COLUMN),
! 									 errmsg("column \"%s\" of relation \"%s\" does not exist",
! 											NameStr(att_tup->attname),
! 											rte->eref->aliasname)));
! 						*vartype = att_tup->atttypid;
! 						*vartypmod = att_tup->atttypmod;
! 						*varcollid = att_tup->attcollation;
! 					}
  				}
  				else if (functypclass == TYPEFUNC_SCALAR)
  				{
! 					if (rte->ordinality && attnum == 2)
! 					{
! 						*vartype = INT8OID;
! 						*vartypmod = -1;
! 						*varcollid = InvalidOid;
! 					} 
! 					else
! 					{
! 						Assert(attnum == 1);
! 
! 						/* Base data type, i.e. scalar */
! 						*vartype = funcrettype;
! 						*vartypmod = -1;
! 						*varcollid = exprCollation(rte->funcexpr);
! 					}
  				}
  				else if (functypclass == TYPEFUNC_RECORD)
  				{
***************
*** 2322,2328 **** get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
  				Oid			funcrettype = exprType(rte->funcexpr);
  				Oid			funcrelid = typeidTypeRelid(funcrettype);
  
! 				if (OidIsValid(funcrelid))
  				{
  					/*
  					 * Composite data type, i.e. a table's row type
--- 2390,2400 ----
  				Oid			funcrettype = exprType(rte->funcexpr);
  				Oid			funcrelid = typeidTypeRelid(funcrettype);
  
! 				if (rte->ordinality && attnum == list_length(rte->eref->colnames))
! 				{
! 					result = false;
! 				}
! 				else if (OidIsValid(funcrelid))
  				{
  					/*
  					 * Composite data type, i.e. a table's row type
*** 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
***************
*** 7972,7977 **** get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
--- 7972,7979 ----
  			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
***************
*** 1398,1404 **** typedef struct FunctionScanState
  {
  	ScanState	ss;				/* its first field is NodeTag */
  	int			eflags;
! 	TupleDesc	tupdesc;
  	Tuplestorestate *tuplestorestate;
  	ExprState  *funcexpr;
  } FunctionScanState;
--- 1398,1407 ----
  {
  	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
***************
*** 769,774 **** typedef struct RangeTblEntry
--- 770,776 ----
  	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
***************
*** 424,429 **** typedef struct FunctionScan
--- 424,430 ----
  {
  	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
***************
*** 268,273 **** PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD)
--- 268,274 ----
  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/test/regress/expected/rangefuncs.out
--- b/src/test/regress/expected/rangefuncs.out
***************
*** 18,24 **** CREATE TABLE foo2(fooid int, f2 int);
  INSERT INTO foo2 VALUES(1, 11);
  INSERT INTO foo2 VALUES(2, 22);
  INSERT INTO foo2 VALUES(1, 111);
! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1;' LANGUAGE SQL;
  -- function with implicit LATERAL
  select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
   fooid | f2  | fooid | f2  
--- 18,92 ----
  INSERT INTO foo2 VALUES(1, 11);
  INSERT INTO foo2 VALUES(2, 22);
  INSERT INTO foo2 VALUES(1, 111);
! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1 ORDER BY f2;' LANGUAGE SQL;
! -- function with ORDINALITY
! select * from foot(1) with ordinality as z(a,b,ord);
!  a |  b  | ord 
! ---+-----+-----
!  1 |  11 |   1
!  1 | 111 |   2
! (2 rows)
! 
! select * from foot(1) with ordinality as z(a,b,ord) where b > 100;   -- ordinal 2, not 1
!  a |  b  | ord 
! ---+-----+-----
!  1 | 111 |   2
! (1 row)
! 
! -- ordinality vs. column names and types
! select a,b,ord from foot(1) with ordinality as z(a,b,ord);
!  a |  b  | ord 
! ---+-----+-----
!  1 |  11 |   1
!  1 | 111 |   2
! (2 rows)
! 
! select a,ord from unnest(array['a','b']) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  a |   1
!  b |   2
! (2 rows)
! 
! select * from unnest(array['a','b']) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  a |   1
!  b |   2
! (2 rows)
! 
! select a,ord from unnest(array[1.0::float8]) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  1 |   1
! (1 row)
! 
! select * from unnest(array[1.0::float8]) with ordinality as z(a,ord);
!  a | ord 
! ---+-----
!  1 |   1
! (1 row)
! 
! -- ordinality vs. views
! create temporary view vw_ord as select * from (values (1)) v(n) join foot(1) with ordinality as z(a,b,ord) on (n=ord);
! select * from vw_ord;
!  n | a | b  | ord 
! ---+---+----+-----
!  1 | 1 | 11 |   1
! (1 row)
! 
! select definition from pg_views where viewname='vw_ord';
!                             definition                             
! -------------------------------------------------------------------
!   SELECT v.n,                                                     +
!      z.a,                                                         +
!      z.b,                                                         +
!      z.ord                                                        +
!     FROM (( VALUES (1)) v(n)                                      +
!     JOIN foot(1) WITH ORDINALITY z(a, b, ord) ON ((v.n = z.ord)));
! (1 row)
! 
! drop view vw_ord;
  -- function with implicit LATERAL
  select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
   fooid | f2  | fooid | f2  
***************
*** 28,33 **** select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
--- 96,110 ----
       1 | 111 |     1 | 111
  (3 rows)
  
+ -- function with implicit LATERAL and explicit ORDINALITY
+ select * from foo2, foot(foo2.fooid) with ordinality as z(fooid,f2,ord) where foo2.f2 = z.f2;
+  fooid | f2  | fooid | f2  | ord 
+ -------+-----+-------+-----+-----
+      1 |  11 |     1 |  11 |   1
+      2 |  22 |     2 |  22 |   1
+      1 | 111 |     1 | 111 |   2
+ (3 rows)
+ 
  -- function in subselect
  select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = foo2.fooid) ORDER BY 1,2;
   fooid | f2  
***************
*** 73,78 **** SELECT * FROM getfoo(1) AS t1;
--- 150,161 ----
    1
  (1 row)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+  v | o 
+ ---+---
+  1 | 1
+ (1 row)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   getfoo 
***************
*** 80,85 **** SELECT * FROM vw_getfoo;
--- 163,176 ----
        1
  (1 row)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY as t1(v,o);
+ SELECT * FROM vw_getfoo;
+  v | o 
+ ---+---
+  1 | 1
+ (1 row)
+ 
  -- sql, proretset = t, prorettype = b
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 91,96 **** SELECT * FROM getfoo(1) AS t1;
--- 182,194 ----
    1
  (2 rows)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+  v | o 
+ ---+---
+  1 | 1
+  1 | 2
+ (2 rows)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   getfoo 
***************
*** 99,104 **** SELECT * FROM vw_getfoo;
--- 197,211 ----
        1
  (2 rows)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
+  v | o 
+ ---+---
+  1 | 1
+  1 | 2
+ (2 rows)
+ 
  -- sql, proretset = t, prorettype = b
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 110,115 **** SELECT * FROM getfoo(1) AS t1;
--- 217,229 ----
   Ed
  (2 rows)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+   v  | o 
+ -----+---
+  Joe | 1
+  Ed  | 2
+ (2 rows)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   getfoo 
***************
*** 118,123 **** SELECT * FROM vw_getfoo;
--- 232,246 ----
   Ed
  (2 rows)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
+   v  | o 
+ -----+---
+  Joe | 1
+  Ed  | 2
+ (2 rows)
+ 
  -- sql, proretset = f, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 128,133 **** SELECT * FROM getfoo(1) AS t1;
--- 251,262 ----
       1 |        1 | Joe
  (1 row)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+ (1 row)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   fooid | foosubid | fooname 
***************
*** 135,140 **** SELECT * FROM vw_getfoo;
--- 264,277 ----
       1 |        1 | Joe
  (1 row)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+ (1 row)
+ 
  -- sql, proretset = t, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 146,151 **** SELECT * FROM getfoo(1) AS t1;
--- 283,295 ----
       1 |        2 | Ed
  (2 rows)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+  1 | 2 | Ed  | 2
+ (2 rows)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   fooid | foosubid | fooname 
***************
*** 154,159 **** SELECT * FROM vw_getfoo;
--- 298,313 ----
       1 |        2 | Ed
  (2 rows)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+  1 | 2 | Ed  | 2
+ (2 rows)
+ 
+ -- ordinality not supported for returns record yet
  -- sql, proretset = f, prorettype = record
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 202,207 **** SELECT * FROM getfoo(1) AS t1;
--- 356,367 ----
    1
  (1 row)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+  v | o 
+ ---+---
+  1 | 1
+ (1 row)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   getfoo 
***************
*** 209,214 **** SELECT * FROM vw_getfoo;
--- 369,382 ----
        1
  (1 row)
  
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
+  v | o 
+ ---+---
+  1 | 1
+ (1 row)
+ 
  -- plpgsql, proretset = f, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 219,224 **** SELECT * FROM getfoo(1) AS t1;
--- 387,398 ----
       1 |        1 | Joe
  (1 row)
  
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+ (1 row)
+ 
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
   fooid | foosubid | fooname 
***************
*** 227,407 **** SELECT * FROM vw_getfoo;
  (1 row)
  
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  DROP FUNCTION foot(int);
  DROP TABLE foo2;
  DROP TABLE foo;
  -- Rescan tests --
! CREATE TABLE foorescan (fooid int, foosubid int, fooname text, primary key(fooid,foosubid));
! INSERT INTO foorescan values(5000,1,'abc.5000.1');
! INSERT INTO foorescan values(5001,1,'abc.5001.1');
! INSERT INTO foorescan values(5002,1,'abc.5002.1');
! INSERT INTO foorescan values(5003,1,'abc.5003.1');
! INSERT INTO foorescan values(5004,1,'abc.5004.1');
! INSERT INTO foorescan values(5005,1,'abc.5005.1');
! INSERT INTO foorescan values(5006,1,'abc.5006.1');
! INSERT INTO foorescan values(5007,1,'abc.5007.1');
! INSERT INTO foorescan values(5008,1,'abc.5008.1');
! INSERT INTO foorescan values(5009,1,'abc.5009.1');
! INSERT INTO foorescan values(5000,2,'abc.5000.2');
! INSERT INTO foorescan values(5001,2,'abc.5001.2');
! INSERT INTO foorescan values(5002,2,'abc.5002.2');
! INSERT INTO foorescan values(5003,2,'abc.5003.2');
! INSERT INTO foorescan values(5004,2,'abc.5004.2');
! INSERT INTO foorescan values(5005,2,'abc.5005.2');
! INSERT INTO foorescan values(5006,2,'abc.5006.2');
! INSERT INTO foorescan values(5007,2,'abc.5007.2');
! INSERT INTO foorescan values(5008,2,'abc.5008.2');
! INSERT INTO foorescan values(5009,2,'abc.5009.2');
! INSERT INTO foorescan values(5000,3,'abc.5000.3');
! INSERT INTO foorescan values(5001,3,'abc.5001.3');
! INSERT INTO foorescan values(5002,3,'abc.5002.3');
! INSERT INTO foorescan values(5003,3,'abc.5003.3');
! INSERT INTO foorescan values(5004,3,'abc.5004.3');
! INSERT INTO foorescan values(5005,3,'abc.5005.3');
! INSERT INTO foorescan values(5006,3,'abc.5006.3');
! INSERT INTO foorescan values(5007,3,'abc.5007.3');
! INSERT INTO foorescan values(5008,3,'abc.5008.3');
! INSERT INTO foorescan values(5009,3,'abc.5009.3');
! INSERT INTO foorescan values(5000,4,'abc.5000.4');
! INSERT INTO foorescan values(5001,4,'abc.5001.4');
! INSERT INTO foorescan values(5002,4,'abc.5002.4');
! INSERT INTO foorescan values(5003,4,'abc.5003.4');
! INSERT INTO foorescan values(5004,4,'abc.5004.4');
! INSERT INTO foorescan values(5005,4,'abc.5005.4');
! INSERT INTO foorescan values(5006,4,'abc.5006.4');
! INSERT INTO foorescan values(5007,4,'abc.5007.4');
! INSERT INTO foorescan values(5008,4,'abc.5008.4');
! INSERT INTO foorescan values(5009,4,'abc.5009.4');
! INSERT INTO foorescan values(5000,5,'abc.5000.5');
! INSERT INTO foorescan values(5001,5,'abc.5001.5');
! INSERT INTO foorescan values(5002,5,'abc.5002.5');
! INSERT INTO foorescan values(5003,5,'abc.5003.5');
! INSERT INTO foorescan values(5004,5,'abc.5004.5');
! INSERT INTO foorescan values(5005,5,'abc.5005.5');
! INSERT INTO foorescan values(5006,5,'abc.5006.5');
! INSERT INTO foorescan values(5007,5,'abc.5007.5');
! INSERT INTO foorescan values(5008,5,'abc.5008.5');
! INSERT INTO foorescan values(5009,5,'abc.5009.5');
! CREATE FUNCTION foorescan(int,int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid >= $1 and fooid < $2 ;' LANGUAGE SQL;
! --invokes ExecReScanFunctionScan
! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;
!  fooid | foosubid |  fooname   
! -------+----------+------------
!   5002 |        1 | abc.5002.1
!   5002 |        2 | abc.5002.2
!   5002 |        3 | abc.5002.3
!   5002 |        4 | abc.5002.4
!   5002 |        5 | abc.5002.5
!   5003 |        1 | abc.5003.1
!   5003 |        2 | abc.5003.2
!   5003 |        3 | abc.5003.3
!   5003 |        4 | abc.5003.4
!   5003 |        5 | abc.5003.5
  (10 rows)
  
! CREATE VIEW vw_foorescan AS SELECT * FROM foorescan(5002,5004);
! --invokes ExecReScanFunctionScan
! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2;
!  fooid | foosubid |  fooname   
! -------+----------+------------
!   5002 |        1 | abc.5002.1
!   5002 |        2 | abc.5002.2
!   5002 |        3 | abc.5002.3
!   5002 |        4 | abc.5002.4
!   5002 |        5 | abc.5002.5
!   5003 |        1 | abc.5003.1
!   5003 |        2 | abc.5003.2
!   5003 |        3 | abc.5003.3
!   5003 |        4 | abc.5003.4
!   5003 |        5 | abc.5003.5
  (10 rows)
  
! CREATE TABLE barrescan (fooid int primary key);
! INSERT INTO barrescan values(5003);
! INSERT INTO barrescan values(5004);
! INSERT INTO barrescan values(5005);
! INSERT INTO barrescan values(5006);
! INSERT INTO barrescan values(5007);
! INSERT INTO barrescan values(5008);
! CREATE FUNCTION foorescan(int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid = $1;' LANGUAGE SQL;
! --invokes ExecReScanFunctionScan with chgParam != NULL
! SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2;
!  fooid | foosubid |  fooname   
! -------+----------+------------
!   5003 |        1 | abc.5003.1
!   5003 |        2 | abc.5003.2
!   5003 |        3 | abc.5003.3
!   5003 |        4 | abc.5003.4
!   5003 |        5 | abc.5003.5
!   5004 |        1 | abc.5004.1
!   5004 |        2 | abc.5004.2
!   5004 |        3 | abc.5004.3
!   5004 |        4 | abc.5004.4
!   5004 |        5 | abc.5004.5
!   5005 |        1 | abc.5005.1
!   5005 |        2 | abc.5005.2
!   5005 |        3 | abc.5005.3
!   5005 |        4 | abc.5005.4
!   5005 |        5 | abc.5005.5
!   5006 |        1 | abc.5006.1
!   5006 |        2 | abc.5006.2
!   5006 |        3 | abc.5006.3
!   5006 |        4 | abc.5006.4
!   5006 |        5 | abc.5006.5
!   5007 |        1 | abc.5007.1
!   5007 |        2 | abc.5007.2
!   5007 |        3 | abc.5007.3
!   5007 |        4 | abc.5007.4
!   5007 |        5 | abc.5007.5
!   5008 |        1 | abc.5008.1
!   5008 |        2 | abc.5008.2
!   5008 |        3 | abc.5008.3
!   5008 |        4 | abc.5008.4
!   5008 |        5 | abc.5008.5
! (30 rows)
! 
! SELECT b.fooid, max(f.foosubid) FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2;
!  fooid | max 
! -------+-----
!   5003 |   5
!   5004 |   5
!   5005 |   5
!   5006 |   5
!   5007 |   5
!   5008 |   5
  (6 rows)
  
! CREATE VIEW fooview1 AS SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2;
! SELECT * FROM fooview1 AS fv WHERE fv.fooid = 5004;
!  fooid | foosubid |  fooname   
! -------+----------+------------
!   5004 |        1 | abc.5004.1
!   5004 |        2 | abc.5004.2
!   5004 |        3 | abc.5004.3
!   5004 |        4 | abc.5004.4
!   5004 |        5 | abc.5004.5
! (5 rows)
! 
! CREATE VIEW fooview2 AS SELECT b.fooid, max(f.foosubid) AS maxsubid FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2;
! SELECT * FROM fooview2 AS fv WHERE fv.maxsubid = 5;
!  fooid | maxsubid 
! -------+----------
!   5003 |        5
!   5004 |        5
!   5005 |        5
!   5006 |        5
!   5007 |        5
!   5008 |        5
  (6 rows)
  
! DROP VIEW vw_foorescan;
! DROP VIEW fooview1;
! DROP VIEW fooview2;
! DROP FUNCTION foorescan(int,int);
! DROP FUNCTION foorescan(int);
! DROP TABLE foorescan;
! DROP TABLE barrescan;
  --
  -- Test cases involving OUT parameters
  --
--- 401,1022 ----
  (1 row)
  
  DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
+  a | b |  c  | o 
+ ---+---+-----+---
+  1 | 1 | Joe | 1
+ (1 row)
+ 
+ DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  DROP FUNCTION foot(int);
  DROP TABLE foo2;
  DROP TABLE foo;
  -- Rescan tests --
! CREATE TEMPORARY SEQUENCE foo_rescan_seq;
! CREATE TYPE foo_rescan_t AS (i integer, s bigint);
! CREATE FUNCTION foo_sql(int,int) RETURNS setof foo_rescan_t AS 'SELECT i, nextval(''foo_rescan_seq'') FROM generate_series($1,$2) i;' LANGUAGE SQL;
! -- plpgsql functions use materialize mode
! CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''foo_rescan_seq'')); end loop; end;' LANGUAGE plpgsql;
! --invokes ExecReScanFunctionScan - all these cases should materialize the function only once
! -- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function
! -- is on the inner path of a nestloop join
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100;
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 11 | 1
!  2 | 12 | 2
!  2 | 13 | 3
!  3 | 11 | 1
!  3 | 12 | 2
!  3 | 13 | 3
! (9 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 11 | 1 | 1
!  2 | 12 | 2 | 2
!  2 | 13 | 3 | 3
!  3 | 11 | 1 | 1
!  3 | 12 | 2 | 2
!  3 | 13 | 3 | 3
! (9 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100;
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 11 | 1
!  2 | 12 | 2
!  2 | 13 | 3
!  3 | 11 | 1
!  3 | 12 | 2
!  3 | 13 | 3
! (9 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 11 | 1 | 1
!  2 | 12 | 2 | 2
!  2 | 13 | 3 | 3
!  3 | 11 | 1 | 1
!  3 | 12 | 2 | 2
!  3 | 13 | 3 | 3
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100;
!  r | i  
! ---+----
!  1 | 11
!  1 | 12
!  1 | 13
!  2 | 11
!  2 | 12
!  2 | 13
!  3 | 11
!  3 | 12
!  3 | 13
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
!  r | i  | o 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 11 | 1
!  2 | 12 | 2
!  2 | 13 | 3
!  3 | 11 | 1
!  3 | 12 | 2
!  3 | 13 | 3
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) f(i) ON (r+i)<100;
!  r | i  
! ---+----
!  1 | 10
!  1 | 20
!  1 | 30
!  2 | 10
!  2 | 20
!  2 | 30
!  3 | 10
!  3 | 20
!  3 | 30
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
!  r | i  | o 
! ---+----+---
!  1 | 10 | 1
!  1 | 20 | 2
!  1 | 30 | 3
!  2 | 10 | 1
!  2 | 20 | 2
!  2 | 30 | 3
!  3 | 10 | 1
!  3 | 20 | 2
!  3 | 30 | 3
! (9 rows)
! 
! --invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 12 | 4
!  2 | 13 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 12 | 4 | 1
!  2 | 13 | 5 | 2
!  3 | 13 | 6 | 1
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  2 | 11 | 2
!  2 | 12 | 3
!  3 | 11 | 4
!  3 | 12 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  2 | 11 | 2 | 1
!  2 | 12 | 3 | 2
!  3 | 11 | 4 | 1
!  3 | 12 | 5 | 2
!  3 | 13 | 6 | 3
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2);
!  r1 | r2 | i  | s  
! ----+----+----+----
!  11 | 12 | 11 |  1
!  11 | 12 | 12 |  2
!  13 | 15 | 13 |  3
!  13 | 15 | 14 |  4
!  13 | 15 | 15 |  5
!  16 | 20 | 16 |  6
!  16 | 20 | 17 |  7
!  16 | 20 | 18 |  8
!  16 | 20 | 19 |  9
!  16 | 20 | 20 | 10
  (10 rows)
  
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORDINALITY AS f(i,s,o);
!  r1 | r2 | i  | s  | o 
! ----+----+----+----+---
!  11 | 12 | 11 |  1 | 1
!  11 | 12 | 12 |  2 | 2
!  13 | 15 | 13 |  3 | 1
!  13 | 15 | 14 |  4 | 2
!  13 | 15 | 15 |  5 | 3
!  16 | 20 | 16 |  6 | 1
!  16 | 20 | 17 |  7 | 2
!  16 | 20 | 18 |  8 | 3
!  16 | 20 | 19 |  9 | 4
!  16 | 20 | 20 | 10 | 5
  (10 rows)
  
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  2 | 12 | 4
!  2 | 13 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  1 | 12 | 2 | 2
!  1 | 13 | 3 | 3
!  2 | 12 | 4 | 1
!  2 | 13 | 5 | 2
!  3 | 13 | 6 | 1
  (6 rows)
  
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r);
!  r | i  | s 
! ---+----+---
!  1 | 11 | 1
!  2 | 11 | 2
!  2 | 12 | 3
!  3 | 11 | 4
!  3 | 12 | 5
!  3 | 13 | 6
! (6 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i,s,o);
!  r | i  | s | o 
! ---+----+---+---
!  1 | 11 | 1 | 1
!  2 | 11 | 2 | 1
!  2 | 12 | 3 | 2
!  3 | 11 | 4 | 1
!  3 | 12 | 5 | 2
!  3 | 13 | 6 | 3
  (6 rows)
  
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2);
!  r1 | r2 | i  | s  
! ----+----+----+----
!  11 | 12 | 11 |  1
!  11 | 12 | 12 |  2
!  13 | 15 | 13 |  3
!  13 | 15 | 14 |  4
!  13 | 15 | 15 |  5
!  16 | 20 | 16 |  6
!  16 | 20 | 17 |  7
!  16 | 20 | 18 |  8
!  16 | 20 | 19 |  9
!  16 | 20 | 20 | 10
! (10 rows)
! 
! SELECT setval('foo_rescan_seq',1,false);
!  setval 
! --------
!       1
! (1 row)
! 
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORDINALITY AS f(i,s,o);
!  r1 | r2 | i  | s  | o 
! ----+----+----+----+---
!  11 | 12 | 11 |  1 | 1
!  11 | 12 | 12 |  2 | 2
!  13 | 15 | 13 |  3 | 1
!  13 | 15 | 14 |  4 | 2
!  13 | 15 | 15 |  5 | 3
!  16 | 20 | 16 |  6 | 1
!  16 | 20 | 17 |  7 | 2
!  16 | 20 | 18 |  8 | 3
!  16 | 20 | 19 |  9 | 4
!  16 | 20 | 20 | 10 | 5
! (10 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i);
!  r | i  
! ---+----
!  1 | 11
!  1 | 12
!  1 | 13
!  1 | 14
!  1 | 15
!  1 | 16
!  1 | 17
!  1 | 18
!  1 | 19
!  2 | 12
!  2 | 13
!  2 | 14
!  2 | 15
!  2 | 16
!  2 | 17
!  2 | 18
!  3 | 13
!  3 | 14
!  3 | 15
!  3 | 16
!  3 | 17
! (21 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o);
!  r | i  | o 
! ---+----+---
!  1 | 11 | 1
!  1 | 12 | 2
!  1 | 13 | 3
!  1 | 14 | 4
!  1 | 15 | 5
!  1 | 16 | 6
!  1 | 17 | 7
!  1 | 18 | 8
!  1 | 19 | 9
!  2 | 12 | 1
!  2 | 13 | 2
!  2 | 14 | 3
!  2 | 15 | 4
!  2 | 16 | 5
!  2 | 17 | 6
!  2 | 18 | 7
!  3 | 13 | 1
!  3 | 14 | 2
!  3 | 15 | 3
!  3 | 16 | 4
!  3 | 17 | 5
! (21 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) f(i);
!  r | i  
! ---+----
!  1 | 10
!  1 | 20
!  1 | 30
!  2 | 20
!  2 | 40
!  2 | 60
!  3 | 30
!  3 | 60
!  3 | 90
! (9 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) WITH ORDINALITY AS f(i,o);
!  r | i  | o 
! ---+----+---
!  1 | 10 | 1
!  1 | 20 | 2
!  1 | 30 | 3
!  2 | 20 | 1
!  2 | 40 | 2
!  2 | 60 | 3
!  3 | 30 | 1
!  3 | 60 | 2
!  3 | 90 | 3
! (9 rows)
! 
! -- deep nesting
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(21,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i  
! ----+----+----+----
!   1 |  1 | 10 | 21
!   1 |  1 | 10 | 22
!   1 |  1 | 10 | 23
!   1 |  1 | 20 | 21
!   1 |  1 | 20 | 22
!   1 |  1 | 20 | 23
!   1 |  1 | 30 | 21
!   1 |  1 | 30 | 22
!   1 |  1 | 30 | 23
!   2 |  2 | 10 | 21
!   2 |  2 | 10 | 22
!   2 |  2 | 10 | 23
!   2 |  2 | 20 | 21
!   2 |  2 | 20 | 22
!   2 |  2 | 20 | 23
!   2 |  2 | 30 | 21
!   2 |  2 | 30 | 22
!   2 |  2 | 30 | 23
!   3 |  3 | 10 | 21
!   3 |  3 | 10 | 22
!   3 |  3 | 10 | 23
!   3 |  3 | 20 | 21
!   3 |  3 | 20 | 22
!   3 |  3 | 20 | 23
!   3 |  3 | 30 | 21
!   3 |  3 | 30 | 22
!   3 |  3 | 30 | 23
! (27 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(20+r1,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i  
! ----+----+----+----
!   1 |  1 | 10 | 21
!   1 |  1 | 10 | 22
!   1 |  1 | 10 | 23
!   1 |  1 | 20 | 21
!   1 |  1 | 20 | 22
!   1 |  1 | 20 | 23
!   1 |  1 | 30 | 21
!   1 |  1 | 30 | 22
!   1 |  1 | 30 | 23
!   2 |  2 | 10 | 22
!   2 |  2 | 10 | 23
!   2 |  2 | 20 | 22
!   2 |  2 | 20 | 23
!   2 |  2 | 30 | 22
!   2 |  2 | 30 | 23
!   3 |  3 | 10 | 23
!   3 |  3 | 20 | 23
!   3 |  3 | 30 | 23
! (18 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(r2,r2+3) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i  
! ----+----+----+----
!   1 |  1 | 10 | 10
!   1 |  1 | 10 | 11
!   1 |  1 | 10 | 12
!   1 |  1 | 10 | 13
!   1 |  1 | 20 | 20
!   1 |  1 | 20 | 21
!   1 |  1 | 20 | 22
!   1 |  1 | 20 | 23
!   1 |  1 | 30 | 30
!   1 |  1 | 30 | 31
!   1 |  1 | 30 | 32
!   1 |  1 | 30 | 33
!   2 |  2 | 10 | 10
!   2 |  2 | 10 | 11
!   2 |  2 | 10 | 12
!   2 |  2 | 10 | 13
!   2 |  2 | 20 | 20
!   2 |  2 | 20 | 21
!   2 |  2 | 20 | 22
!   2 |  2 | 20 | 23
!   2 |  2 | 30 | 30
!   2 |  2 | 30 | 31
!   2 |  2 | 30 | 32
!   2 |  2 | 30 | 33
!   3 |  3 | 10 | 10
!   3 |  3 | 10 | 11
!   3 |  3 | 10 | 12
!   3 |  3 | 10 | 13
!   3 |  3 | 20 | 20
!   3 |  3 | 20 | 21
!   3 |  3 | 20 | 22
!   3 |  3 | 20 | 23
!   3 |  3 | 30 | 30
!   3 |  3 | 30 | 31
!   3 |  3 | 30 | 32
!   3 |  3 | 30 | 33
! (36 rows)
! 
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(r1,2+r2/5) f(i) ON ((r2+i)<100) OFFSET 0) s1;
!  r1 | r1 | r2 | i 
! ----+----+----+---
!   1 |  1 | 10 | 1
!   1 |  1 | 10 | 2
!   1 |  1 | 10 | 3
!   1 |  1 | 10 | 4
!   1 |  1 | 20 | 1
!   1 |  1 | 20 | 2
!   1 |  1 | 20 | 3
!   1 |  1 | 20 | 4
!   1 |  1 | 20 | 5
!   1 |  1 | 20 | 6
!   1 |  1 | 30 | 1
!   1 |  1 | 30 | 2
!   1 |  1 | 30 | 3
!   1 |  1 | 30 | 4
!   1 |  1 | 30 | 5
!   1 |  1 | 30 | 6
!   1 |  1 | 30 | 7
!   1 |  1 | 30 | 8
!   2 |  2 | 10 | 2
!   2 |  2 | 10 | 3
!   2 |  2 | 10 | 4
!   2 |  2 | 20 | 2
!   2 |  2 | 20 | 3
!   2 |  2 | 20 | 4
!   2 |  2 | 20 | 5
!   2 |  2 | 20 | 6
!   2 |  2 | 30 | 2
!   2 |  2 | 30 | 3
!   2 |  2 | 30 | 4
!   2 |  2 | 30 | 5
!   2 |  2 | 30 | 6
!   2 |  2 | 30 | 7
!   2 |  2 | 30 | 8
!   3 |  3 | 10 | 3
!   3 |  3 | 10 | 4
!   3 |  3 | 20 | 3
!   3 |  3 | 20 | 4
!   3 |  3 | 20 | 5
!   3 |  3 | 20 | 6
!   3 |  3 | 30 | 3
!   3 |  3 | 30 | 4
!   3 |  3 | 30 | 5
!   3 |  3 | 30 | 6
!   3 |  3 | 30 | 7
!   3 |  3 | 30 | 8
! (45 rows)
! 
! DROP FUNCTION foo_sql(int,int);
! DROP FUNCTION foo_mat(int,int);
! DROP SEQUENCE foo_rescan_seq;
  --
  -- Test cases involving OUT parameters
  --
***************
*** 877,882 **** SELECT * FROM get_users();
--- 1492,1504 ----
   id2    | email2 | t
  (2 rows)
  
+ SELECT * FROM get_users() WITH ORDINALITY;   -- make sure ordinality copes
+  userid | email  | enabled | ?column? 
+ --------+--------+---------+----------
+  id     | email  | t       |        1
+  id2    | email2 | t       |        2
+ (2 rows)
+ 
  drop function get_first_user();
  drop function get_users();
  drop table users;
*** a/src/test/regress/sql/rangefuncs.sql
--- b/src/test/regress/sql/rangefuncs.sql
***************
*** 5,15 **** INSERT INTO foo2 VALUES(1, 11);
  INSERT INTO foo2 VALUES(2, 22);
  INSERT INTO foo2 VALUES(1, 111);
  
! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1;' LANGUAGE SQL;
  
  -- function with implicit LATERAL
  select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
  
  -- function in subselect
  select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = foo2.fooid) ORDER BY 1,2;
  
--- 5,33 ----
  INSERT INTO foo2 VALUES(2, 22);
  INSERT INTO foo2 VALUES(1, 111);
  
! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1 ORDER BY f2;' LANGUAGE SQL;
! 
! -- function with ORDINALITY
! select * from foot(1) with ordinality as z(a,b,ord);
! select * from foot(1) with ordinality as z(a,b,ord) where b > 100;   -- ordinal 2, not 1
! -- ordinality vs. column names and types
! select a,b,ord from foot(1) with ordinality as z(a,b,ord);
! select a,ord from unnest(array['a','b']) with ordinality as z(a,ord);
! select * from unnest(array['a','b']) with ordinality as z(a,ord);
! select a,ord from unnest(array[1.0::float8]) with ordinality as z(a,ord);
! select * from unnest(array[1.0::float8]) with ordinality as z(a,ord);
! -- ordinality vs. views
! create temporary view vw_ord as select * from (values (1)) v(n) join foot(1) with ordinality as z(a,b,ord) on (n=ord);
! select * from vw_ord;
! select definition from pg_views where viewname='vw_ord';
! drop view vw_ord;
  
  -- function with implicit LATERAL
  select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
  
+ -- function with implicit LATERAL and explicit ORDINALITY
+ select * from foo2, foot(foo2.fooid) with ordinality as z(fooid,f2,ord) where foo2.f2 = z.f2;
+ 
  -- function in subselect
  select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = foo2.fooid) ORDER BY 1,2;
  
***************
*** 30,70 **** INSERT INTO foo VALUES(2,1,'Mary');
--- 48,109 ----
  -- sql, proretset = f, prorettype = b
  CREATE FUNCTION getfoo(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY as t1(v,o);
+ SELECT * FROM vw_getfoo;
  
  -- sql, proretset = t, prorettype = b
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
  
  -- sql, proretset = t, prorettype = b
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
  
  -- sql, proretset = f, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
  
  -- sql, proretset = t, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
  
+ -- ordinality not supported for returns record yet
  -- sql, proretset = f, prorettype = record
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 88,103 **** DROP VIEW vw_getfoo;
--- 127,150 ----
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
+ SELECT * FROM vw_getfoo;
  
  -- plpgsql, proretset = f, prorettype = c
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
  CREATE FUNCTION getfoo(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql;
  SELECT * FROM getfoo(1) AS t1;
+ SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
  CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
  SELECT * FROM vw_getfoo;
+ DROP VIEW vw_getfoo;
+ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
+ SELECT * FROM vw_getfoo;
  
  DROP VIEW vw_getfoo;
  DROP FUNCTION getfoo(int);
***************
*** 106,204 **** DROP TABLE foo2;
  DROP TABLE foo;
  
  -- Rescan tests --
! CREATE TABLE foorescan (fooid int, foosubid int, fooname text, primary key(fooid,foosubid));
! INSERT INTO foorescan values(5000,1,'abc.5000.1');
! INSERT INTO foorescan values(5001,1,'abc.5001.1');
! INSERT INTO foorescan values(5002,1,'abc.5002.1');
! INSERT INTO foorescan values(5003,1,'abc.5003.1');
! INSERT INTO foorescan values(5004,1,'abc.5004.1');
! INSERT INTO foorescan values(5005,1,'abc.5005.1');
! INSERT INTO foorescan values(5006,1,'abc.5006.1');
! INSERT INTO foorescan values(5007,1,'abc.5007.1');
! INSERT INTO foorescan values(5008,1,'abc.5008.1');
! INSERT INTO foorescan values(5009,1,'abc.5009.1');
  
! INSERT INTO foorescan values(5000,2,'abc.5000.2');
! INSERT INTO foorescan values(5001,2,'abc.5001.2');
! INSERT INTO foorescan values(5002,2,'abc.5002.2');
! INSERT INTO foorescan values(5003,2,'abc.5003.2');
! INSERT INTO foorescan values(5004,2,'abc.5004.2');
! INSERT INTO foorescan values(5005,2,'abc.5005.2');
! INSERT INTO foorescan values(5006,2,'abc.5006.2');
! INSERT INTO foorescan values(5007,2,'abc.5007.2');
! INSERT INTO foorescan values(5008,2,'abc.5008.2');
! INSERT INTO foorescan values(5009,2,'abc.5009.2');
  
! INSERT INTO foorescan values(5000,3,'abc.5000.3');
! INSERT INTO foorescan values(5001,3,'abc.5001.3');
! INSERT INTO foorescan values(5002,3,'abc.5002.3');
! INSERT INTO foorescan values(5003,3,'abc.5003.3');
! INSERT INTO foorescan values(5004,3,'abc.5004.3');
! INSERT INTO foorescan values(5005,3,'abc.5005.3');
! INSERT INTO foorescan values(5006,3,'abc.5006.3');
! INSERT INTO foorescan values(5007,3,'abc.5007.3');
! INSERT INTO foorescan values(5008,3,'abc.5008.3');
! INSERT INTO foorescan values(5009,3,'abc.5009.3');
  
! INSERT INTO foorescan values(5000,4,'abc.5000.4');
! INSERT INTO foorescan values(5001,4,'abc.5001.4');
! INSERT INTO foorescan values(5002,4,'abc.5002.4');
! INSERT INTO foorescan values(5003,4,'abc.5003.4');
! INSERT INTO foorescan values(5004,4,'abc.5004.4');
! INSERT INTO foorescan values(5005,4,'abc.5005.4');
! INSERT INTO foorescan values(5006,4,'abc.5006.4');
! INSERT INTO foorescan values(5007,4,'abc.5007.4');
! INSERT INTO foorescan values(5008,4,'abc.5008.4');
! INSERT INTO foorescan values(5009,4,'abc.5009.4');
  
! INSERT INTO foorescan values(5000,5,'abc.5000.5');
! INSERT INTO foorescan values(5001,5,'abc.5001.5');
! INSERT INTO foorescan values(5002,5,'abc.5002.5');
! INSERT INTO foorescan values(5003,5,'abc.5003.5');
! INSERT INTO foorescan values(5004,5,'abc.5004.5');
! INSERT INTO foorescan values(5005,5,'abc.5005.5');
! INSERT INTO foorescan values(5006,5,'abc.5006.5');
! INSERT INTO foorescan values(5007,5,'abc.5007.5');
! INSERT INTO foorescan values(5008,5,'abc.5008.5');
! INSERT INTO foorescan values(5009,5,'abc.5009.5');
  
! CREATE FUNCTION foorescan(int,int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid >= $1 and fooid < $2 ;' LANGUAGE SQL;
  
! --invokes ExecReScanFunctionScan
! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;
  
! CREATE VIEW vw_foorescan AS SELECT * FROM foorescan(5002,5004);
  
! --invokes ExecReScanFunctionScan
! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2;
  
! CREATE TABLE barrescan (fooid int primary key);
! INSERT INTO barrescan values(5003);
! INSERT INTO barrescan values(5004);
! INSERT INTO barrescan values(5005);
! INSERT INTO barrescan values(5006);
! INSERT INTO barrescan values(5007);
! INSERT INTO barrescan values(5008);
  
! CREATE FUNCTION foorescan(int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid = $1;' LANGUAGE SQL;
  
! --invokes ExecReScanFunctionScan with chgParam != NULL
! SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2;
! SELECT b.fooid, max(f.foosubid) FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2;
  
! CREATE VIEW fooview1 AS SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2;
! SELECT * FROM fooview1 AS fv WHERE fv.fooid = 5004;
  
! CREATE VIEW fooview2 AS SELECT b.fooid, max(f.foosubid) AS maxsubid FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2;
! SELECT * FROM fooview2 AS fv WHERE fv.maxsubid = 5;
  
! DROP VIEW vw_foorescan;
! DROP VIEW fooview1;
! DROP VIEW fooview2;
! DROP FUNCTION foorescan(int,int);
! DROP FUNCTION foorescan(int);
! DROP TABLE foorescan;
! DROP TABLE barrescan;
  
  --
  -- Test cases involving OUT parameters
--- 153,237 ----
  DROP TABLE foo;
  
  -- Rescan tests --
! CREATE TEMPORARY SEQUENCE foo_rescan_seq;
! CREATE TYPE foo_rescan_t AS (i integer, s bigint);
  
! CREATE FUNCTION foo_sql(int,int) RETURNS setof foo_rescan_t AS 'SELECT i, nextval(''foo_rescan_seq'') FROM generate_series($1,$2) i;' LANGUAGE SQL;
! -- plpgsql functions use materialize mode
! CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''foo_rescan_seq'')); end loop; end;' LANGUAGE plpgsql;
  
! --invokes ExecReScanFunctionScan - all these cases should materialize the function only once
! -- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function
! -- is on the inner path of a nestloop join
  
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100;
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
  
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100;
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100;
  
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100;
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
  
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) f(i) ON (r+i)<100;
! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH ORDINALITY AS f(i,o) ON (r+i)<100;
  
! --invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
  
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i,s,o);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i,s,o);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORDINALITY AS f(i,s,o);
  
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i,s,o);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2);
! SELECT setval('foo_rescan_seq',1,false);
! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORDINALITY AS f(i,s,o);
  
! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o);
  
! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) f(i);
! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) WITH ORDINALITY AS f(i,o);
  
! -- deep nesting
  
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(21,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(20+r1,23) f(i) ON ((r2+i)<100) OFFSET 0) s1;
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(r2,r2+3) f(i) ON ((r2+i)<100) OFFSET 0) s1;
! SELECT * FROM (VALUES (1),(2),(3)) v1(r1),
!               LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2)
!                                          LEFT JOIN generate_series(r1,2+r2/5) f(i) ON ((r2+i)<100) OFFSET 0) s1;
  
! DROP FUNCTION foo_sql(int,int);
! DROP FUNCTION foo_mat(int,int);
! DROP SEQUENCE foo_rescan_seq;
  
  --
  -- Test cases involving OUT parameters
***************
*** 414,419 **** language sql stable;
--- 447,453 ----
  
  SELECT get_users();
  SELECT * FROM get_users();
+ SELECT * FROM get_users() WITH ORDINALITY;   -- make sure ordinality copes
  
  drop function get_first_user();
  drop function get_users();