Review: UNNEST (and other functions) WITH ORDINALITY

Started by Dean Rasheedover 12 years ago68 messages
#1Dean Rasheed
dean.a.rasheed@gmail.com

On 17 June 2013 06:33, David Fetter <david@fetter.org> wrote:

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

Rebased vs. git master.

Here's my review of the WITH ORDINALITY patch.

Overall I think that the patch is in good shape, and I think that this
will be a very useful new feature, so I'm keen to see this committed.

All the basic stuff is OK --- the patch applies cleanly, compiles with
no warnings, and has appropriate docs and new regression tests which
pass. I also tested it fairly thoroughly myself, and I couldn't break
it. Everything worked as I expected, and it works nicely with LATERAL.

I have a few minor comments that should be considered before passing
it on to a committer:

1). In func.sgml, the new doc example "unnest WITH ORDINALITY" is
mislablled, since it it's not actually an example of unnest(). Also it
doesn't really belong in that code example block, which is about
generate_subscripts(). I think that there should probably be a new
sub-section starting at that point for WITH ORDINALITY. Perhaps it
should start with a brief paragraph explaining how WITH ORDINALITY can
be applied to functions in the FROM clause of a query.

[Actually it appears that WITH ORDINALITY works with non-SRF's too,
but that's less useful, so I think that the SRF section is probably
still the right place to document this]

It might also be worth mentioning here that currently WITH ORDINALITY
is not supported for functions that return records.

In the code example itself, the prompt should be trimmed down to match
the previous examples.

2). In the SELECT docs, where function_name is documented, I would be
tempted to start a new paragraph for the sentence starting "If the
function has been defined as returning the record data type...", since
that's really a separate syntax. I think that should also make mention
of the fact that WITH ORDINALITY is not currently supported in that
case.

3). I think it would be good to have a more meaningful default name
for the new ordinality column, rather than "?column?". In many cases
the user might then choose to not bother giving it an alias. It could
simply be called ordinality by default, since that's non-reserved.

4). In gram.y, WITH_ORDINALITY should not be listed as an ordinary
keyword, but instead should be listed as a token below that (just
before WITH_TIME).

5). In plannodes.h, FunctionScan's new field should probably have a comment.

6). In parsenodes.h, the field added to RangeTblEntry is only valid
for function RTEs, so it should be moved to that group of fields and
renamed appropriately (unless you're expecting to extend it to other
RTE kinds in the future?). Logically then, the new check for
ordinality in inline_set_returning_function() should be moved so that
it is after the check that the RTE actually a function RTE, and in
addRangeTableEntryForFunction() the RTE's ordinality field should be
set at the start along with the other function-related fields.

7). In execnodes.h, the new fields added to FunctionScanState should
be documented in the comment block above.

Overall, as I said, I really like this feature, and I think it's not
far from being commitable.

Regards,
Dean

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

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

On Tue, Jun 18, 2013 at 11:36:08AM +0100, Dean Rasheed wrote:

On 17 June 2013 06:33, David Fetter <david@fetter.org> wrote:

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

Rebased vs. git master.

Here's my review of the WITH ORDINALITY patch.

Overall I think that the patch is in good shape, and I think that this
will be a very useful new feature, so I'm keen to see this committed.

All the basic stuff is OK --- the patch applies cleanly, compiles with
no warnings, and has appropriate docs and new regression tests which
pass. I also tested it fairly thoroughly myself, and I couldn't break
it. Everything worked as I expected, and it works nicely with LATERAL.

I have a few minor comments that should be considered before passing
it on to a committer:

1). In func.sgml, the new doc example "unnest WITH ORDINALITY" is
mislablled, since it it's not actually an example of unnest().

Fixed in patch attached.

Also it doesn't really belong in that code example block, which is
about generate_subscripts(). I think that there should probably be a
new sub-section starting at that point for WITH ORDINALITY. Perhaps
it should start with a brief paragraph explaining how WITH
ORDINALITY can be applied to functions in the FROM clause of a
query.

How's the attached?

[Actually it appears that WITH ORDINALITY works with non-SRF's too,
but that's less useful, so I think that the SRF section is probably
still the right place to document this]

As of this patch, that's now both in the SELECT docs and the SRF
section.

It might also be worth mentioning here that currently WITH ORDINALITY
is not supported for functions that return records.

Added.

In the code example itself, the prompt should be trimmed down to match
the previous examples.

Done.

2). In the SELECT docs, where function_name is documented, I would be
tempted to start a new paragraph for the sentence starting "If the
function has been defined as returning the record data type...", since
that's really a separate syntax. I think that should also make mention
of the fact that WITH ORDINALITY is not currently supported in that
case.

Done-ish. What do you think?

3). I think it would be good to have a more meaningful default name
for the new ordinality column, rather than "?column?". In many cases
the user might then choose to not bother giving it an alias. It could
simply be called ordinality by default, since that's non-reserved.

I don't think this needs doing, per spec. The column name needs to be
unique, and if someone happens to name an output column of a function,
"?column?", that's really not our problem.

4). In gram.y, WITH_ORDINALITY should not be listed as an ordinary
keyword, but instead should be listed as a token below that (just
before WITH_TIME).

Done.

5). In plannodes.h, FunctionScan's new field should probably have a comment.

Done.

6). In parsenodes.h, the field added to RangeTblEntry is only valid
for function RTEs, so it should be moved to that group of fields and
renamed appropriately (unless you're expecting to extend it to other
RTE kinds in the future?).

Nope, and done.

Logically then, the new check for ordinality in
inline_set_returning_function() should be moved so that it is after
the check that the RTE actually a function RTE, and in
addRangeTableEntryForFunction() the RTE's ordinality field should be
set at the start along with the other function-related fields.

We don't actually get to inline_set_returning_function unless it's a
function RTE.

7). In execnodes.h, the new fields added to FunctionScanState should
be documented in the comment block above.

Done.

Overall, as I said, I really like this feature, and I think it's not
far from being commitable.

Great! :)

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_08.difftext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4c5af4b..789c3e7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13276,6 +13276,48 @@ postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
 </programlisting>
   </para>
 
+  <indexterm>
+   <primary>ordinality</primary>
+  </indexterm>
+
+  <para>
+  When a function in the <literal>FROM</literal> clause is suffixed by
+  <literal>WITH ORDINALITY</literal>, an integer column is appended to
+  the output which starts from 1 and increments by 1 for each row of
+  the function's output.  This is more useful in the case of
+  set-returning functions than of others.  This functionality is
+  available for functions returning composite types or using
+  <literal>OUT</literal> parameters, but not when using a function
+  returning <literal>RECORD</literal> with an explicit column definition list.
+
+<programlisting>
+-- SRF WITH ORDINALITY
+postgres=# 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>
+
  </sect1>
 
  <sect1 id="functions-info">
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 68309ba..58336f8 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -52,7 +52,8 @@ 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> [, ...] ] ) [ 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,20 +367,42 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
        <para>
         Function calls can appear in the <literal>FROM</literal>
         clause.  (This is especially useful for functions that return
-        result sets, but any function can be used.)  This acts as
+        result sets, but any function except those that return
+        <literal>[SETOF] RECORD</literal> 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
+        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, including the column added by <literal>ORDINALITY</literal> if present.
+      </para>
+
+      <para>
+        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.
+        class="parameter">data_type</replaceable> <optional>, ...
+        </>)</literal>.  The column definition list must match the
+        actual number and types of columns returned by the function.
+        <literal>ORDINALITY</literal> does not work in this case.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index fb5c199..3c6f5e3 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -158,6 +158,35 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 }
 
 /*
+ * 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).
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 24325fe..7f8ede7 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -25,7 +25,7 @@
 #include "executor/nodeFunctionscan.h"
 #include "funcapi.h"
 #include "nodes/nodeFuncs.h"
-
+#include "catalog/pg_type.h"
 
 static TupleTableSlot *FunctionNext(FunctionScanState *node);
 
@@ -42,10 +42,27 @@ static TupleTableSlot *FunctionNext(FunctionScanState *node);
 static TupleTableSlot *
 FunctionNext(FunctionScanState *node)
 {
-	TupleTableSlot *slot;
 	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,19 +81,52 @@ FunctionNext(FunctionScanState *node)
 		node->tuplestorestate = tuplestorestate =
 			ExecMakeTableFunctionResult(node->funcexpr,
 										node->ss.ps.ps_ExprContext,
-										node->tupdesc,
+										node->func_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;
+								   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,7 +166,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	FunctionScanState *scanstate;
 	Oid			funcrettype;
 	TypeFuncClass functypclass;
-	TupleDesc	tupdesc = NULL;
+	TupleDesc	func_tupdesc = NULL;
+	TupleDesc	scan_tupdesc = NULL;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & EXEC_FLAG_MARK));
@@ -148,6 +199,11 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	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,37 +220,48 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	 */
 	functypclass = get_expr_result_type(node->funcexpr,
 										&funcrettype,
-										&tupdesc);
+										&func_tupdesc);
 
 	if (functypclass == TYPEFUNC_COMPOSITE)
 	{
 		/* Composite data type, e.g. a table's row type */
-		Assert(tupdesc);
+		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 */
-		tupdesc = CreateTupleDescCopy(tupdesc);
+		func_tupdesc = CreateTupleDescCopy(func_tupdesc);
 	}
 	else if (functypclass == TYPEFUNC_SCALAR)
 	{
 		/* Base data type, i.e. scalar */
 		char	   *attname = strVal(linitial(node->funccolnames));
 
-		tupdesc = CreateTemplateTupleDesc(1, false);
-		TupleDescInitEntry(tupdesc,
+		func_tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(func_tupdesc,
 						   (AttrNumber) 1,
 						   attname,
 						   funcrettype,
 						   -1,
 						   0);
-		TupleDescInitEntryCollation(tupdesc,
+		TupleDescInitEntryCollation(func_tupdesc,
 									(AttrNumber) 1,
 									exprCollation(node->funcexpr));
 	}
 	else if (functypclass == TYPEFUNC_RECORD)
 	{
-		tupdesc = BuildDescFromLists(node->funccolnames,
-									 node->funccoltypes,
-									 node->funccoltypmods,
-									 node->funccolcollations);
+		func_tupdesc = BuildDescFromLists(node->funccolnames,
+										  node->funccoltypes,
+										  node->funccoltypmods,
+										  node->funccolcollations);
 	}
 	else
 	{
@@ -207,14 +274,36 @@ 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);
+	BlessTupleDesc(func_tupdesc);
 
-	scanstate->tupdesc = tupdesc;
-	ExecAssignScanType(&scanstate->ss, 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,6 +338,8 @@ ExecEndFunctionScan(FunctionScanState *node)
 	 */
 	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
+	if (node->func_slot)
+		ExecClearTuple(node->func_slot);
 
 	/*
 	 * Release tuplestore resources
@@ -268,9 +359,13 @@ void
 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.
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..944866d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -508,6 +508,7 @@ _copyFunctionScan(const FunctionScan *from)
 	COPY_NODE_FIELD(funccoltypes);
 	COPY_NODE_FIELD(funccoltypmods);
 	COPY_NODE_FIELD(funccolcollations);
+	COPY_SCALAR_FIELD(funcordinality);
 
 	return newnode;
 }
@@ -1980,6 +1981,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_NODE_FIELD(funccoltypes);
 	COPY_NODE_FIELD(funccoltypmods);
 	COPY_NODE_FIELD(funccolcollations);
+	COPY_SCALAR_FIELD(funcordinality);
 	COPY_NODE_FIELD(values_lists);
 	COPY_NODE_FIELD(values_collations);
 	COPY_STRING_FIELD(ctename);
@@ -2280,6 +2282,7 @@ _copyRangeFunction(const RangeFunction *from)
 {
 	RangeFunction *newnode = makeNode(RangeFunction);
 
+	COPY_SCALAR_FIELD(ordinality);
 	COPY_SCALAR_FIELD(lateral);
 	COPY_NODE_FIELD(funccallnode);
 	COPY_NODE_FIELD(alias);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..358c70a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2120,6 +2120,7 @@ _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
 static bool
 _equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
 {
+	COMPARE_SCALAR_FIELD(ordinality);
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_NODE_FIELD(funccallnode);
 	COMPARE_NODE_FIELD(alias);
@@ -2228,6 +2229,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_NODE_FIELD(funccoltypes);
 	COMPARE_NODE_FIELD(funccoltypmods);
 	COMPARE_NODE_FIELD(funccolcollations);
+	COMPARE_SCALAR_FIELD(funcordinality);
 	COMPARE_NODE_FIELD(values_lists);
 	COMPARE_NODE_FIELD(values_collations);
 	COMPARE_STRING_FIELD(ctename);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index c487db9..806e213 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -153,12 +153,12 @@ makeWholeRowVar(RangeTblEntry *rte,
 			break;
 		case RTE_FUNCTION:
 			toid = exprType(rte->funcexpr);
-			if (type_is_rowtype(toid))
+			if (type_is_rowtype(toid) || rte->funcordinality)
 			{
 				/* func returns composite; same as relation case */
 				result = makeVar(varno,
 								 InvalidAttrNumber,
-								 toid,
+								 (rte->funcordinality) ? RECORDOID : toid,
 								 -1,
 								 InvalidOid,
 								 varlevelsup);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..eeb3ba9 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -520,6 +520,7 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
 	WRITE_NODE_FIELD(funccoltypes);
 	WRITE_NODE_FIELD(funccoltypmods);
 	WRITE_NODE_FIELD(funccolcollations);
+	WRITE_BOOL_FIELD(funcordinality);
 }
 
 static void
@@ -2367,6 +2368,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_NODE_FIELD(funccoltypes);
 			WRITE_NODE_FIELD(funccoltypmods);
 			WRITE_NODE_FIELD(funccolcollations);
+			WRITE_BOOL_FIELD(funcordinality);
 			break;
 		case RTE_VALUES:
 			WRITE_NODE_FIELD(values_lists);
@@ -2599,6 +2601,7 @@ _outRangeFunction(StringInfo str, const RangeFunction *node)
 {
 	WRITE_NODE_TYPE("RANGEFUNCTION");
 
+	WRITE_BOOL_FIELD(ordinality);
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_NODE_FIELD(funccallnode);
 	WRITE_NODE_FIELD(alias);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..a61b977 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1205,6 +1205,7 @@ _readRangeTblEntry(void)
 			READ_NODE_FIELD(funccoltypes);
 			READ_NODE_FIELD(funccoltypmods);
 			READ_NODE_FIELD(funccolcollations);
+			READ_BOOL_FIELD(funcordinality);
 			break;
 		case RTE_VALUES:
 			READ_NODE_FIELD(values_lists);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 52bab79..2b0d2f2 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -115,8 +115,8 @@ 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,
+				  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,6 +1733,7 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
 
 	scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
 								  funcexpr,
+								  rte->funcordinality,
 								  rte->eref->colnames,
 								  rte->funccoltypes,
 								  rte->funccoltypmods,
@@ -3366,6 +3367,7 @@ make_functionscan(List *qptlist,
 				  List *qpqual,
 				  Index scanrelid,
 				  Node *funcexpr,
+				  bool ordinality,
 				  List *funccolnames,
 				  List *funccoltypes,
 				  List *funccoltypmods,
@@ -3381,6 +3383,7 @@ make_functionscan(List *qptlist,
 	plan->righttree = NULL;
 	node->scan.scanrelid = scanrelid;
 	node->funcexpr = funcexpr;
+	node->funcordinality = ordinality;
 	node->funccolnames = funccolnames;
 	node->funccoltypes = funccoltypes;
 	node->funccoltypmods = funccoltypmods;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 6d5b204..87501b0 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4497,10 +4497,15 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 	 */
 	check_stack_depth();
 
+	/* Fail if the caller wanted ORDINALITY - we don't implement that here. */
+	if (rte->funcordinality)
+		return NULL;
+
 	/* Fail if FROM item isn't a simple FuncExpr */
 	fexpr = (FuncExpr *) rte->funcexpr;
 	if (fexpr == NULL || !IsA(fexpr, FuncExpr))
 		return NULL;
+
 	func_oid = fexpr->funcid;
 
 	/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5094226..3acc8fe 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -563,7 +563,7 @@ 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
+	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
@@ -606,8 +606,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * list and so can never be entered directly.  The filter in parser.c
  * creates these tokens when required.
  */
-%token			NULLS_FIRST NULLS_LAST WITH_TIME
-
+%token			NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
 
 /* Precedence: lowest to highest */
 %nonassoc	SET				/* see relation_expr_opt_alias */
@@ -9570,20 +9569,42 @@ table_ref:	relation_expr opt_alias_clause
 				{
 					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,6 +12815,7 @@ unreserved_keyword:
 			| OPERATOR
 			| OPTION
 			| OPTIONS
+			| ORDINALITY
 			| OWNED
 			| OWNER
 			| PARSER
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a9254c8..9d0f3b6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -797,7 +797,7 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
  * physical column numbers.
  */
 static void
-buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
+buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality)
 {
 	int			maxattrs = tupdesc->natts;
 	ListCell   *aliaslc;
@@ -849,6 +849,25 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
 		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,43 +890,42 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
  */
 static void
 buildScalarFunctionAlias(Node *funcexpr, char *funcname,
-						 Alias *alias, Alias *eref)
+						 Alias *alias, Alias *eref, bool ordinality)
 {
-	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)
+		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);
-		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))
+	else
 	{
-		pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid);
-		if (pname)
-		{
-			eref->colnames = list_make1(makeString(pname));
-			return;
-		}
+		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));
 	}
 
-	/*
-	 * Otherwise use the previously-determined alias (not necessarily the
-	 * function name!)
-	 */
-	eref->colnames = list_make1(makeString(eref->aliasname));
+	if (ordinality && list_length(eref->colnames) < 2)
+		eref->colnames = lappend(eref->colnames, makeString(pstrdup("?column?")));
+
+	return;
 }
 
 /*
@@ -1003,7 +1021,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1063,7 +1081,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
 
 	/*
 	 * Set flags and access permissions.
@@ -1234,17 +1252,23 @@ addRangeTableEntryForFunction(ParseState *pstate,
 		/* Composite data type, e.g. a table's row type */
 		Assert(tupdesc);
 		/* Build the column alias list */
-		buildRelationAliases(tupdesc, alias, eref);
+		buildRelationAliases(tupdesc, alias, eref, rangefunc->ordinality);
 	}
 	else if (functypclass == TYPEFUNC_SCALAR)
 	{
 		/* Base data type, i.e. scalar */
-		buildScalarFunctionAlias(funcexpr, funcname, alias, eref);
+		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,6 +1311,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * permissions mechanism).
 	 */
 	rte->lateral = lateral;
+	rte->funcordinality = rangefunc->ordinality;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
@@ -1710,6 +1735,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 				TypeFuncClass functypclass;
 				Oid			funcrettype;
 				TupleDesc	tupdesc;
+				int         ordattno = 0;
 
 				functypclass = get_expr_result_type(rte->funcexpr,
 													&funcrettype,
@@ -1721,6 +1747,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					expandTupleDesc(tupdesc, rte->eref,
 									rtindex, sublevels_up, location,
 									include_dropped, colnames, colvars);
+					ordattno = tupdesc->natts + 1;
 				}
 				else if (functypclass == TYPEFUNC_SCALAR)
 				{
@@ -1741,6 +1768,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 
 						*colvars = lappend(*colvars, varnode);
 					}
+
+					ordattno = 2;
 				}
 				else if (functypclass == TYPEFUNC_RECORD)
 				{
@@ -1779,6 +1808,24 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					/* addRangeTableEntryForFunction should've caught this */
 					elog(ERROR, "function in FROM has unsupported return type");
 				}
+
+				/* tack on the extra ordinality column if present */
+				if (rte->funcordinality)
+				{
+					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,36 +2220,57 @@ 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 (rte->funcordinality && 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)));
 
-					/*
-					 * 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;
+						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);
+					if (rte->funcordinality && 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,7 +2390,11 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 				Oid			funcrettype = exprType(rte->funcexpr);
 				Oid			funcrelid = typeidTypeRelid(funcrettype);
 
-				if (OidIsValid(funcrelid))
+				if (rte->funcordinality && attnum == list_length(rte->eref->colnames))
+				{
+					result = false;
+				}
+				else if (OidIsValid(funcrelid))
 				{
 					/*
 					 * Composite data type, i.e. a table's row type
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index b8ec790..541d364 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -133,7 +133,7 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 		case WITH:
 
 			/*
-			 * WITH TIME must be reduced to one token
+			 * WITH TIME and WITH ORDINALITY must each be reduced to one token
 			 */
 			cur_yylval = lvalp->core_yystype;
 			cur_yylloc = *llocp;
@@ -143,6 +143,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 				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;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a1ed781..263d25d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7972,6 +7972,8 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			case RTE_FUNCTION:
 				/* Function RTE */
 				get_rule_expr(rte->funcexpr, context, true);
+				if (rte->funcordinality)
+					appendStringInfoString(buf, " WITH ORDINALITY");
 				break;
 			case RTE_VALUES:
 				/* Values list RTE */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 51c5575..49226b7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -87,6 +87,7 @@ extern TupleDesc CreateTupleDesc(int natts, bool hasoid,
 				Form_pg_attribute *attrs);
 
 extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
+extern TupleDesc CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts);
 
 extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..8061a39 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1398,7 +1398,10 @@ typedef struct FunctionScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
 	int			eflags;
-	TupleDesc	tupdesc;
+	int64       ordinal;        /* column for WITH ORDINALITY */
+	TupleDesc	scan_tupdesc;   /* Scan tuple descriptor */
+	TupleDesc	func_tupdesc;   /* Function tuple descriptor */
+	TupleTableSlot *func_slot;	/* Function slot */
 	Tuplestorestate *tuplestorestate;
 	ExprState  *funcexpr;
 } FunctionScanState;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6723647..7483530 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -463,6 +463,7 @@ typedef struct RangeFunction
 {
 	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
@@ -746,6 +747,7 @@ typedef struct RangeTblEntry
 	List	   *funccoltypes;	/* OID list of column type OIDs */
 	List	   *funccoltypmods; /* integer list of column typmods */
 	List	   *funccolcollations;		/* OID list of column collation OIDs */
+	bool		funcordinality;	/* is this called WITH ORDINALITY? */
 
 	/*
 	 * Fields valid for a values RTE (else NIL):
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 841701e..5341be6 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -424,6 +424,7 @@ typedef struct FunctionScan
 {
 	Scan		scan;
 	Node	   *funcexpr;		/* expression tree for func call */
+	bool        funcordinality; /* WITH ORDINALITY */
 	List	   *funccolnames;	/* output column names (string Value nodes) */
 	List	   *funccoltypes;	/* OID list of column type OIDs */
 	List	   *funccoltypmods; /* integer list of column typmods */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 68a13b7..c22c72f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -268,6 +268,7 @@ PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD)
 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)
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 1678277..377c00a 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -18,7 +18,75 @@ 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;
+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,6 +96,15 @@ select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
      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,6 +150,12 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +163,14 @@ SELECT * FROM vw_getfoo;
       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,6 +182,13 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +197,15 @@ SELECT * FROM vw_getfoo;
       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,6 +217,13 @@ SELECT * FROM getfoo(1) AS t1;
  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,6 +232,15 @@ SELECT * FROM vw_getfoo;
  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,6 +251,12 @@ SELECT * FROM getfoo(1) AS t1;
      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,6 +264,14 @@ SELECT * FROM vw_getfoo;
      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,6 +283,13 @@ SELECT * FROM getfoo(1) AS t1;
      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,6 +298,16 @@ SELECT * FROM vw_getfoo;
      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,6 +356,12 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +369,14 @@ SELECT * FROM vw_getfoo;
       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,6 +387,12 @@ SELECT * FROM getfoo(1) AS t1;
      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,181 +401,622 @@ SELECT * FROM vw_getfoo;
 (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 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
+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)
 
-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
+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)
 
-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
+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)
 
-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
+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)
 
-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;
+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,6 +1492,13 @@ SELECT * FROM get_users();
  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;
diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql
index f1a405a..003da51 100644
--- a/src/test/regress/sql/rangefuncs.sql
+++ b/src/test/regress/sql/rangefuncs.sql
@@ -5,11 +5,29 @@ 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;
+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,41 +48,62 @@ INSERT INTO foo VALUES(2,1,'Mary');
 -- 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,16 +127,24 @@ DROP VIEW vw_getfoo;
 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,99 +153,85 @@ 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');
+CREATE TEMPORARY SEQUENCE foo_rescan_seq;
+CREATE TYPE foo_rescan_t AS (i integer, s bigint);
 
-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');
+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;
 
-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');
+--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
 
-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');
+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;
 
-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');
+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;
 
-CREATE FUNCTION foorescan(int,int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid >= $1 and fooid < $2 ;' LANGUAGE SQL;
+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;
 
---invokes ExecReScanFunctionScan
-SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;
+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;
 
-CREATE VIEW vw_foorescan AS SELECT * FROM foorescan(5002,5004);
+--invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
 
---invokes ExecReScanFunctionScan
-SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2;
+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);
 
-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);
+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);
 
-CREATE FUNCTION foorescan(int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid = $1;' LANGUAGE SQL;
+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);
 
---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;
+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);
 
-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;
+-- deep nesting
 
-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;
+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 VIEW vw_foorescan;
-DROP VIEW fooview1;
-DROP VIEW fooview2;
-DROP FUNCTION foorescan(int,int);
-DROP FUNCTION foorescan(int);
-DROP TABLE foorescan;
-DROP TABLE barrescan;
+DROP FUNCTION foo_sql(int,int);
+DROP FUNCTION foo_mat(int,int);
+DROP SEQUENCE foo_rescan_seq;
 
 --
 -- Test cases involving OUT parameters
@@ -414,6 +447,7 @@ language sql stable;
 
 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();
#3Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: David Fetter (#2)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 19 June 2013 04:11, David Fetter <david@fetter.org> wrote:

On Tue, Jun 18, 2013 at 11:36:08AM +0100, Dean Rasheed wrote:

On 17 June 2013 06:33, David Fetter <david@fetter.org> wrote:

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

Rebased vs. git master.

Here's my review of the WITH ORDINALITY patch.

Overall I think that the patch is in good shape, and I think that this
will be a very useful new feature, so I'm keen to see this committed.

All the basic stuff is OK --- the patch applies cleanly, compiles with
no warnings, and has appropriate docs and new regression tests which
pass. I also tested it fairly thoroughly myself, and I couldn't break
it. Everything worked as I expected, and it works nicely with LATERAL.

I have a few minor comments that should be considered before passing
it on to a committer:

1). In func.sgml, the new doc example "unnest WITH ORDINALITY" is
mislablled, since it it's not actually an example of unnest().

Fixed in patch attached.

Also it doesn't really belong in that code example block, which is
about generate_subscripts(). I think that there should probably be a
new sub-section starting at that point for WITH ORDINALITY. Perhaps
it should start with a brief paragraph explaining how WITH
ORDINALITY can be applied to functions in the FROM clause of a
query.

How's the attached?

Looks good.

[Actually it appears that WITH ORDINALITY works with non-SRF's too,
but that's less useful, so I think that the SRF section is probably
still the right place to document this]

As of this patch, that's now both in the SELECT docs and the SRF
section.

It might also be worth mentioning here that currently WITH ORDINALITY
is not supported for functions that return records.

Added.

In the code example itself, the prompt should be trimmed down to match
the previous examples.

Done.

Oh, on closer inspection, the previous examples mostly don't show the
prompt at all, except for the last one. So perhaps it should be
removed from both those places.

2). In the SELECT docs, where function_name is documented, I would be
tempted to start a new paragraph for the sentence starting "If the
function has been defined as returning the record data type...", since
that's really a separate syntax. I think that should also make mention
of the fact that WITH ORDINALITY is not currently supported in that
case.

Done-ish. What do you think?

Hmm, I fear that might have made it worse, because now it reads as if
functions that return records can't be used in the FROM clause at all
(at least if you don't read all the way to the end, which many people
don't). I think if you just take out this change:

         Function calls can appear in the <literal>FROM</literal>
         clause.  (This is especially useful for functions that return
-        result sets, but any function can be used.)  This acts as
+        result sets, but any function except those that return
+        <literal>[SETOF] RECORD</literal> can be used.)  This acts as

then what's left is OK.

3). I think it would be good to have a more meaningful default name
for the new ordinality column, rather than "?column?". In many cases
the user might then choose to not bother giving it an alias. It could
simply be called ordinality by default, since that's non-reserved.

I don't think this needs doing, per spec. The column name needs to be
unique, and if someone happens to name an output column of a function,
"?column?", that's really not our problem.

I don't think the spec says anything about how the new column should
be named, so it's up to us, but I do think a sensible default would be
useful to save the user from having to give it an alias in many common
cases.

For example "SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS file"
would then produce a column that could be referred to in the rest of
the query as file.ordinality or simply ordinality. As it stands,
they'd have to write file."?column?", which is really ugly, so we're
effectively forcing them to supply an alias for this column every
time. I think it would be better if they only had to supply a name to
resolve name conflicts, or if they wanted a different name.

4). In gram.y, WITH_ORDINALITY should not be listed as an ordinary
keyword, but instead should be listed as a token below that (just
before WITH_TIME).

Done.

5). In plannodes.h, FunctionScan's new field should probably have a comment.

Done.

6). In parsenodes.h, the field added to RangeTblEntry is only valid
for function RTEs, so it should be moved to that group of fields and
renamed appropriately (unless you're expecting to extend it to other
RTE kinds in the future?).

Nope, and done.

Logically then, the new check for ordinality in
inline_set_returning_function() should be moved so that it is after
the check that the RTE actually a function RTE, and in
addRangeTableEntryForFunction() the RTE's ordinality field should be
set at the start along with the other function-related fields.

We don't actually get to inline_set_returning_function unless it's a
function RTE.

OK, yes you're right.

7). In execnodes.h, the new fields added to FunctionScanState should
be documented in the comment block above.

Done.

There's still a comment relating the old tupdesc field in the comment
block above. I think for consistency with the surrounding code, all
those comments should be in header comment block (except for the
NodeTag one).

Everything else looks good. I think we're now down to just a few minor
comment/doc issues, and the question of whether the new column should
have a better default name.

Regards,
Dean

--
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: Dean Rasheed (#3)
1 attachment(s)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Wed, Jun 19, 2013 at 01:03:42PM +0100, Dean Rasheed wrote:

On 19 June 2013 04:11, David Fetter <david@fetter.org> wrote:

On Tue, Jun 18, 2013 at 11:36:08AM +0100, Dean Rasheed wrote:

On 17 June 2013 06:33, David Fetter <david@fetter.org> wrote:

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

Rebased vs. git master.

Here's my review of the WITH ORDINALITY patch.

Overall I think that the patch is in good shape, and I think that this
will be a very useful new feature, so I'm keen to see this committed.

All the basic stuff is OK --- the patch applies cleanly, compiles with
no warnings, and has appropriate docs and new regression tests which
pass. I also tested it fairly thoroughly myself, and I couldn't break
it. Everything worked as I expected, and it works nicely with LATERAL.

I have a few minor comments that should be considered before passing
it on to a committer:

1). In func.sgml, the new doc example "unnest WITH ORDINALITY" is
mislablled, since it it's not actually an example of unnest().

Fixed in patch attached.

Also it doesn't really belong in that code example block, which is
about generate_subscripts(). I think that there should probably be a
new sub-section starting at that point for WITH ORDINALITY. Perhaps
it should start with a brief paragraph explaining how WITH
ORDINALITY can be applied to functions in the FROM clause of a
query.

How's the attached?

Looks good.

Thanks!

In the code example itself, the prompt should be trimmed down to match
the previous examples.

Done.

Oh, on closer inspection, the previous examples mostly don't show the
prompt at all, except for the last one. So perhaps it should be
removed from both those places.

Done.

2). In the SELECT docs, where function_name is documented, I would be
tempted to start a new paragraph for the sentence starting "If the
function has been defined as returning the record data type...", since
that's really a separate syntax. I think that should also make mention
of the fact that WITH ORDINALITY is not currently supported in that
case.

Done-ish. What do you think?

Hmm, I fear that might have made it worse, because now it reads as if
functions that return records can't be used in the FROM clause at all
(at least if you don't read all the way to the end, which many people
don't). I think if you just take out this change:

Function calls can appear in the <literal>FROM</literal>
clause.  (This is especially useful for functions that return
-        result sets, but any function can be used.)  This acts as
+        result sets, but any function except those that return
+        <literal>[SETOF] RECORD</literal> can be used.)  This acts as

then what's left is OK.

Done.

3). I think it would be good to have a more meaningful default name
for the new ordinality column, rather than "?column?". In many cases
the user might then choose to not bother giving it an alias. It could
simply be called ordinality by default, since that's non-reserved.

I don't think this needs doing, per spec. The column name needs to be
unique, and if someone happens to name an output column of a function,
"?column?", that's really not our problem.

I don't think the spec says anything about how the new column should
be named, so it's up to us, but I do think a sensible default would be
useful to save the user from having to give it an alias in many common
cases.

For example "SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS file"

The spec is pretty specific about the "all or none" nature of naming
in the AS clause...unless we can figure out a way of getting around it
somehow.

7). In execnodes.h, the new fields added to FunctionScanState should
be documented in the comment block above.

Done.

There's still a comment relating the old tupdesc field in the comment
block above. I think for consistency with the surrounding code, all
those comments should be in header comment block (except for the
NodeTag one).

Done.

Everything else looks good. I think we're now down to just a few minor
comment/doc issues, and the question of whether the new column should
have a better default name.

I'm weighing in on the side of a name that's like ?columnN? elsewhere
in the code, i.e. one that couldn't sanely be an actual column name,
whether in table, view, or SRF.

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_09.difftext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4c5af4b..422a56e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13265,7 +13265,7 @@ select $1[i][j]
         generate_subscripts($1,2) g2(j);
 $$ LANGUAGE sql IMMUTABLE;
 CREATE FUNCTION
-postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
+SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
  unnest2 
 ---------
        1
@@ -13276,6 +13276,48 @@ postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
 </programlisting>
   </para>
 
+  <indexterm>
+   <primary>ordinality</primary>
+  </indexterm>
+
+  <para>
+  When a function in the <literal>FROM</literal> clause is suffixed by
+  <literal>WITH ORDINALITY</literal>, an integer column is appended to
+  the output which starts from 1 and increments by 1 for each row of
+  the function's output.  This is more useful in the case of
+  set-returning functions than of others.  This functionality is
+  available for functions returning composite types or using
+  <literal>OUT</literal> parameters, but not when using a function
+  returning <literal>RECORD</literal> with an explicit column definition list.
+
+<programlisting>
+-- SRF WITH ORDINALITY
+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>
+
  </sect1>
 
  <sect1 id="functions-info">
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 68309ba..88c557e 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -52,7 +52,8 @@ 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> [, ...] ] ) [ 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,18 +369,39 @@ 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
+        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, including the column added by <literal>ORDINALITY</literal> if present.
+      </para>
+
+      <para>
+        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.
+        class="parameter">data_type</replaceable> <optional>, ...
+        </>)</literal>.  The column definition list must match the
+        actual number and types of columns returned by the function.
+        <literal>ORDINALITY</literal> does not work in this case.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index fb5c199..3c6f5e3 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -158,6 +158,35 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 }
 
 /*
+ * 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).
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 24325fe..7f8ede7 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -25,7 +25,7 @@
 #include "executor/nodeFunctionscan.h"
 #include "funcapi.h"
 #include "nodes/nodeFuncs.h"
-
+#include "catalog/pg_type.h"
 
 static TupleTableSlot *FunctionNext(FunctionScanState *node);
 
@@ -42,10 +42,27 @@ static TupleTableSlot *FunctionNext(FunctionScanState *node);
 static TupleTableSlot *
 FunctionNext(FunctionScanState *node)
 {
-	TupleTableSlot *slot;
 	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,19 +81,52 @@ FunctionNext(FunctionScanState *node)
 		node->tuplestorestate = tuplestorestate =
 			ExecMakeTableFunctionResult(node->funcexpr,
 										node->ss.ps.ps_ExprContext,
-										node->tupdesc,
+										node->func_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;
+								   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,7 +166,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	FunctionScanState *scanstate;
 	Oid			funcrettype;
 	TypeFuncClass functypclass;
-	TupleDesc	tupdesc = NULL;
+	TupleDesc	func_tupdesc = NULL;
+	TupleDesc	scan_tupdesc = NULL;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & EXEC_FLAG_MARK));
@@ -148,6 +199,11 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	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,37 +220,48 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	 */
 	functypclass = get_expr_result_type(node->funcexpr,
 										&funcrettype,
-										&tupdesc);
+										&func_tupdesc);
 
 	if (functypclass == TYPEFUNC_COMPOSITE)
 	{
 		/* Composite data type, e.g. a table's row type */
-		Assert(tupdesc);
+		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 */
-		tupdesc = CreateTupleDescCopy(tupdesc);
+		func_tupdesc = CreateTupleDescCopy(func_tupdesc);
 	}
 	else if (functypclass == TYPEFUNC_SCALAR)
 	{
 		/* Base data type, i.e. scalar */
 		char	   *attname = strVal(linitial(node->funccolnames));
 
-		tupdesc = CreateTemplateTupleDesc(1, false);
-		TupleDescInitEntry(tupdesc,
+		func_tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(func_tupdesc,
 						   (AttrNumber) 1,
 						   attname,
 						   funcrettype,
 						   -1,
 						   0);
-		TupleDescInitEntryCollation(tupdesc,
+		TupleDescInitEntryCollation(func_tupdesc,
 									(AttrNumber) 1,
 									exprCollation(node->funcexpr));
 	}
 	else if (functypclass == TYPEFUNC_RECORD)
 	{
-		tupdesc = BuildDescFromLists(node->funccolnames,
-									 node->funccoltypes,
-									 node->funccoltypmods,
-									 node->funccolcollations);
+		func_tupdesc = BuildDescFromLists(node->funccolnames,
+										  node->funccoltypes,
+										  node->funccoltypmods,
+										  node->funccolcollations);
 	}
 	else
 	{
@@ -207,14 +274,36 @@ 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);
+	BlessTupleDesc(func_tupdesc);
 
-	scanstate->tupdesc = tupdesc;
-	ExecAssignScanType(&scanstate->ss, 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,6 +338,8 @@ ExecEndFunctionScan(FunctionScanState *node)
 	 */
 	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
+	if (node->func_slot)
+		ExecClearTuple(node->func_slot);
 
 	/*
 	 * Release tuplestore resources
@@ -268,9 +359,13 @@ void
 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.
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..944866d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -508,6 +508,7 @@ _copyFunctionScan(const FunctionScan *from)
 	COPY_NODE_FIELD(funccoltypes);
 	COPY_NODE_FIELD(funccoltypmods);
 	COPY_NODE_FIELD(funccolcollations);
+	COPY_SCALAR_FIELD(funcordinality);
 
 	return newnode;
 }
@@ -1980,6 +1981,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_NODE_FIELD(funccoltypes);
 	COPY_NODE_FIELD(funccoltypmods);
 	COPY_NODE_FIELD(funccolcollations);
+	COPY_SCALAR_FIELD(funcordinality);
 	COPY_NODE_FIELD(values_lists);
 	COPY_NODE_FIELD(values_collations);
 	COPY_STRING_FIELD(ctename);
@@ -2280,6 +2282,7 @@ _copyRangeFunction(const RangeFunction *from)
 {
 	RangeFunction *newnode = makeNode(RangeFunction);
 
+	COPY_SCALAR_FIELD(ordinality);
 	COPY_SCALAR_FIELD(lateral);
 	COPY_NODE_FIELD(funccallnode);
 	COPY_NODE_FIELD(alias);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..358c70a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2120,6 +2120,7 @@ _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
 static bool
 _equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
 {
+	COMPARE_SCALAR_FIELD(ordinality);
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_NODE_FIELD(funccallnode);
 	COMPARE_NODE_FIELD(alias);
@@ -2228,6 +2229,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_NODE_FIELD(funccoltypes);
 	COMPARE_NODE_FIELD(funccoltypmods);
 	COMPARE_NODE_FIELD(funccolcollations);
+	COMPARE_SCALAR_FIELD(funcordinality);
 	COMPARE_NODE_FIELD(values_lists);
 	COMPARE_NODE_FIELD(values_collations);
 	COMPARE_STRING_FIELD(ctename);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index c487db9..806e213 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -153,12 +153,12 @@ makeWholeRowVar(RangeTblEntry *rte,
 			break;
 		case RTE_FUNCTION:
 			toid = exprType(rte->funcexpr);
-			if (type_is_rowtype(toid))
+			if (type_is_rowtype(toid) || rte->funcordinality)
 			{
 				/* func returns composite; same as relation case */
 				result = makeVar(varno,
 								 InvalidAttrNumber,
-								 toid,
+								 (rte->funcordinality) ? RECORDOID : toid,
 								 -1,
 								 InvalidOid,
 								 varlevelsup);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..eeb3ba9 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -520,6 +520,7 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
 	WRITE_NODE_FIELD(funccoltypes);
 	WRITE_NODE_FIELD(funccoltypmods);
 	WRITE_NODE_FIELD(funccolcollations);
+	WRITE_BOOL_FIELD(funcordinality);
 }
 
 static void
@@ -2367,6 +2368,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_NODE_FIELD(funccoltypes);
 			WRITE_NODE_FIELD(funccoltypmods);
 			WRITE_NODE_FIELD(funccolcollations);
+			WRITE_BOOL_FIELD(funcordinality);
 			break;
 		case RTE_VALUES:
 			WRITE_NODE_FIELD(values_lists);
@@ -2599,6 +2601,7 @@ _outRangeFunction(StringInfo str, const RangeFunction *node)
 {
 	WRITE_NODE_TYPE("RANGEFUNCTION");
 
+	WRITE_BOOL_FIELD(ordinality);
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_NODE_FIELD(funccallnode);
 	WRITE_NODE_FIELD(alias);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..a61b977 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1205,6 +1205,7 @@ _readRangeTblEntry(void)
 			READ_NODE_FIELD(funccoltypes);
 			READ_NODE_FIELD(funccoltypmods);
 			READ_NODE_FIELD(funccolcollations);
+			READ_BOOL_FIELD(funcordinality);
 			break;
 		case RTE_VALUES:
 			READ_NODE_FIELD(values_lists);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 52bab79..2b0d2f2 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -115,8 +115,8 @@ 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,
+				  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,6 +1733,7 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
 
 	scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
 								  funcexpr,
+								  rte->funcordinality,
 								  rte->eref->colnames,
 								  rte->funccoltypes,
 								  rte->funccoltypmods,
@@ -3366,6 +3367,7 @@ make_functionscan(List *qptlist,
 				  List *qpqual,
 				  Index scanrelid,
 				  Node *funcexpr,
+				  bool ordinality,
 				  List *funccolnames,
 				  List *funccoltypes,
 				  List *funccoltypmods,
@@ -3381,6 +3383,7 @@ make_functionscan(List *qptlist,
 	plan->righttree = NULL;
 	node->scan.scanrelid = scanrelid;
 	node->funcexpr = funcexpr;
+	node->funcordinality = ordinality;
 	node->funccolnames = funccolnames;
 	node->funccoltypes = funccoltypes;
 	node->funccoltypmods = funccoltypmods;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 6d5b204..87501b0 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4497,10 +4497,15 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 	 */
 	check_stack_depth();
 
+	/* Fail if the caller wanted ORDINALITY - we don't implement that here. */
+	if (rte->funcordinality)
+		return NULL;
+
 	/* Fail if FROM item isn't a simple FuncExpr */
 	fexpr = (FuncExpr *) rte->funcexpr;
 	if (fexpr == NULL || !IsA(fexpr, FuncExpr))
 		return NULL;
+
 	func_oid = fexpr->funcid;
 
 	/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5094226..3acc8fe 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -563,7 +563,7 @@ 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
+	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
@@ -606,8 +606,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * list and so can never be entered directly.  The filter in parser.c
  * creates these tokens when required.
  */
-%token			NULLS_FIRST NULLS_LAST WITH_TIME
-
+%token			NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
 
 /* Precedence: lowest to highest */
 %nonassoc	SET				/* see relation_expr_opt_alias */
@@ -9570,20 +9569,42 @@ table_ref:	relation_expr opt_alias_clause
 				{
 					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,6 +12815,7 @@ unreserved_keyword:
 			| OPERATOR
 			| OPTION
 			| OPTIONS
+			| ORDINALITY
 			| OWNED
 			| OWNER
 			| PARSER
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a9254c8..9d0f3b6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -797,7 +797,7 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
  * physical column numbers.
  */
 static void
-buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
+buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality)
 {
 	int			maxattrs = tupdesc->natts;
 	ListCell   *aliaslc;
@@ -849,6 +849,25 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
 		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,43 +890,42 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
  */
 static void
 buildScalarFunctionAlias(Node *funcexpr, char *funcname,
-						 Alias *alias, Alias *eref)
+						 Alias *alias, Alias *eref, bool ordinality)
 {
-	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)
+		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);
-		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))
+	else
 	{
-		pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid);
-		if (pname)
-		{
-			eref->colnames = list_make1(makeString(pname));
-			return;
-		}
+		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));
 	}
 
-	/*
-	 * Otherwise use the previously-determined alias (not necessarily the
-	 * function name!)
-	 */
-	eref->colnames = list_make1(makeString(eref->aliasname));
+	if (ordinality && list_length(eref->colnames) < 2)
+		eref->colnames = lappend(eref->colnames, makeString(pstrdup("?column?")));
+
+	return;
 }
 
 /*
@@ -1003,7 +1021,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1063,7 +1081,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
 
 	/*
 	 * Set flags and access permissions.
@@ -1234,17 +1252,23 @@ addRangeTableEntryForFunction(ParseState *pstate,
 		/* Composite data type, e.g. a table's row type */
 		Assert(tupdesc);
 		/* Build the column alias list */
-		buildRelationAliases(tupdesc, alias, eref);
+		buildRelationAliases(tupdesc, alias, eref, rangefunc->ordinality);
 	}
 	else if (functypclass == TYPEFUNC_SCALAR)
 	{
 		/* Base data type, i.e. scalar */
-		buildScalarFunctionAlias(funcexpr, funcname, alias, eref);
+		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,6 +1311,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * permissions mechanism).
 	 */
 	rte->lateral = lateral;
+	rte->funcordinality = rangefunc->ordinality;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
@@ -1710,6 +1735,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 				TypeFuncClass functypclass;
 				Oid			funcrettype;
 				TupleDesc	tupdesc;
+				int         ordattno = 0;
 
 				functypclass = get_expr_result_type(rte->funcexpr,
 													&funcrettype,
@@ -1721,6 +1747,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					expandTupleDesc(tupdesc, rte->eref,
 									rtindex, sublevels_up, location,
 									include_dropped, colnames, colvars);
+					ordattno = tupdesc->natts + 1;
 				}
 				else if (functypclass == TYPEFUNC_SCALAR)
 				{
@@ -1741,6 +1768,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 
 						*colvars = lappend(*colvars, varnode);
 					}
+
+					ordattno = 2;
 				}
 				else if (functypclass == TYPEFUNC_RECORD)
 				{
@@ -1779,6 +1808,24 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					/* addRangeTableEntryForFunction should've caught this */
 					elog(ERROR, "function in FROM has unsupported return type");
 				}
+
+				/* tack on the extra ordinality column if present */
+				if (rte->funcordinality)
+				{
+					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,36 +2220,57 @@ 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 (rte->funcordinality && 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)));
 
-					/*
-					 * 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;
+						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);
+					if (rte->funcordinality && 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,7 +2390,11 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 				Oid			funcrettype = exprType(rte->funcexpr);
 				Oid			funcrelid = typeidTypeRelid(funcrettype);
 
-				if (OidIsValid(funcrelid))
+				if (rte->funcordinality && attnum == list_length(rte->eref->colnames))
+				{
+					result = false;
+				}
+				else if (OidIsValid(funcrelid))
 				{
 					/*
 					 * Composite data type, i.e. a table's row type
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index b8ec790..541d364 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -133,7 +133,7 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 		case WITH:
 
 			/*
-			 * WITH TIME must be reduced to one token
+			 * WITH TIME and WITH ORDINALITY must each be reduced to one token
 			 */
 			cur_yylval = lvalp->core_yystype;
 			cur_yylloc = *llocp;
@@ -143,6 +143,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 				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;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a1ed781..263d25d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7972,6 +7972,8 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			case RTE_FUNCTION:
 				/* Function RTE */
 				get_rule_expr(rte->funcexpr, context, true);
+				if (rte->funcordinality)
+					appendStringInfoString(buf, " WITH ORDINALITY");
 				break;
 			case RTE_VALUES:
 				/* Values list RTE */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 51c5575..49226b7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -87,6 +87,7 @@ extern TupleDesc CreateTupleDesc(int natts, bool hasoid,
 				Form_pg_attribute *attrs);
 
 extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
+extern TupleDesc CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts);
 
 extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..6e9003a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1389,7 +1389,10 @@ typedef struct SubqueryScanState
  *		function appearing in FROM (typically a function returning set).
  *
  *		eflags				node's capability flags
- *		tupdesc				expected return tuple description
+ *		ordinal				column for WITH ORDINALITY
+ *		scan_tupdesc		scan tuple descriptor 
+ *		func_tupdesc		function tuple descriptor 
+ *		func_slot			function slot
  *		tuplestorestate		private state of tuplestore.c
  *		funcexpr			state for function expression being evaluated
  * ----------------
@@ -1398,7 +1401,10 @@ typedef struct FunctionScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
 	int			eflags;
-	TupleDesc	tupdesc;
+	int64       ordinal;
+	TupleDesc	scan_tupdesc;
+	TupleDesc	func_tupdesc;
+	TupleTableSlot *func_slot;
 	Tuplestorestate *tuplestorestate;
 	ExprState  *funcexpr;
 } FunctionScanState;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6723647..7483530 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -463,6 +463,7 @@ typedef struct RangeFunction
 {
 	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
@@ -746,6 +747,7 @@ typedef struct RangeTblEntry
 	List	   *funccoltypes;	/* OID list of column type OIDs */
 	List	   *funccoltypmods; /* integer list of column typmods */
 	List	   *funccolcollations;		/* OID list of column collation OIDs */
+	bool		funcordinality;	/* is this called WITH ORDINALITY? */
 
 	/*
 	 * Fields valid for a values RTE (else NIL):
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 841701e..5341be6 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -424,6 +424,7 @@ typedef struct FunctionScan
 {
 	Scan		scan;
 	Node	   *funcexpr;		/* expression tree for func call */
+	bool        funcordinality; /* WITH ORDINALITY */
 	List	   *funccolnames;	/* output column names (string Value nodes) */
 	List	   *funccoltypes;	/* OID list of column type OIDs */
 	List	   *funccoltypmods; /* integer list of column typmods */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 68a13b7..c22c72f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -268,6 +268,7 @@ PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD)
 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)
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 1678277..377c00a 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -18,7 +18,75 @@ 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;
+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,6 +96,15 @@ select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
      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,6 +150,12 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +163,14 @@ SELECT * FROM vw_getfoo;
       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,6 +182,13 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +197,15 @@ SELECT * FROM vw_getfoo;
       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,6 +217,13 @@ SELECT * FROM getfoo(1) AS t1;
  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,6 +232,15 @@ SELECT * FROM vw_getfoo;
  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,6 +251,12 @@ SELECT * FROM getfoo(1) AS t1;
      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,6 +264,14 @@ SELECT * FROM vw_getfoo;
      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,6 +283,13 @@ SELECT * FROM getfoo(1) AS t1;
      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,6 +298,16 @@ SELECT * FROM vw_getfoo;
      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,6 +356,12 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +369,14 @@ SELECT * FROM vw_getfoo;
       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,6 +387,12 @@ SELECT * FROM getfoo(1) AS t1;
      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,181 +401,622 @@ SELECT * FROM vw_getfoo;
 (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 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
+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)
 
-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
+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)
 
-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
+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)
 
-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
+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)
 
-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;
+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,6 +1492,13 @@ SELECT * FROM get_users();
  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;
diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql
index f1a405a..003da51 100644
--- a/src/test/regress/sql/rangefuncs.sql
+++ b/src/test/regress/sql/rangefuncs.sql
@@ -5,11 +5,29 @@ 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;
+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,41 +48,62 @@ INSERT INTO foo VALUES(2,1,'Mary');
 -- 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,16 +127,24 @@ DROP VIEW vw_getfoo;
 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,99 +153,85 @@ 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');
+CREATE TEMPORARY SEQUENCE foo_rescan_seq;
+CREATE TYPE foo_rescan_t AS (i integer, s bigint);
 
-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');
+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;
 
-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');
+--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
 
-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');
+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;
 
-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');
+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;
 
-CREATE FUNCTION foorescan(int,int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid >= $1 and fooid < $2 ;' LANGUAGE SQL;
+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;
 
---invokes ExecReScanFunctionScan
-SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;
+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;
 
-CREATE VIEW vw_foorescan AS SELECT * FROM foorescan(5002,5004);
+--invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
 
---invokes ExecReScanFunctionScan
-SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2;
+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);
 
-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);
+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);
 
-CREATE FUNCTION foorescan(int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid = $1;' LANGUAGE SQL;
+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);
 
---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;
+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);
 
-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;
+-- deep nesting
 
-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;
+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 VIEW vw_foorescan;
-DROP VIEW fooview1;
-DROP VIEW fooview2;
-DROP FUNCTION foorescan(int,int);
-DROP FUNCTION foorescan(int);
-DROP TABLE foorescan;
-DROP TABLE barrescan;
+DROP FUNCTION foo_sql(int,int);
+DROP FUNCTION foo_mat(int,int);
+DROP SEQUENCE foo_rescan_seq;
 
 --
 -- Test cases involving OUT parameters
@@ -414,6 +447,7 @@ language sql stable;
 
 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();
#5Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: David Fetter (#4)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 21 June 2013 06:54, David Fetter <david@fetter.org> wrote:

For example "SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS file"

The spec is pretty specific about the "all or none" nature of naming
in the AS clause...unless we can figure out a way of getting around it
somehow.

We already support and document the ability to provide a subset of the
columns in the alias. I didn't realise that was beyond the spec, but
since we already have it...

I'm weighing in on the side of a name that's like ?columnN? elsewhere
in the code, i.e. one that couldn't sanely be an actual column name,
whether in table, view, or SRF.

I don't think we need to be overly concerned with coming up with a
unique name for the column. Duplicate column names are fine, except if
the user wants to refer to them elsewhere in the query, in which case
they need to provide aliases to distinguish them, otherwise the query
won't be accepted.

I'd be happy if you just replaced "?column?" with ordinality in a
couple of places in your original patch.

Regards,
Dean

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

#6Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Dean Rasheed (#5)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 21 June 2013 08:02, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 21 June 2013 06:54, David Fetter <david@fetter.org> wrote:

For example "SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS file"

The spec is pretty specific about the "all or none" nature of naming
in the AS clause...unless we can figure out a way of getting around it
somehow.

We already support and document the ability to provide a subset of the
columns in the alias. I didn't realise that was beyond the spec, but
since we already have it...

I'm weighing in on the side of a name that's like ?columnN? elsewhere
in the code, i.e. one that couldn't sanely be an actual column name,
whether in table, view, or SRF.

I don't think we need to be overly concerned with coming up with a
unique name for the column. Duplicate column names are fine, except if
the user wants to refer to them elsewhere in the query, in which case
they need to provide aliases to distinguish them, otherwise the query
won't be accepted.

I'd be happy if you just replaced "?column?" with ordinality in a
couple of places in your original patch.

Expanding on that, I think it's perfectly acceptable to allow
potentially duplicate column names in this context. For the majority
of simple queries the user wouldn't have to (and wouldn't feel
compelled to) supply aliases. Where there was ambiguity they would be
forced to do so, but people are already used to that.

If I write a simple query that selects from a single unnest() with or
without ordinality, I probably won't want to supply aliases.

If I select from 2 unnest()'s *without* ordinality, I already have to
provide aliases.

If I select from 2 SRF's functions with ordinality, I won't be too
surprised if I am also forced to provide aliases.

Regards,
Dean

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

#7Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Dean Rasheed (#1)
Naming of ORDINALITY column (was: Re: Review: UNNEST (and other functions) WITH ORDINALITY)

OK, let's try to cover all the bases here in one go.

First, the spec definition of WITH ORDINALITY simply says that the
column name in the result is not equivalent to any other identifier in
the same <table primary> (including the <correlation name>). It is
clear that the intention of the spec is that any non-positional
reference to this column (which is defined as being positionally last)
requires an alias at some point, whether directly attached to the
<table primary> or at an outer level.

Second, all the documentation I've looked at for other databases that
implement this feature (such as DB2, Teradata, etc.) takes it for
granted that the user must always supply an alias, even though the
syntax does not actually require one. None of the ones I've seen
suggest that the ordinality column has a useful or consistent name if
no alias is supplied.

So, while clearly there's nothing stopping us from going beyond the
spec and using a column name that people can refer to without needing
an alias, it would be a significant divergence from common practice in
other dbs. (iirc, it was my suggestion to David to use "?column?" in
the first place for this reason.)

So as I see it the options are:

1. Stick with "?column?" as a warning flag that you're not supposed to
be using this without aliasing it to something.

2. Use some other fixed name like "ordinality" simply to allow people
to do things like select ... from unnest(x) with ordinality; without
having to bother to provide an alias, simply as a convenience, without
regard for consistency with others. (This will result in a duplicate
name if "x" is of a composite type containing a column called
"ordinality", so the caller will have to provide an alias in that
specific case or get an ambiguous reference error. Similarly if using
some other SRF which defines its own return column names.)

3. Generate an actually unique name (probably pointless)

4. Something else I haven't thought of.

My vote remains with option 1 here; I don't think users should be
encouraged to assume that the ordinality column will have a known
name.

--
Andrew (irc:RhodiumToad)

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

#8Josh Berkus
josh@agliodbs.com
In reply to: Dean Rasheed (#1)
Re: Naming of ORDINALITY column

On 06/23/2013 08:00 PM, Andrew Gierth wrote:

OK, let's try to cover all the bases here in one go.

1. Stick with "?column?" as a warning flag that you're not supposed to
be using this without aliasing it to something.

How do I actually supply an alias which covers both columns? What does
that look like, syntactically?

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

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

#9Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Dean Rasheed (#6)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 21 June 2013 08:31, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 21 June 2013 08:02, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 21 June 2013 06:54, David Fetter <david@fetter.org> wrote:

For example "SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS file"

The spec is pretty specific about the "all or none" nature of naming
in the AS clause...unless we can figure out a way of getting around it
somehow.

We already support and document the ability to provide a subset of the
columns in the alias. I didn't realise that was beyond the spec, but
since we already have it...

I'm weighing in on the side of a name that's like ?columnN? elsewhere
in the code, i.e. one that couldn't sanely be an actual column name,
whether in table, view, or SRF.

I don't think we need to be overly concerned with coming up with a
unique name for the column. Duplicate column names are fine, except if
the user wants to refer to them elsewhere in the query, in which case
they need to provide aliases to distinguish them, otherwise the query
won't be accepted.

I'd be happy if you just replaced "?column?" with ordinality in a
couple of places in your original patch.

Expanding on that, I think it's perfectly acceptable to allow
potentially duplicate column names in this context. For the majority
of simple queries the user wouldn't have to (and wouldn't feel
compelled to) supply aliases. Where there was ambiguity they would be
forced to do so, but people are already used to that.

If I write a simple query that selects from a single unnest() with or
without ordinality, I probably won't want to supply aliases.

If I select from 2 unnest()'s *without* ordinality, I already have to
provide aliases.

If I select from 2 SRF's functions with ordinality, I won't be too
surprised if I am also forced to provide aliases.

No one else has expressed an opinion about the naming of the new
column. All other review comments have been addressed, and the patch
looks to be in good shape, so I'm marking this as ready for committer.

IMO it's a very useful piece of new functionality. Good job!

Regards,
Dean

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

#10Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Josh Berkus (#8)
Re: Naming of ORDINALITY column

On 24 June 2013 04:29, Josh Berkus <josh@agliodbs.com> wrote:

On 06/23/2013 08:00 PM, Andrew Gierth wrote:

OK, let's try to cover all the bases here in one go.

1. Stick with "?column?" as a warning flag that you're not supposed to
be using this without aliasing it to something.

How do I actually supply an alias which covers both columns? What does
that look like, syntactically?

There are a number of possible syntaxes:

SELECT unnest, "?column?" FROM unnest(ARRAY['x','y']) WITH ORDINALITY;
or
SELECT unnest.unnest, unnest."?column?" FROM unnest(ARRAY['x','y'])
WITH ORDINALITY;
unnest | ?column?
--------+----------
x | 1
y | 2
(2 rows)

SELECT t, "?column?" FROM unnest(ARRAY['x','y']) WITH ORDINALITY AS t;
or
SELECT t.t, t."?column?" FROM unnest(ARRAY['x','y']) WITH ORDINALITY AS t;
t | ?column?
---+----------
x | 1
y | 2
(2 rows)

SELECT val, "?column?" FROM unnest(ARRAY['x','y']) WITH ORDINALITY AS t(val);
or
SELECT t.val, t."?column?" FROM unnest(ARRAY['x','y']) WITH ORDINALITY
AS t(val);
val | ?column?
-----+----------
x | 1
y | 2
(2 rows)

SELECT val, ord FROM unnest(ARRAY['x','y']) WITH ORDINALITY AS t(val, ord);
or
SELECT t.val, t.ord FROM unnest(ARRAY['x','y']) WITH ORDINALITY AS t(val, ord);
val | ord
-----+-----
x | 1
y | 2
(2 rows)

My suggestion was to replace "?column?" with ordinality wherever it
appears above, for the user's convenience, but so far more people
prefer "?column?" as a way of indicating that you're supposed to
provide an alias for the column.

If that's what people prefer, I don't mind --- it's still going to be
a very handy new feature.

Regards,
Dean

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

#11Josh Berkus
josh@agliodbs.com
In reply to: Dean Rasheed (#1)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Folks,

(the below was already discussed on IRC)

Leaving names aside on this patch, I'm wondering about a piece of
functionality I have with the current unnest() and with the
unnest_ordinality()[1] extension: namely, the ability to unnest several
arrays "in parallel" by using unnest() in the target list.

For example, given the table:

lotsarrays (
id serial PK,
arr1 int[],
arr2 numeric[],
arr3 boolean[]
)

I can currently do:

SELECT id,
unnest(arr1) as arr1,
unnest(arr2) as arr2,
unnest(arr3) as arr3
FROM lotsarrays;

... and if arr1, 2 and 3 are exactly the same length, this creates a
coordinated dataset. I can even use the unnest_ordinality() extension
function to get the ordinality of this combined dataset:

SELECT id,
(unnest_ordinality(arr1)).element_number as array_index,
unnest(arr1) as arr1,
unnest(arr2) as arr2,
unnest(arr3) as arr3
FROM lotsarrays;

There are reasons why this will be complicated to implement WITH
ORDINALITY; DF, Andrew and I discussed them on IRC. So allowing WITH
ORDINALITY in the target list is a TODO, either for later in 9.4
development, or for 9.5.

So, this isn't stopping the patch; I just want a TODO for "implement
WITH ORDINALITY in the target list for SRFs".

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

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

#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Josh Berkus (#11)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Josh Berkus <josh@agliodbs.com> writes:

... and if arr1, 2 and 3 are exactly the same length, this creates a
coordinated dataset. I can even use the unnest_ordinality() extension
function to get the ordinality of this combined dataset:

SELECT id,
(unnest_ordinality(arr1)).element_number as array_index,
unnest(arr1) as arr1,
unnest(arr2) as arr2,
unnest(arr3) as arr3
FROM lotsarrays;

There are reasons why this will be complicated to implement WITH
ORDINALITY; DF, Andrew and I discussed them on IRC. So allowing WITH
ORDINALITY in the target list is a TODO, either for later in 9.4
development, or for 9.5.

Some of the rest of us would like to hear those reasons, because my
immediate reaction is that the patch must be broken by design. WITH
ORDINALITY should not be needing to mess with the fundamental evaluation
semantics of SRFs, but it sure sounds like it is doing so if that case
doesn't work as expected.

regards, tom lane

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

#13Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Josh Berkus (#11)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 26 June 2013 01:22, Josh Berkus <josh@agliodbs.com> wrote:

Folks,

(the below was already discussed on IRC)

Leaving names aside on this patch, I'm wondering about a piece of
functionality I have with the current unnest() and with the
unnest_ordinality()[1] extension: namely, the ability to unnest several
arrays "in parallel" by using unnest() in the target list.

For example, given the table:

lotsarrays (
id serial PK,
arr1 int[],
arr2 numeric[],
arr3 boolean[]
)

I can currently do:

SELECT id,
unnest(arr1) as arr1,
unnest(arr2) as arr2,
unnest(arr3) as arr3
FROM lotsarrays;

... and if arr1, 2 and 3 are exactly the same length, this creates a
coordinated dataset. I can even use the unnest_ordinality() extension
function to get the ordinality of this combined dataset:

SELECT id,
(unnest_ordinality(arr1)).element_number as array_index,
unnest(arr1) as arr1,
unnest(arr2) as arr2,
unnest(arr3) as arr3
FROM lotsarrays;

There are reasons why this will be complicated to implement WITH
ORDINALITY; DF, Andrew and I discussed them on IRC. So allowing WITH
ORDINALITY in the target list is a TODO, either for later in 9.4
development, or for 9.5.

So, this isn't stopping the patch; I just want a TODO for "implement
WITH ORDINALITY in the target list for SRFs".

So if I'm understanding correctly, your issue is that WITH ORDINALITY
is currently only accepted on SRFs in the FROM list, not that it isn't
working as expected in any way. I have no objection to adding a TODO
item to extend it, but note that the restriction is trivial to work
around:

CREATE TABLE lotsarrays
(
id serial primary key,
arr1 int[],
arr2 numeric[],
arr3 boolean[]
);

INSERT INTO lotsarrays(arr1, arr2, arr3) VALUES
(ARRAY[1,2], ARRAY[1.1, 2.2], ARRAY[true, false]),
(ARRAY[10,20,30], ARRAY[10.1, 20.2, 30.3], ARRAY[true, false, true]);

CREATE OR REPLACE FUNCTION unnest_ordinality(anyarray)
RETURNS TABLE(element_number bigint, element anyelement) AS
$$
SELECT ord, elt FROM unnest($1) WITH ORDINALITY AS t(elt, ord)
$$
LANGUAGE sql STRICT IMMUTABLE;

SELECT id,
(unnest_ordinality(arr1)).element_number as array_index,
unnest(arr1) as arr1,
unnest(arr2) as arr2,
unnest(arr3) as arr3
FROM lotsarrays;
id | array_index | arr1 | arr2 | arr3
----+-------------+------+------+------
1 | 1 | 1 | 1.1 | t
1 | 2 | 2 | 2.2 | f
2 | 1 | 10 | 10.1 | t
2 | 2 | 20 | 20.2 | f
2 | 3 | 30 | 30.3 | t
(5 rows)

Personally I'm not a fan of SRFs in the select list, especially not
multiple SRFs there, since the results are hard to deal with if they
return differing numbers of rows. So I would tend to write this as a
LATERAL FULL join on the ordinality columns:

SELECT id,
COALESCE(u1.ord, u2.ord, u3.ord) AS array_index,
u1.arr1, u2.arr2, u3.arr3
FROM lotsarrays,
unnest(arr1) WITH ORDINALITY AS u1(arr1, ord)
FULL JOIN unnest(arr2) WITH ORDINALITY AS u2(arr2, ord) ON u2.ord = u1.ord
FULL JOIN unnest(arr3) WITH ORDINALITY AS u3(arr3, ord) ON u3.ord =
COALESCE(u1.ord, u2.ord);

Either way, I think the WITH ORDINALITY patch is working as expected.

Regards,
Dean

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

#14David Fetter
david@fetter.org
In reply to: Dean Rasheed (#9)
1 attachment(s)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Mon, Jun 24, 2013 at 03:04:04PM +0100, Dean Rasheed wrote:

On 21 June 2013 08:31, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 21 June 2013 08:02, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 21 June 2013 06:54, David Fetter <david@fetter.org> wrote:

For example "SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS file"

The spec is pretty specific about the "all or none" nature of naming
in the AS clause...unless we can figure out a way of getting around it
somehow.

We already support and document the ability to provide a subset of the
columns in the alias. I didn't realise that was beyond the spec, but
since we already have it...

I'm weighing in on the side of a name that's like ?columnN? elsewhere
in the code, i.e. one that couldn't sanely be an actual column name,
whether in table, view, or SRF.

I don't think we need to be overly concerned with coming up with a
unique name for the column. Duplicate column names are fine, except if
the user wants to refer to them elsewhere in the query, in which case
they need to provide aliases to distinguish them, otherwise the query
won't be accepted.

I'd be happy if you just replaced "?column?" with ordinality in a
couple of places in your original patch.

Expanding on that, I think it's perfectly acceptable to allow
potentially duplicate column names in this context. For the majority
of simple queries the user wouldn't have to (and wouldn't feel
compelled to) supply aliases. Where there was ambiguity they would be
forced to do so, but people are already used to that.

If I write a simple query that selects from a single unnest() with or
without ordinality, I probably won't want to supply aliases.

If I select from 2 unnest()'s *without* ordinality, I already have to
provide aliases.

If I select from 2 SRF's functions with ordinality, I won't be too
surprised if I am also forced to provide aliases.

No one else has expressed an opinion about the naming of the new
column. All other review comments have been addressed, and the patch
looks to be in good shape, so I'm marking this as ready for committer.

IMO it's a very useful piece of new functionality. Good job!

Thanks!

Please find attach another rev of the patch which reflects the
de-reserving of OVER.

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_10.difftext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7c009d8..d151c7e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13274,7 +13274,7 @@ select $1[i][j]
         generate_subscripts($1,2) g2(j);
 $$ LANGUAGE sql IMMUTABLE;
 CREATE FUNCTION
-postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
+SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
  unnest2 
 ---------
        1
@@ -13285,6 +13285,48 @@ postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
 </programlisting>
   </para>
 
+  <indexterm>
+   <primary>ordinality</primary>
+  </indexterm>
+
+  <para>
+  When a function in the <literal>FROM</literal> clause is suffixed by
+  <literal>WITH ORDINALITY</literal>, an integer column is appended to
+  the output which starts from 1 and increments by 1 for each row of
+  the function's output.  This is more useful in the case of
+  set-returning functions than of others.  This functionality is
+  available for functions returning composite types or using
+  <literal>OUT</literal> parameters, but not when using a function
+  returning <literal>RECORD</literal> with an explicit column definition list.
+
+<programlisting>
+-- SRF WITH ORDINALITY
+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>
+
  </sect1>
 
  <sect1 id="functions-info">
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 68309ba..88c557e 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -52,7 +52,8 @@ 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> [, ...] ] ) [ 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,18 +369,39 @@ 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
+        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, including the column added by <literal>ORDINALITY</literal> if present.
+      </para>
+
+      <para>
+        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.
+        class="parameter">data_type</replaceable> <optional>, ...
+        </>)</literal>.  The column definition list must match the
+        actual number and types of columns returned by the function.
+        <literal>ORDINALITY</literal> does not work in this case.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index fb5c199..3c6f5e3 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -158,6 +158,35 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 }
 
 /*
+ * 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).
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 24325fe..7f8ede7 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -25,7 +25,7 @@
 #include "executor/nodeFunctionscan.h"
 #include "funcapi.h"
 #include "nodes/nodeFuncs.h"
-
+#include "catalog/pg_type.h"
 
 static TupleTableSlot *FunctionNext(FunctionScanState *node);
 
@@ -42,10 +42,27 @@ static TupleTableSlot *FunctionNext(FunctionScanState *node);
 static TupleTableSlot *
 FunctionNext(FunctionScanState *node)
 {
-	TupleTableSlot *slot;
 	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,19 +81,52 @@ FunctionNext(FunctionScanState *node)
 		node->tuplestorestate = tuplestorestate =
 			ExecMakeTableFunctionResult(node->funcexpr,
 										node->ss.ps.ps_ExprContext,
-										node->tupdesc,
+										node->func_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;
+								   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,7 +166,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	FunctionScanState *scanstate;
 	Oid			funcrettype;
 	TypeFuncClass functypclass;
-	TupleDesc	tupdesc = NULL;
+	TupleDesc	func_tupdesc = NULL;
+	TupleDesc	scan_tupdesc = NULL;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & EXEC_FLAG_MARK));
@@ -148,6 +199,11 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	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,37 +220,48 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	 */
 	functypclass = get_expr_result_type(node->funcexpr,
 										&funcrettype,
-										&tupdesc);
+										&func_tupdesc);
 
 	if (functypclass == TYPEFUNC_COMPOSITE)
 	{
 		/* Composite data type, e.g. a table's row type */
-		Assert(tupdesc);
+		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 */
-		tupdesc = CreateTupleDescCopy(tupdesc);
+		func_tupdesc = CreateTupleDescCopy(func_tupdesc);
 	}
 	else if (functypclass == TYPEFUNC_SCALAR)
 	{
 		/* Base data type, i.e. scalar */
 		char	   *attname = strVal(linitial(node->funccolnames));
 
-		tupdesc = CreateTemplateTupleDesc(1, false);
-		TupleDescInitEntry(tupdesc,
+		func_tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(func_tupdesc,
 						   (AttrNumber) 1,
 						   attname,
 						   funcrettype,
 						   -1,
 						   0);
-		TupleDescInitEntryCollation(tupdesc,
+		TupleDescInitEntryCollation(func_tupdesc,
 									(AttrNumber) 1,
 									exprCollation(node->funcexpr));
 	}
 	else if (functypclass == TYPEFUNC_RECORD)
 	{
-		tupdesc = BuildDescFromLists(node->funccolnames,
-									 node->funccoltypes,
-									 node->funccoltypmods,
-									 node->funccolcollations);
+		func_tupdesc = BuildDescFromLists(node->funccolnames,
+										  node->funccoltypes,
+										  node->funccoltypmods,
+										  node->funccolcollations);
 	}
 	else
 	{
@@ -207,14 +274,36 @@ 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);
+	BlessTupleDesc(func_tupdesc);
 
-	scanstate->tupdesc = tupdesc;
-	ExecAssignScanType(&scanstate->ss, 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,6 +338,8 @@ ExecEndFunctionScan(FunctionScanState *node)
 	 */
 	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
+	if (node->func_slot)
+		ExecClearTuple(node->func_slot);
 
 	/*
 	 * Release tuplestore resources
@@ -268,9 +359,13 @@ void
 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.
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..944866d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -508,6 +508,7 @@ _copyFunctionScan(const FunctionScan *from)
 	COPY_NODE_FIELD(funccoltypes);
 	COPY_NODE_FIELD(funccoltypmods);
 	COPY_NODE_FIELD(funccolcollations);
+	COPY_SCALAR_FIELD(funcordinality);
 
 	return newnode;
 }
@@ -1980,6 +1981,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_NODE_FIELD(funccoltypes);
 	COPY_NODE_FIELD(funccoltypmods);
 	COPY_NODE_FIELD(funccolcollations);
+	COPY_SCALAR_FIELD(funcordinality);
 	COPY_NODE_FIELD(values_lists);
 	COPY_NODE_FIELD(values_collations);
 	COPY_STRING_FIELD(ctename);
@@ -2280,6 +2282,7 @@ _copyRangeFunction(const RangeFunction *from)
 {
 	RangeFunction *newnode = makeNode(RangeFunction);
 
+	COPY_SCALAR_FIELD(ordinality);
 	COPY_SCALAR_FIELD(lateral);
 	COPY_NODE_FIELD(funccallnode);
 	COPY_NODE_FIELD(alias);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..358c70a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2120,6 +2120,7 @@ _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
 static bool
 _equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
 {
+	COMPARE_SCALAR_FIELD(ordinality);
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_NODE_FIELD(funccallnode);
 	COMPARE_NODE_FIELD(alias);
@@ -2228,6 +2229,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_NODE_FIELD(funccoltypes);
 	COMPARE_NODE_FIELD(funccoltypmods);
 	COMPARE_NODE_FIELD(funccolcollations);
+	COMPARE_SCALAR_FIELD(funcordinality);
 	COMPARE_NODE_FIELD(values_lists);
 	COMPARE_NODE_FIELD(values_collations);
 	COMPARE_STRING_FIELD(ctename);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index c487db9..806e213 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -153,12 +153,12 @@ makeWholeRowVar(RangeTblEntry *rte,
 			break;
 		case RTE_FUNCTION:
 			toid = exprType(rte->funcexpr);
-			if (type_is_rowtype(toid))
+			if (type_is_rowtype(toid) || rte->funcordinality)
 			{
 				/* func returns composite; same as relation case */
 				result = makeVar(varno,
 								 InvalidAttrNumber,
-								 toid,
+								 (rte->funcordinality) ? RECORDOID : toid,
 								 -1,
 								 InvalidOid,
 								 varlevelsup);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..eeb3ba9 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -520,6 +520,7 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
 	WRITE_NODE_FIELD(funccoltypes);
 	WRITE_NODE_FIELD(funccoltypmods);
 	WRITE_NODE_FIELD(funccolcollations);
+	WRITE_BOOL_FIELD(funcordinality);
 }
 
 static void
@@ -2367,6 +2368,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_NODE_FIELD(funccoltypes);
 			WRITE_NODE_FIELD(funccoltypmods);
 			WRITE_NODE_FIELD(funccolcollations);
+			WRITE_BOOL_FIELD(funcordinality);
 			break;
 		case RTE_VALUES:
 			WRITE_NODE_FIELD(values_lists);
@@ -2599,6 +2601,7 @@ _outRangeFunction(StringInfo str, const RangeFunction *node)
 {
 	WRITE_NODE_TYPE("RANGEFUNCTION");
 
+	WRITE_BOOL_FIELD(ordinality);
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_NODE_FIELD(funccallnode);
 	WRITE_NODE_FIELD(alias);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..a61b977 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1205,6 +1205,7 @@ _readRangeTblEntry(void)
 			READ_NODE_FIELD(funccoltypes);
 			READ_NODE_FIELD(funccoltypmods);
 			READ_NODE_FIELD(funccolcollations);
+			READ_BOOL_FIELD(funcordinality);
 			break;
 		case RTE_VALUES:
 			READ_NODE_FIELD(values_lists);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 52bab79..2b0d2f2 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -115,8 +115,8 @@ 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,
+				  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,6 +1733,7 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
 
 	scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
 								  funcexpr,
+								  rte->funcordinality,
 								  rte->eref->colnames,
 								  rte->funccoltypes,
 								  rte->funccoltypmods,
@@ -3366,6 +3367,7 @@ make_functionscan(List *qptlist,
 				  List *qpqual,
 				  Index scanrelid,
 				  Node *funcexpr,
+				  bool ordinality,
 				  List *funccolnames,
 				  List *funccoltypes,
 				  List *funccoltypmods,
@@ -3381,6 +3383,7 @@ make_functionscan(List *qptlist,
 	plan->righttree = NULL;
 	node->scan.scanrelid = scanrelid;
 	node->funcexpr = funcexpr;
+	node->funcordinality = ordinality;
 	node->funccolnames = funccolnames;
 	node->funccoltypes = funccoltypes;
 	node->funccoltypmods = funccoltypmods;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 6d5b204..87501b0 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4497,10 +4497,15 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 	 */
 	check_stack_depth();
 
+	/* Fail if the caller wanted ORDINALITY - we don't implement that here. */
+	if (rte->funcordinality)
+		return NULL;
+
 	/* Fail if FROM item isn't a simple FuncExpr */
 	fexpr = (FuncExpr *) rte->funcexpr;
 	if (fexpr == NULL || !IsA(fexpr, FuncExpr))
 		return NULL;
+
 	func_oid = fexpr->funcid;
 
 	/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0fc5b13..f3125d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -565,7 +565,7 @@ 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
+	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
@@ -608,8 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * list and so can never be entered directly.  The filter in parser.c
  * creates these tokens when required.
  */
-%token			NULLS_FIRST NULLS_LAST WITH_TIME
-
+%token			NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
 
 /* Precedence: lowest to highest */
 %nonassoc	SET				/* see relation_expr_opt_alias */
@@ -9587,20 +9586,42 @@ table_ref:	relation_expr opt_alias_clause
 				{
 					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);
@@ -12839,6 +12860,7 @@ unreserved_keyword:
 			| OPERATOR
 			| OPTION
 			| OPTIONS
+			| ORDINALITY
 			| OVER
 			| OWNED
 			| OWNER
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a9254c8..9d0f3b6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -797,7 +797,7 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
  * physical column numbers.
  */
 static void
-buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
+buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality)
 {
 	int			maxattrs = tupdesc->natts;
 	ListCell   *aliaslc;
@@ -849,6 +849,25 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
 		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,43 +890,42 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
  */
 static void
 buildScalarFunctionAlias(Node *funcexpr, char *funcname,
-						 Alias *alias, Alias *eref)
+						 Alias *alias, Alias *eref, bool ordinality)
 {
-	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)
+		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);
-		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))
+	else
 	{
-		pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid);
-		if (pname)
-		{
-			eref->colnames = list_make1(makeString(pname));
-			return;
-		}
+		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));
 	}
 
-	/*
-	 * Otherwise use the previously-determined alias (not necessarily the
-	 * function name!)
-	 */
-	eref->colnames = list_make1(makeString(eref->aliasname));
+	if (ordinality && list_length(eref->colnames) < 2)
+		eref->colnames = lappend(eref->colnames, makeString(pstrdup("?column?")));
+
+	return;
 }
 
 /*
@@ -1003,7 +1021,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1063,7 +1081,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
 
 	/*
 	 * Set flags and access permissions.
@@ -1234,17 +1252,23 @@ addRangeTableEntryForFunction(ParseState *pstate,
 		/* Composite data type, e.g. a table's row type */
 		Assert(tupdesc);
 		/* Build the column alias list */
-		buildRelationAliases(tupdesc, alias, eref);
+		buildRelationAliases(tupdesc, alias, eref, rangefunc->ordinality);
 	}
 	else if (functypclass == TYPEFUNC_SCALAR)
 	{
 		/* Base data type, i.e. scalar */
-		buildScalarFunctionAlias(funcexpr, funcname, alias, eref);
+		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,6 +1311,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * permissions mechanism).
 	 */
 	rte->lateral = lateral;
+	rte->funcordinality = rangefunc->ordinality;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
@@ -1710,6 +1735,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 				TypeFuncClass functypclass;
 				Oid			funcrettype;
 				TupleDesc	tupdesc;
+				int         ordattno = 0;
 
 				functypclass = get_expr_result_type(rte->funcexpr,
 													&funcrettype,
@@ -1721,6 +1747,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					expandTupleDesc(tupdesc, rte->eref,
 									rtindex, sublevels_up, location,
 									include_dropped, colnames, colvars);
+					ordattno = tupdesc->natts + 1;
 				}
 				else if (functypclass == TYPEFUNC_SCALAR)
 				{
@@ -1741,6 +1768,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 
 						*colvars = lappend(*colvars, varnode);
 					}
+
+					ordattno = 2;
 				}
 				else if (functypclass == TYPEFUNC_RECORD)
 				{
@@ -1779,6 +1808,24 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					/* addRangeTableEntryForFunction should've caught this */
 					elog(ERROR, "function in FROM has unsupported return type");
 				}
+
+				/* tack on the extra ordinality column if present */
+				if (rte->funcordinality)
+				{
+					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,36 +2220,57 @@ 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 (rte->funcordinality && 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)));
 
-					/*
-					 * 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;
+						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);
+					if (rte->funcordinality && 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,7 +2390,11 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 				Oid			funcrettype = exprType(rte->funcexpr);
 				Oid			funcrelid = typeidTypeRelid(funcrettype);
 
-				if (OidIsValid(funcrelid))
+				if (rte->funcordinality && attnum == list_length(rte->eref->colnames))
+				{
+					result = false;
+				}
+				else if (OidIsValid(funcrelid))
 				{
 					/*
 					 * Composite data type, i.e. a table's row type
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index b8ec790..541d364 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -133,7 +133,7 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 		case WITH:
 
 			/*
-			 * WITH TIME must be reduced to one token
+			 * WITH TIME and WITH ORDINALITY must each be reduced to one token
 			 */
 			cur_yylval = lvalp->core_yystype;
 			cur_yylloc = *llocp;
@@ -143,6 +143,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 				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;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a1ed781..263d25d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7972,6 +7972,8 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			case RTE_FUNCTION:
 				/* Function RTE */
 				get_rule_expr(rte->funcexpr, context, true);
+				if (rte->funcordinality)
+					appendStringInfoString(buf, " WITH ORDINALITY");
 				break;
 			case RTE_VALUES:
 				/* Values list RTE */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 51c5575..49226b7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -87,6 +87,7 @@ extern TupleDesc CreateTupleDesc(int natts, bool hasoid,
 				Form_pg_attribute *attrs);
 
 extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
+extern TupleDesc CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts);
 
 extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..6e9003a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1389,7 +1389,10 @@ typedef struct SubqueryScanState
  *		function appearing in FROM (typically a function returning set).
  *
  *		eflags				node's capability flags
- *		tupdesc				expected return tuple description
+ *		ordinal				column for WITH ORDINALITY
+ *		scan_tupdesc		scan tuple descriptor 
+ *		func_tupdesc		function tuple descriptor 
+ *		func_slot			function slot
  *		tuplestorestate		private state of tuplestore.c
  *		funcexpr			state for function expression being evaluated
  * ----------------
@@ -1398,7 +1401,10 @@ typedef struct FunctionScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
 	int			eflags;
-	TupleDesc	tupdesc;
+	int64       ordinal;
+	TupleDesc	scan_tupdesc;
+	TupleDesc	func_tupdesc;
+	TupleTableSlot *func_slot;
 	Tuplestorestate *tuplestorestate;
 	ExprState  *funcexpr;
 } FunctionScanState;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9453e1d..3cb551d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -463,6 +463,7 @@ typedef struct RangeFunction
 {
 	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
@@ -746,6 +747,7 @@ typedef struct RangeTblEntry
 	List	   *funccoltypes;	/* OID list of column type OIDs */
 	List	   *funccoltypmods; /* integer list of column typmods */
 	List	   *funccolcollations;		/* OID list of column collation OIDs */
+	bool		funcordinality;	/* is this called WITH ORDINALITY? */
 
 	/*
 	 * Fields valid for a values RTE (else NIL):
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 841701e..5341be6 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -424,6 +424,7 @@ typedef struct FunctionScan
 {
 	Scan		scan;
 	Node	   *funcexpr;		/* expression tree for func call */
+	bool        funcordinality; /* WITH ORDINALITY */
 	List	   *funccolnames;	/* output column names (string Value nodes) */
 	List	   *funccoltypes;	/* OID list of column type OIDs */
 	List	   *funccoltypmods; /* integer list of column typmods */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b3d72a9..798765d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -268,6 +268,7 @@ PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD)
 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, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 1678277..377c00a 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -18,7 +18,75 @@ 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;
+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,6 +96,15 @@ select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
      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,6 +150,12 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +163,14 @@ SELECT * FROM vw_getfoo;
       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,6 +182,13 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +197,15 @@ SELECT * FROM vw_getfoo;
       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,6 +217,13 @@ SELECT * FROM getfoo(1) AS t1;
  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,6 +232,15 @@ SELECT * FROM vw_getfoo;
  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,6 +251,12 @@ SELECT * FROM getfoo(1) AS t1;
      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,6 +264,14 @@ SELECT * FROM vw_getfoo;
      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,6 +283,13 @@ SELECT * FROM getfoo(1) AS t1;
      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,6 +298,16 @@ SELECT * FROM vw_getfoo;
      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,6 +356,12 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +369,14 @@ SELECT * FROM vw_getfoo;
       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,6 +387,12 @@ SELECT * FROM getfoo(1) AS t1;
      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,181 +401,622 @@ SELECT * FROM vw_getfoo;
 (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 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
+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)
 
-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
+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)
 
-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
+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)
 
-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
+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)
 
-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;
+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,6 +1492,13 @@ SELECT * FROM get_users();
  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;
diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql
index f1a405a..003da51 100644
--- a/src/test/regress/sql/rangefuncs.sql
+++ b/src/test/regress/sql/rangefuncs.sql
@@ -5,11 +5,29 @@ 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;
+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,41 +48,62 @@ INSERT INTO foo VALUES(2,1,'Mary');
 -- 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,16 +127,24 @@ DROP VIEW vw_getfoo;
 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,99 +153,85 @@ 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');
+CREATE TEMPORARY SEQUENCE foo_rescan_seq;
+CREATE TYPE foo_rescan_t AS (i integer, s bigint);
 
-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');
+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;
 
-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');
+--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
 
-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');
+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;
 
-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');
+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;
 
-CREATE FUNCTION foorescan(int,int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid >= $1 and fooid < $2 ;' LANGUAGE SQL;
+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;
 
---invokes ExecReScanFunctionScan
-SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;
+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;
 
-CREATE VIEW vw_foorescan AS SELECT * FROM foorescan(5002,5004);
+--invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
 
---invokes ExecReScanFunctionScan
-SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2;
+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);
 
-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);
+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);
 
-CREATE FUNCTION foorescan(int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid = $1;' LANGUAGE SQL;
+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);
 
---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;
+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);
 
-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;
+-- deep nesting
 
-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;
+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 VIEW vw_foorescan;
-DROP VIEW fooview1;
-DROP VIEW fooview2;
-DROP FUNCTION foorescan(int,int);
-DROP FUNCTION foorescan(int);
-DROP TABLE foorescan;
-DROP TABLE barrescan;
+DROP FUNCTION foo_sql(int,int);
+DROP FUNCTION foo_mat(int,int);
+DROP SEQUENCE foo_rescan_seq;
 
 --
 -- Test cases involving OUT parameters
@@ -414,6 +447,7 @@ language sql stable;
 
 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();
#15David Fetter
david@fetter.org
In reply to: David Fetter (#14)
1 attachment(s)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Sun, Jun 30, 2013 at 06:00:35PM -0700, David Fetter wrote:

On Mon, Jun 24, 2013 at 03:04:04PM +0100, Dean Rasheed wrote:

On 21 June 2013 08:31, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 21 June 2013 08:02, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

On 21 June 2013 06:54, David Fetter <david@fetter.org> wrote:

For example "SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS file"

The spec is pretty specific about the "all or none" nature of naming
in the AS clause...unless we can figure out a way of getting around it
somehow.

We already support and document the ability to provide a subset of the
columns in the alias. I didn't realise that was beyond the spec, but
since we already have it...

I'm weighing in on the side of a name that's like ?columnN? elsewhere
in the code, i.e. one that couldn't sanely be an actual column name,
whether in table, view, or SRF.

I don't think we need to be overly concerned with coming up with a
unique name for the column. Duplicate column names are fine, except if
the user wants to refer to them elsewhere in the query, in which case
they need to provide aliases to distinguish them, otherwise the query
won't be accepted.

I'd be happy if you just replaced "?column?" with ordinality in a
couple of places in your original patch.

Expanding on that, I think it's perfectly acceptable to allow
potentially duplicate column names in this context. For the majority
of simple queries the user wouldn't have to (and wouldn't feel
compelled to) supply aliases. Where there was ambiguity they would be
forced to do so, but people are already used to that.

If I write a simple query that selects from a single unnest() with or
without ordinality, I probably won't want to supply aliases.

If I select from 2 unnest()'s *without* ordinality, I already have to
provide aliases.

If I select from 2 SRF's functions with ordinality, I won't be too
surprised if I am also forced to provide aliases.

No one else has expressed an opinion about the naming of the new
column. All other review comments have been addressed, and the patch
looks to be in good shape, so I'm marking this as ready for committer.

IMO it's a very useful piece of new functionality. Good job!

Thanks!

Please find attach another rev of the patch which reflects the
de-reserving of OVER.

Patch re-jiggered for recent changes to 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_11.difftext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5765ddf..0c97365 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13278,7 +13278,7 @@ select $1[i][j]
         generate_subscripts($1,2) g2(j);
 $$ LANGUAGE sql IMMUTABLE;
 CREATE FUNCTION
-postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
+SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
  unnest2 
 ---------
        1
@@ -13289,6 +13289,48 @@ postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
 </programlisting>
   </para>
 
+  <indexterm>
+   <primary>ordinality</primary>
+  </indexterm>
+
+  <para>
+  When a function in the <literal>FROM</literal> clause is suffixed by
+  <literal>WITH ORDINALITY</literal>, an integer column is appended to
+  the output which starts from 1 and increments by 1 for each row of
+  the function's output.  This is more useful in the case of
+  set-returning functions than of others.  This functionality is
+  available for functions returning composite types or using
+  <literal>OUT</literal> parameters, but not when using a function
+  returning <literal>RECORD</literal> with an explicit column definition list.
+
+<programlisting>
+-- SRF WITH ORDINALITY
+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>
+
  </sect1>
 
  <sect1 id="functions-info">
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 68309ba..88c557e 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -52,7 +52,8 @@ 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> [, ...] ] ) [ 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,18 +369,39 @@ 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
+        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, including the column added by <literal>ORDINALITY</literal> if present.
+      </para>
+
+      <para>
+        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.
+        class="parameter">data_type</replaceable> <optional>, ...
+        </>)</literal>.  The column definition list must match the
+        actual number and types of columns returned by the function.
+        <literal>ORDINALITY</literal> does not work in this case.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index fb5c199..3c6f5e3 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -158,6 +158,35 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 }
 
 /*
+ * 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).
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 24325fe..7f8ede7 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -25,7 +25,7 @@
 #include "executor/nodeFunctionscan.h"
 #include "funcapi.h"
 #include "nodes/nodeFuncs.h"
-
+#include "catalog/pg_type.h"
 
 static TupleTableSlot *FunctionNext(FunctionScanState *node);
 
@@ -42,10 +42,27 @@ static TupleTableSlot *FunctionNext(FunctionScanState *node);
 static TupleTableSlot *
 FunctionNext(FunctionScanState *node)
 {
-	TupleTableSlot *slot;
 	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,19 +81,52 @@ FunctionNext(FunctionScanState *node)
 		node->tuplestorestate = tuplestorestate =
 			ExecMakeTableFunctionResult(node->funcexpr,
 										node->ss.ps.ps_ExprContext,
-										node->tupdesc,
+										node->func_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;
+								   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,7 +166,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	FunctionScanState *scanstate;
 	Oid			funcrettype;
 	TypeFuncClass functypclass;
-	TupleDesc	tupdesc = NULL;
+	TupleDesc	func_tupdesc = NULL;
+	TupleDesc	scan_tupdesc = NULL;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & EXEC_FLAG_MARK));
@@ -148,6 +199,11 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	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,37 +220,48 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	 */
 	functypclass = get_expr_result_type(node->funcexpr,
 										&funcrettype,
-										&tupdesc);
+										&func_tupdesc);
 
 	if (functypclass == TYPEFUNC_COMPOSITE)
 	{
 		/* Composite data type, e.g. a table's row type */
-		Assert(tupdesc);
+		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 */
-		tupdesc = CreateTupleDescCopy(tupdesc);
+		func_tupdesc = CreateTupleDescCopy(func_tupdesc);
 	}
 	else if (functypclass == TYPEFUNC_SCALAR)
 	{
 		/* Base data type, i.e. scalar */
 		char	   *attname = strVal(linitial(node->funccolnames));
 
-		tupdesc = CreateTemplateTupleDesc(1, false);
-		TupleDescInitEntry(tupdesc,
+		func_tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(func_tupdesc,
 						   (AttrNumber) 1,
 						   attname,
 						   funcrettype,
 						   -1,
 						   0);
-		TupleDescInitEntryCollation(tupdesc,
+		TupleDescInitEntryCollation(func_tupdesc,
 									(AttrNumber) 1,
 									exprCollation(node->funcexpr));
 	}
 	else if (functypclass == TYPEFUNC_RECORD)
 	{
-		tupdesc = BuildDescFromLists(node->funccolnames,
-									 node->funccoltypes,
-									 node->funccoltypmods,
-									 node->funccolcollations);
+		func_tupdesc = BuildDescFromLists(node->funccolnames,
+										  node->funccoltypes,
+										  node->funccoltypmods,
+										  node->funccolcollations);
 	}
 	else
 	{
@@ -207,14 +274,36 @@ 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);
+	BlessTupleDesc(func_tupdesc);
 
-	scanstate->tupdesc = tupdesc;
-	ExecAssignScanType(&scanstate->ss, 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,6 +338,8 @@ ExecEndFunctionScan(FunctionScanState *node)
 	 */
 	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
+	if (node->func_slot)
+		ExecClearTuple(node->func_slot);
 
 	/*
 	 * Release tuplestore resources
@@ -268,9 +359,13 @@ void
 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.
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..944866d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -508,6 +508,7 @@ _copyFunctionScan(const FunctionScan *from)
 	COPY_NODE_FIELD(funccoltypes);
 	COPY_NODE_FIELD(funccoltypmods);
 	COPY_NODE_FIELD(funccolcollations);
+	COPY_SCALAR_FIELD(funcordinality);
 
 	return newnode;
 }
@@ -1980,6 +1981,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_NODE_FIELD(funccoltypes);
 	COPY_NODE_FIELD(funccoltypmods);
 	COPY_NODE_FIELD(funccolcollations);
+	COPY_SCALAR_FIELD(funcordinality);
 	COPY_NODE_FIELD(values_lists);
 	COPY_NODE_FIELD(values_collations);
 	COPY_STRING_FIELD(ctename);
@@ -2280,6 +2282,7 @@ _copyRangeFunction(const RangeFunction *from)
 {
 	RangeFunction *newnode = makeNode(RangeFunction);
 
+	COPY_SCALAR_FIELD(ordinality);
 	COPY_SCALAR_FIELD(lateral);
 	COPY_NODE_FIELD(funccallnode);
 	COPY_NODE_FIELD(alias);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..358c70a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2120,6 +2120,7 @@ _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
 static bool
 _equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
 {
+	COMPARE_SCALAR_FIELD(ordinality);
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_NODE_FIELD(funccallnode);
 	COMPARE_NODE_FIELD(alias);
@@ -2228,6 +2229,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_NODE_FIELD(funccoltypes);
 	COMPARE_NODE_FIELD(funccoltypmods);
 	COMPARE_NODE_FIELD(funccolcollations);
+	COMPARE_SCALAR_FIELD(funcordinality);
 	COMPARE_NODE_FIELD(values_lists);
 	COMPARE_NODE_FIELD(values_collations);
 	COMPARE_STRING_FIELD(ctename);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 245aef2..9b3b13b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -153,12 +153,12 @@ makeWholeRowVar(RangeTblEntry *rte,
 			break;
 		case RTE_FUNCTION:
 			toid = exprType(rte->funcexpr);
-			if (type_is_rowtype(toid))
+			if (type_is_rowtype(toid) || rte->funcordinality)
 			{
 				/* func returns composite; same as relation case */
 				result = makeVar(varno,
 								 InvalidAttrNumber,
-								 toid,
+								 (rte->funcordinality) ? RECORDOID : toid,
 								 -1,
 								 InvalidOid,
 								 varlevelsup);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..eeb3ba9 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -520,6 +520,7 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
 	WRITE_NODE_FIELD(funccoltypes);
 	WRITE_NODE_FIELD(funccoltypmods);
 	WRITE_NODE_FIELD(funccolcollations);
+	WRITE_BOOL_FIELD(funcordinality);
 }
 
 static void
@@ -2367,6 +2368,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_NODE_FIELD(funccoltypes);
 			WRITE_NODE_FIELD(funccoltypmods);
 			WRITE_NODE_FIELD(funccolcollations);
+			WRITE_BOOL_FIELD(funcordinality);
 			break;
 		case RTE_VALUES:
 			WRITE_NODE_FIELD(values_lists);
@@ -2599,6 +2601,7 @@ _outRangeFunction(StringInfo str, const RangeFunction *node)
 {
 	WRITE_NODE_TYPE("RANGEFUNCTION");
 
+	WRITE_BOOL_FIELD(ordinality);
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_NODE_FIELD(funccallnode);
 	WRITE_NODE_FIELD(alias);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..a61b977 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1205,6 +1205,7 @@ _readRangeTblEntry(void)
 			READ_NODE_FIELD(funccoltypes);
 			READ_NODE_FIELD(funccoltypmods);
 			READ_NODE_FIELD(funccolcollations);
+			READ_BOOL_FIELD(funcordinality);
 			break;
 		case RTE_VALUES:
 			READ_NODE_FIELD(values_lists);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 52bab79..2b0d2f2 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -115,8 +115,8 @@ 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,
+				  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,6 +1733,7 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
 
 	scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
 								  funcexpr,
+								  rte->funcordinality,
 								  rte->eref->colnames,
 								  rte->funccoltypes,
 								  rte->funccoltypmods,
@@ -3366,6 +3367,7 @@ make_functionscan(List *qptlist,
 				  List *qpqual,
 				  Index scanrelid,
 				  Node *funcexpr,
+				  bool ordinality,
 				  List *funccolnames,
 				  List *funccoltypes,
 				  List *funccoltypmods,
@@ -3381,6 +3383,7 @@ make_functionscan(List *qptlist,
 	plan->righttree = NULL;
 	node->scan.scanrelid = scanrelid;
 	node->funcexpr = funcexpr;
+	node->funcordinality = ordinality;
 	node->funccolnames = funccolnames;
 	node->funccoltypes = funccoltypes;
 	node->funccoltypmods = funccoltypmods;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 6d5b204..87501b0 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4497,10 +4497,15 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 	 */
 	check_stack_depth();
 
+	/* Fail if the caller wanted ORDINALITY - we don't implement that here. */
+	if (rte->funcordinality)
+		return NULL;
+
 	/* Fail if FROM item isn't a simple FuncExpr */
 	fexpr = (FuncExpr *) rte->funcexpr;
 	if (fexpr == NULL || !IsA(fexpr, FuncExpr))
 		return NULL;
+
 	func_oid = fexpr->funcid;
 
 	/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f67ef0c..e046e86 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -565,7 +565,7 @@ 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
+	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
@@ -608,8 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * list and so can never be entered directly.  The filter in parser.c
  * creates these tokens when required.
  */
-%token			NULLS_FIRST NULLS_LAST WITH_TIME
-
+%token			NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
 
 /* Precedence: lowest to highest */
 %nonassoc	SET				/* see relation_expr_opt_alias */
@@ -9587,20 +9586,42 @@ table_ref:	relation_expr opt_alias_clause
 				{
 					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);
@@ -12567,6 +12588,7 @@ unreserved_keyword:
 			| OPERATOR
 			| OPTION
 			| OPTIONS
+			| ORDINALITY
 			| OVER
 			| OWNED
 			| OWNER
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a9254c8..9d0f3b6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -797,7 +797,7 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
  * physical column numbers.
  */
 static void
-buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
+buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality)
 {
 	int			maxattrs = tupdesc->natts;
 	ListCell   *aliaslc;
@@ -849,6 +849,25 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
 		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,43 +890,42 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
  */
 static void
 buildScalarFunctionAlias(Node *funcexpr, char *funcname,
-						 Alias *alias, Alias *eref)
+						 Alias *alias, Alias *eref, bool ordinality)
 {
-	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)
+		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);
-		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))
+	else
 	{
-		pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid);
-		if (pname)
-		{
-			eref->colnames = list_make1(makeString(pname));
-			return;
-		}
+		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));
 	}
 
-	/*
-	 * Otherwise use the previously-determined alias (not necessarily the
-	 * function name!)
-	 */
-	eref->colnames = list_make1(makeString(eref->aliasname));
+	if (ordinality && list_length(eref->colnames) < 2)
+		eref->colnames = lappend(eref->colnames, makeString(pstrdup("?column?")));
+
+	return;
 }
 
 /*
@@ -1003,7 +1021,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1063,7 +1081,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
 
 	/*
 	 * Set flags and access permissions.
@@ -1234,17 +1252,23 @@ addRangeTableEntryForFunction(ParseState *pstate,
 		/* Composite data type, e.g. a table's row type */
 		Assert(tupdesc);
 		/* Build the column alias list */
-		buildRelationAliases(tupdesc, alias, eref);
+		buildRelationAliases(tupdesc, alias, eref, rangefunc->ordinality);
 	}
 	else if (functypclass == TYPEFUNC_SCALAR)
 	{
 		/* Base data type, i.e. scalar */
-		buildScalarFunctionAlias(funcexpr, funcname, alias, eref);
+		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,6 +1311,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * permissions mechanism).
 	 */
 	rte->lateral = lateral;
+	rte->funcordinality = rangefunc->ordinality;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
@@ -1710,6 +1735,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 				TypeFuncClass functypclass;
 				Oid			funcrettype;
 				TupleDesc	tupdesc;
+				int         ordattno = 0;
 
 				functypclass = get_expr_result_type(rte->funcexpr,
 													&funcrettype,
@@ -1721,6 +1747,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					expandTupleDesc(tupdesc, rte->eref,
 									rtindex, sublevels_up, location,
 									include_dropped, colnames, colvars);
+					ordattno = tupdesc->natts + 1;
 				}
 				else if (functypclass == TYPEFUNC_SCALAR)
 				{
@@ -1741,6 +1768,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 
 						*colvars = lappend(*colvars, varnode);
 					}
+
+					ordattno = 2;
 				}
 				else if (functypclass == TYPEFUNC_RECORD)
 				{
@@ -1779,6 +1808,24 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					/* addRangeTableEntryForFunction should've caught this */
 					elog(ERROR, "function in FROM has unsupported return type");
 				}
+
+				/* tack on the extra ordinality column if present */
+				if (rte->funcordinality)
+				{
+					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,36 +2220,57 @@ 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 (rte->funcordinality && 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)));
 
-					/*
-					 * 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;
+						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);
+					if (rte->funcordinality && 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,7 +2390,11 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 				Oid			funcrettype = exprType(rte->funcexpr);
 				Oid			funcrelid = typeidTypeRelid(funcrettype);
 
-				if (OidIsValid(funcrelid))
+				if (rte->funcordinality && attnum == list_length(rte->eref->colnames))
+				{
+					result = false;
+				}
+				else if (OidIsValid(funcrelid))
 				{
 					/*
 					 * Composite data type, i.e. a table's row type
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index b8ec790..541d364 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -133,7 +133,7 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 		case WITH:
 
 			/*
-			 * WITH TIME must be reduced to one token
+			 * WITH TIME and WITH ORDINALITY must each be reduced to one token
 			 */
 			cur_yylval = lvalp->core_yystype;
 			cur_yylloc = *llocp;
@@ -143,6 +143,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 				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;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cf9ce3f..752f81a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7972,6 +7972,8 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			case RTE_FUNCTION:
 				/* Function RTE */
 				get_rule_expr(rte->funcexpr, context, true);
+				if (rte->funcordinality)
+					appendStringInfoString(buf, " WITH ORDINALITY");
 				break;
 			case RTE_VALUES:
 				/* Values list RTE */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 51c5575..49226b7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -87,6 +87,7 @@ extern TupleDesc CreateTupleDesc(int natts, bool hasoid,
 				Form_pg_attribute *attrs);
 
 extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
+extern TupleDesc CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts);
 
 extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..6e9003a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1389,7 +1389,10 @@ typedef struct SubqueryScanState
  *		function appearing in FROM (typically a function returning set).
  *
  *		eflags				node's capability flags
- *		tupdesc				expected return tuple description
+ *		ordinal				column for WITH ORDINALITY
+ *		scan_tupdesc		scan tuple descriptor 
+ *		func_tupdesc		function tuple descriptor 
+ *		func_slot			function slot
  *		tuplestorestate		private state of tuplestore.c
  *		funcexpr			state for function expression being evaluated
  * ----------------
@@ -1398,7 +1401,10 @@ typedef struct FunctionScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
 	int			eflags;
-	TupleDesc	tupdesc;
+	int64       ordinal;
+	TupleDesc	scan_tupdesc;
+	TupleDesc	func_tupdesc;
+	TupleTableSlot *func_slot;
 	Tuplestorestate *tuplestorestate;
 	ExprState  *funcexpr;
 } FunctionScanState;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index de22dff..75b0d21 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -468,6 +468,7 @@ typedef struct RangeFunction
 {
 	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
@@ -751,6 +752,7 @@ typedef struct RangeTblEntry
 	List	   *funccoltypes;	/* OID list of column type OIDs */
 	List	   *funccoltypmods; /* integer list of column typmods */
 	List	   *funccolcollations;		/* OID list of column collation OIDs */
+	bool		funcordinality;	/* is this called WITH ORDINALITY? */
 
 	/*
 	 * Fields valid for a values RTE (else NIL):
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 841701e..5341be6 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -424,6 +424,7 @@ typedef struct FunctionScan
 {
 	Scan		scan;
 	Node	   *funcexpr;		/* expression tree for func call */
+	bool        funcordinality; /* WITH ORDINALITY */
 	List	   *funccolnames;	/* output column names (string Value nodes) */
 	List	   *funccoltypes;	/* OID list of column type OIDs */
 	List	   *funccoltypmods; /* integer list of column typmods */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b3d72a9..798765d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -268,6 +268,7 @@ PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD)
 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, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 1678277..377c00a 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -18,7 +18,75 @@ 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;
+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,6 +96,15 @@ select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
      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,6 +150,12 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +163,14 @@ SELECT * FROM vw_getfoo;
       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,6 +182,13 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +197,15 @@ SELECT * FROM vw_getfoo;
       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,6 +217,13 @@ SELECT * FROM getfoo(1) AS t1;
  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,6 +232,15 @@ SELECT * FROM vw_getfoo;
  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,6 +251,12 @@ SELECT * FROM getfoo(1) AS t1;
      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,6 +264,14 @@ SELECT * FROM vw_getfoo;
      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,6 +283,13 @@ SELECT * FROM getfoo(1) AS t1;
      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,6 +298,16 @@ SELECT * FROM vw_getfoo;
      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,6 +356,12 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +369,14 @@ SELECT * FROM vw_getfoo;
       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,6 +387,12 @@ SELECT * FROM getfoo(1) AS t1;
      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,181 +401,622 @@ SELECT * FROM vw_getfoo;
 (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 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
+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)
 
-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
+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)
 
-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
+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)
 
-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
+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)
 
-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;
+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,6 +1492,13 @@ SELECT * FROM get_users();
  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;
diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql
index f1a405a..003da51 100644
--- a/src/test/regress/sql/rangefuncs.sql
+++ b/src/test/regress/sql/rangefuncs.sql
@@ -5,11 +5,29 @@ 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;
+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,41 +48,62 @@ INSERT INTO foo VALUES(2,1,'Mary');
 -- 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,16 +127,24 @@ DROP VIEW vw_getfoo;
 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,99 +153,85 @@ 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');
+CREATE TEMPORARY SEQUENCE foo_rescan_seq;
+CREATE TYPE foo_rescan_t AS (i integer, s bigint);
 
-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');
+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;
 
-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');
+--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
 
-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');
+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;
 
-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');
+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;
 
-CREATE FUNCTION foorescan(int,int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid >= $1 and fooid < $2 ;' LANGUAGE SQL;
+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;
 
---invokes ExecReScanFunctionScan
-SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;
+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;
 
-CREATE VIEW vw_foorescan AS SELECT * FROM foorescan(5002,5004);
+--invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
 
---invokes ExecReScanFunctionScan
-SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2;
+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);
 
-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);
+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);
 
-CREATE FUNCTION foorescan(int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid = $1;' LANGUAGE SQL;
+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);
 
---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;
+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);
 
-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;
+-- deep nesting
 
-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;
+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 VIEW vw_foorescan;
-DROP VIEW fooview1;
-DROP VIEW fooview2;
-DROP FUNCTION foorescan(int,int);
-DROP FUNCTION foorescan(int);
-DROP TABLE foorescan;
-DROP TABLE barrescan;
+DROP FUNCTION foo_sql(int,int);
+DROP FUNCTION foo_mat(int,int);
+DROP SEQUENCE foo_rescan_seq;
 
 --
 -- Test cases involving OUT parameters
@@ -414,6 +447,7 @@ language sql stable;
 
 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();
#16Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: David Fetter (#15)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 4 July 2013 00:08, David Fetter <david@fetter.org> wrote:

Patch re-jiggered for recent changes to master.

I re-validated this, and it all still looks good, so still ready for
committer IMO.

Regards,
Dean

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

#17Vik Fearing
vik.fearing@dalibo.com
In reply to: Dean Rasheed (#16)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 07/04/2013 10:15 AM, Dean Rasheed wrote:

On 4 July 2013 00:08, David Fetter <david@fetter.org> wrote:

Patch re-jiggered for recent changes to master.

I re-validated this, and it all still looks good, so still ready for
committer IMO.

I tried to check this out, too and "make check" fails with the
following. I have not looked into the cause.

$ cat /home/vik/postgresql/git/src/test/regress/log/initdb.log
Running in noclean mode. Mistakes will not be cleaned up.
The files belonging to this database system will be owned by user "vik".
This user must also own the server process.

The database cluster will be initialized with locales
COLLATE: en_US.UTF-8
CTYPE: en_US.UTF-8
MESSAGES: C
MONETARY: en_US.UTF-8
NUMERIC: en_US.UTF-8
TIME: en_US.UTF-8
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

creating directory
/home/vik/postgresql/git/src/test/regress/./tmp_check/data ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
creating configuration files ... ok
creating template1 database in
/home/vik/postgresql/git/src/test/regress/./tmp_check/data/base/1 ... ok
initializing pg_authid ... ok
initializing dependencies ... FATAL: role with OID 256 does not exist
STATEMENT: DELETE FROM pg_depend;

child process exited with exit code 1
initdb: data directory
"/home/vik/postgresql/git/src/test/regress/./tmp_check/data" not removed
at user's request

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

#18Vik Fearing
vik.fearing@dalibo.com
In reply to: Vik Fearing (#17)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 07/05/2013 04:51 PM, Vik Fearing wrote:

I tried to check this out, too and "make check" fails with the
following. I have not looked into the cause.

Running ./configure again fixed it. Sorry for the noise.

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

#19David Fetter
david@fetter.org
In reply to: Vik Fearing (#18)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Fri, Jul 05, 2013 at 04:58:30PM +0200, Vik Fearing wrote:

On 07/05/2013 04:51 PM, Vik Fearing wrote:

I tried to check this out, too and "make check" fails with the
following. I have not looked into the cause.

Running ./configure again fixed it. Sorry for the noise.

If I had a nickel for every apparent failure of this nature, I'd never
need to work again.

Thanks for checking :)

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

#20Greg Stark
stark@mit.edu
In reply to: Tom Lane (#12)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Wed, Jun 26, 2013 at 2:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Some of the rest of us would like to hear those reasons, because my
immediate reaction is that the patch must be broken by design. WITH
ORDINALITY should not be needing to mess with the fundamental evaluation
semantics of SRFs, but it sure sounds like it is doing so if that case
doesn't work as expected.

As Dan pointed out later the patch is not affecting whether this case
works as expected. Only that since you can only use WITH ORDINALITY on
SRFs in the FROM list and not in the target list if you want to use it
you have to rewrite this query to put the SRFs in the FROM list.

I think we're ok with that since SRFs in the target list are something
that already work kind of strangely and probably we would rather get
rid of rather than work to extend. It would be hard to work ordinality
into the grammar in the middle of the target list let alone figure out
how to implement it.

My only reservation with this patch is whether the WITH_ORDINALITY
parser hack is the way we want to go. The precedent was already set
with WITH TIME ZONE though and I think this was the best option. The
worst failure I can come up with is this which doesn't seem like a
huge problem:

postgres=# with o as (select 1 ) select * from o;
?column?
----------
1
(1 row)

postgres=# with ordinality as (select 1 ) select * from ordinality;
ERROR: syntax error at or near "ordinality"
LINE 1: with ordinality as (select 1 ) select * from ordinality;
^

--
greg

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

#21Greg Stark
stark@mit.edu
In reply to: Greg Stark (#20)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

So the more I look at this patch the fewer things I want to change in
it. I've several times thought I should make an improvement and then
realized I was really just making unnecessary tweaks that didn't
really make much difference.

It seems a shame that the node has to call the function and get back a
slot only to laboriously copy over all the attributes into a new slot.
Worse, it's actually storing all the original tuples in a tuplestore
without the ordinality and adding in the ordinality on output. This
works because the FunctionScan only supports rescan, not mark/restore
so it can easily recalculate them consistently if the tuplestore is
rescanned. I looked into what it would take to get the ordinality
added on the original scan and it would have to go so deep that it
doesn't really seem worthwhile.

I do find the logic and variable names a bit confusing so I'm tempted
to add some comments based on my initial confusion. And I'm tempted to
add an ordinalityAttNum field to the executor node so we don't need to
make these odd "scanslot means this unless we have ordinality in which
case it means that and funcslot means this" logic. That has the side
benefit that if the executor node ever wants to add more attributes it
won't have this assumption that the last column is the ordinality
baked in. I think it'll make the code a bit clearer.

Also, I really think the test cases are excessive. They're mostly
repeatedly testing the same functionality over and over in cases that
are superficially different but I'm fairly certain just invoke the
same codepaths. This will just be an annoyance if we make later
changes that require adjusting the output.

Notably the test that covers the view defintiion appears in pg_views
scares me. We bash around the formatting rules for view definition
outputs pretty regularly. On the other hand it is nice to have
regression tests that make sure these cases are covered. There's been
more than one bug in the past caused by omitting updating these
functions. I'm leaning towards leaving it in but in the long run we
probably need a better solution for this.

Oh, and I'm definitely leaning towards naming the column "ordinality".
Given we name columns things like "generate_series" and "sum" etc
there doesn't seem to be good reason to avoid doing that here.

All in all though I feel like I'm looking for trouble. It's not a very
complex patch and is definitely basically correct. Who should get
credit for it? David? Andrew? And who reviewed it? Dean? Anyone else?

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

#22Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#21)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Greg Stark <stark@mit.edu> writes:

I do find the logic and variable names a bit confusing so I'm tempted
to add some comments based on my initial confusion. And I'm tempted to
add an ordinalityAttNum field to the executor node so we don't need to
make these odd "scanslot means this unless we have ordinality in which
case it means that and funcslot means this" logic.

I haven't read this patch, but that comment scares the heck out of me.
Even if the patch isn't broken today, it will be tomorrow, if it's
making random changes like that in data structure semantics.
Also, if you're confused, so will be everybody else who has to deal with
the code later --- so I don't think fixing the comments and variable
names is optional.

regards, tom lane

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

#23Greg Stark
stark@mit.edu
In reply to: Tom Lane (#22)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Mon, Jul 22, 2013 at 4:42 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I haven't read this patch, but that comment scares the heck out of me.
Even if the patch isn't broken today, it will be tomorrow, if it's
making random changes like that in data structure semantics.

It's not making random changes. It's just that it has two code paths,
in one it has the function scan write directly to the scan slot and in
the other it has the function write to a different slot and copies the
fields over to the scan slot. It's actually doing the right thing it's
just hard to tell that at first (imho) because it's using the scan
slots to determine which case applies instead of having a flag
something to drive the decision.

Also, if you're confused, so will be everybody else who has to deal with
the code later --- so I don't think fixing the comments and variable
names is optional.

Well that's the main benefit of having someone review the code in my
opinion. I'm no smarter than David or Andrew who wrote the code and
there's no guarantee I'll spot any bugs. But at least I can observe
myself and see where I have difficulty following the logic which the
original author is inherently not in a position to do.

Honestly this is pretty straightforward and well written so I'm just
being especially careful not having committed anything recently.
--
greg

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

#24Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Dean Rasheed (#1)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Greg Stark said:

So the more I look at this patch the fewer things I want to change in
it. I've several times thought I should make an improvement and then
realized I was really just making unnecessary tweaks that didn't
really make much difference.

It's almost as though we actually thought about these things when
writing the patch...

I looked into what it would take to get the ordinality added on the
original scan and it would have to go so deep that it doesn't really
seem worthwhile.

A design goal was that no SRF implementation should be affected by the
change, since there are dozens of C-language SRFs in contrib and third-
party modules.

The existence of materialize mode prevents us from changing the
structure of the tuplestore, since we're not the ones allocating it.
The only other approach that seemed possible was to have the tuplestore
code itself add an ordinality, which it would have to do unconditionally
since for materialize-mode SRFs it would have no way to know if one was
required. Doing it in FunctionScan when projecting out tuples seemed much,
much cleaner.

I do find the logic and variable names a bit confusing so I'm tempted
to add some comments based on my initial confusion. And I'm tempted to
add an ordinalityAttNum field to the executor node so we don't need to
make these odd "scanslot means this unless we have ordinality in which
case it means that and funcslot means this" logic. That has the side
benefit that if the executor node ever wants to add more attributes it
won't have this assumption that the last column is the ordinality
baked in. I think it'll make the code a bit clearer.

I admit the (one single) use of checking func_slot for nullity rather
than checking the funcordinality flag is a micro-optimization
(choosing to fetch the value we know we will need rather than a value
which has no other purpose). I thought the comments there were
sufficient; perhaps you could indicate what isn't clear?

Having the ordinality be the last column is required by spec - I'm
sure we could think of pg-specific extensions that would change that,
but why complicate the code now?

Also, I really think the test cases are excessive. They're mostly
repeatedly testing the same functionality over and over in cases that
are superficially different but I'm fairly certain just invoke the
same codepaths. This will just be an annoyance if we make later
changes that require adjusting the output.

The majority of the tests are adding an "ordinality" version to
existing test cases that test a number of combinations of language,
SETOF, and base vs. composite type. These do exercise various different
code paths in expandRTE and thereabouts.

One thing I did do is dike out and replace the entire existing test
sequence that was commented as testing ExecReScanFunctionScan, because
many of the tests in it did not in fact invoke any rescans and
probably hadn't done since 7.4.

Notably the test that covers the view defintiion appears in pg_views
scares me. We bash around the formatting rules for view definition
outputs pretty regularly. On the other hand it is nice to have
regression tests that make sure these cases are covered. There's been
more than one bug in the past caused by omitting updating these
functions. I'm leaning towards leaving it in but in the long run we
probably need a better solution for this.

There are a dozen of those kinds of tests scattered through the
regression tests (though many use pg_get_viewdef directly rather than
pg_views).

While it might be worth centralizing them somewhere, that's really a
separate issue from this patch, since it also affects aggregates.sql,
window.sql, and with.sql.

Oh, and I'm definitely leaning towards naming the column "ordinality".
Given we name columns things like "generate_series" and "sum" etc
there doesn't seem to be good reason to avoid doing that here.

I've already set out why I object to this.

All in all though I feel like I'm looking for trouble. It's not a very
complex patch and is definitely basically correct. Who should get
credit for it? David? Andrew? And who reviewed it? Dean? Anyone else?

It was a joint project between David and myself. Credit to Dean for the
thorough review.

--
Andrew (irc:RhodiumToad)

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

#25David Fetter
david@fetter.org
In reply to: Greg Stark (#23)
1 attachment(s)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Mon, Jul 22, 2013 at 05:19:35PM +0100, Greg Stark wrote:

On Mon, Jul 22, 2013 at 4:42 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I haven't read this patch, but that comment scares the heck out of me.
Even if the patch isn't broken today, it will be tomorrow, if it's
making random changes like that in data structure semantics.

It's not making random changes. It's just that it has two code paths,
in one it has the function scan write directly to the scan slot and in
the other it has the function write to a different slot and copies the
fields over to the scan slot. It's actually doing the right thing it's
just hard to tell that at first (imho) because it's using the scan
slots to determine which case applies instead of having a flag
something to drive the decision.

Also, if you're confused, so will be everybody else who has to deal with
the code later --- so I don't think fixing the comments and variable
names is optional.

Well that's the main benefit of having someone review the code in my
opinion. I'm no smarter than David or Andrew who wrote the code and
there's no guarantee I'll spot any bugs. But at least I can observe
myself and see where I have difficulty following the logic which the
original author is inherently not in a position to do.

Honestly this is pretty straightforward and well written so I'm just
being especially careful not having committed anything recently.

It turns out Andrew Gierth found two issues: backward scanning and
docs mentioning incorrect data type for the ordinality column. The
attached patch fixes both. His chunk is the backward scans; mine the
one-word change :)

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_12.difftext/plain; charset=us-asciiDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 039b279..f7f67f8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13278,7 +13278,7 @@ select $1[i][j]
         generate_subscripts($1,2) g2(j);
 $$ LANGUAGE sql IMMUTABLE;
 CREATE FUNCTION
-postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
+SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
  unnest2 
 ---------
        1
@@ -13289,6 +13289,48 @@ postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
 </programlisting>
   </para>
 
+  <indexterm>
+   <primary>ordinality</primary>
+  </indexterm>
+
+  <para>
+  When a function in the <literal>FROM</literal> clause is suffixed by
+  <literal>WITH ORDINALITY</literal>, a <type>bigint</type> column is appended to
+  the output which starts from 1 and increments by 1 for each row of
+  the function's output.  This is more useful in the case of
+  set-returning functions than of others.  This functionality is
+  available for functions returning composite types or using
+  <literal>OUT</literal> parameters, but not when using a function
+  returning <literal>RECORD</literal> with an explicit column definition list.
+
+<programlisting>
+-- SRF WITH ORDINALITY
+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>
+
  </sect1>
 
  <sect1 id="functions-info">
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index b0cec14..224dd28 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -52,7 +52,8 @@ 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> [, ...] ] ) [ 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,18 +369,39 @@ 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
+        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, including the column added by <literal>ORDINALITY</literal> if present.
+      </para>
+
+      <para>
+        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.
+        class="parameter">data_type</replaceable> <optional>, ...
+        </>)</literal>.  The column definition list must match the
+        actual number and types of columns returned by the function.
+        <literal>ORDINALITY</literal> does not work in this case.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index fb5c199..3c6f5e3 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -158,6 +158,35 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 }
 
 /*
+ * 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).
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 24325fe..997bf70 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -25,7 +25,7 @@
 #include "executor/nodeFunctionscan.h"
 #include "funcapi.h"
 #include "nodes/nodeFuncs.h"
-
+#include "catalog/pg_type.h"
 
 static TupleTableSlot *FunctionNext(FunctionScanState *node);
 
@@ -42,10 +42,27 @@ static TupleTableSlot *FunctionNext(FunctionScanState *node);
 static TupleTableSlot *
 FunctionNext(FunctionScanState *node)
 {
-	TupleTableSlot *slot;
 	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,19 +81,55 @@ FunctionNext(FunctionScanState *node)
 		node->tuplestorestate = tuplestorestate =
 			ExecMakeTableFunctionResult(node->funcexpr,
 										node->ss.ps.ps_ExprContext,
-										node->tupdesc,
+										node->func_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;
+								   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 (ScanDirectionIsForward(direction))
+		node->ordinal++;
+	else
+		node->ordinal--;
+
+	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];
+		}
+
+		scanslot->tts_values[natts] = Int64GetDatumFast(node->ordinal);
+		scanslot->tts_isnull[natts] = false;
+
+		ExecStoreVirtualTuple(scanslot);
+	}
+
+	return scanslot;
 }
 
 /*
@@ -116,7 +169,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	FunctionScanState *scanstate;
 	Oid			funcrettype;
 	TypeFuncClass functypclass;
-	TupleDesc	tupdesc = NULL;
+	TupleDesc	func_tupdesc = NULL;
+	TupleDesc	scan_tupdesc = NULL;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & EXEC_FLAG_MARK));
@@ -148,6 +202,11 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	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,37 +223,48 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	 */
 	functypclass = get_expr_result_type(node->funcexpr,
 										&funcrettype,
-										&tupdesc);
+										&func_tupdesc);
 
 	if (functypclass == TYPEFUNC_COMPOSITE)
 	{
 		/* Composite data type, e.g. a table's row type */
-		Assert(tupdesc);
+		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 */
-		tupdesc = CreateTupleDescCopy(tupdesc);
+		func_tupdesc = CreateTupleDescCopy(func_tupdesc);
 	}
 	else if (functypclass == TYPEFUNC_SCALAR)
 	{
 		/* Base data type, i.e. scalar */
 		char	   *attname = strVal(linitial(node->funccolnames));
 
-		tupdesc = CreateTemplateTupleDesc(1, false);
-		TupleDescInitEntry(tupdesc,
+		func_tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(func_tupdesc,
 						   (AttrNumber) 1,
 						   attname,
 						   funcrettype,
 						   -1,
 						   0);
-		TupleDescInitEntryCollation(tupdesc,
+		TupleDescInitEntryCollation(func_tupdesc,
 									(AttrNumber) 1,
 									exprCollation(node->funcexpr));
 	}
 	else if (functypclass == TYPEFUNC_RECORD)
 	{
-		tupdesc = BuildDescFromLists(node->funccolnames,
-									 node->funccoltypes,
-									 node->funccoltypmods,
-									 node->funccolcollations);
+		func_tupdesc = BuildDescFromLists(node->funccolnames,
+										  node->funccoltypes,
+										  node->funccoltypmods,
+										  node->funccolcollations);
 	}
 	else
 	{
@@ -207,14 +277,36 @@ 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);
+	BlessTupleDesc(func_tupdesc);
 
-	scanstate->tupdesc = tupdesc;
-	ExecAssignScanType(&scanstate->ss, 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,6 +341,8 @@ ExecEndFunctionScan(FunctionScanState *node)
 	 */
 	ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
+	if (node->func_slot)
+		ExecClearTuple(node->func_slot);
 
 	/*
 	 * Release tuplestore resources
@@ -268,9 +362,13 @@ void
 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.
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bcc6496..71e3058 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -509,6 +509,7 @@ _copyFunctionScan(const FunctionScan *from)
 	COPY_NODE_FIELD(funccoltypes);
 	COPY_NODE_FIELD(funccoltypmods);
 	COPY_NODE_FIELD(funccolcollations);
+	COPY_SCALAR_FIELD(funcordinality);
 
 	return newnode;
 }
@@ -1983,6 +1984,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_NODE_FIELD(funccoltypes);
 	COPY_NODE_FIELD(funccoltypmods);
 	COPY_NODE_FIELD(funccolcollations);
+	COPY_SCALAR_FIELD(funcordinality);
 	COPY_NODE_FIELD(values_lists);
 	COPY_NODE_FIELD(values_collations);
 	COPY_STRING_FIELD(ctename);
@@ -2296,6 +2298,7 @@ _copyRangeFunction(const RangeFunction *from)
 {
 	RangeFunction *newnode = makeNode(RangeFunction);
 
+	COPY_SCALAR_FIELD(ordinality);
 	COPY_SCALAR_FIELD(lateral);
 	COPY_NODE_FIELD(funccallnode);
 	COPY_NODE_FIELD(alias);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7f9737e..3183ccf 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2126,6 +2126,7 @@ _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
 static bool
 _equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
 {
+	COMPARE_SCALAR_FIELD(ordinality);
 	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_NODE_FIELD(funccallnode);
 	COMPARE_NODE_FIELD(alias);
@@ -2234,6 +2235,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_NODE_FIELD(funccoltypes);
 	COMPARE_NODE_FIELD(funccoltypmods);
 	COMPARE_NODE_FIELD(funccolcollations);
+	COMPARE_SCALAR_FIELD(funcordinality);
 	COMPARE_NODE_FIELD(values_lists);
 	COMPARE_NODE_FIELD(values_collations);
 	COMPARE_STRING_FIELD(ctename);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0f8a282..ea1e88d 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -153,12 +153,12 @@ makeWholeRowVar(RangeTblEntry *rte,
 			break;
 		case RTE_FUNCTION:
 			toid = exprType(rte->funcexpr);
-			if (type_is_rowtype(toid))
+			if (type_is_rowtype(toid) || rte->funcordinality)
 			{
 				/* func returns composite; same as relation case */
 				result = makeVar(varno,
 								 InvalidAttrNumber,
-								 toid,
+								 (rte->funcordinality) ? RECORDOID : toid,
 								 -1,
 								 InvalidOid,
 								 varlevelsup);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 48cd9dc..bad2239 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -521,6 +521,7 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
 	WRITE_NODE_FIELD(funccoltypes);
 	WRITE_NODE_FIELD(funccoltypmods);
 	WRITE_NODE_FIELD(funccolcollations);
+	WRITE_BOOL_FIELD(funcordinality);
 }
 
 static void
@@ -2382,6 +2383,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_NODE_FIELD(funccoltypes);
 			WRITE_NODE_FIELD(funccoltypmods);
 			WRITE_NODE_FIELD(funccolcollations);
+			WRITE_BOOL_FIELD(funcordinality);
 			break;
 		case RTE_VALUES:
 			WRITE_NODE_FIELD(values_lists);
@@ -2614,6 +2616,7 @@ _outRangeFunction(StringInfo str, const RangeFunction *node)
 {
 	WRITE_NODE_TYPE("RANGEFUNCTION");
 
+	WRITE_BOOL_FIELD(ordinality);
 	WRITE_BOOL_FIELD(lateral);
 	WRITE_NODE_FIELD(funccallnode);
 	WRITE_NODE_FIELD(alias);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index dc9cb3e..aad63e5 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1223,6 +1223,7 @@ _readRangeTblEntry(void)
 			READ_NODE_FIELD(funccoltypes);
 			READ_NODE_FIELD(funccoltypmods);
 			READ_NODE_FIELD(funccolcollations);
+			READ_BOOL_FIELD(funcordinality);
 			break;
 		case RTE_VALUES:
 			READ_NODE_FIELD(values_lists);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 7fed5e9..617cb19 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -115,8 +115,8 @@ 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,
+				  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,6 +1733,7 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
 
 	scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
 								  funcexpr,
+								  rte->funcordinality,
 								  rte->eref->colnames,
 								  rte->funccoltypes,
 								  rte->funccoltypmods,
@@ -3366,6 +3367,7 @@ make_functionscan(List *qptlist,
 				  List *qpqual,
 				  Index scanrelid,
 				  Node *funcexpr,
+				  bool ordinality,
 				  List *funccolnames,
 				  List *funccoltypes,
 				  List *funccoltypmods,
@@ -3381,6 +3383,7 @@ make_functionscan(List *qptlist,
 	plan->righttree = NULL;
 	node->scan.scanrelid = scanrelid;
 	node->funcexpr = funcexpr;
+	node->funcordinality = ordinality;
 	node->funccolnames = funccolnames;
 	node->funccoltypes = funccoltypes;
 	node->funccoltypmods = funccoltypmods;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 7ec6b0b..2947801 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4508,10 +4508,15 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 	 */
 	check_stack_depth();
 
+	/* Fail if the caller wanted ORDINALITY - we don't implement that here. */
+	if (rte->funcordinality)
+		return NULL;
+
 	/* Fail if FROM item isn't a simple FuncExpr */
 	fexpr = (FuncExpr *) rte->funcexpr;
 	if (fexpr == NULL || !IsA(fexpr, FuncExpr))
 		return NULL;
+
 	func_oid = fexpr->funcid;
 
 	/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d8d2bdf..22e82ba 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -566,7 +566,7 @@ 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
+	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
@@ -609,8 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * list and so can never be entered directly.  The filter in parser.c
  * creates these tokens when required.
  */
-%token			NULLS_FIRST NULLS_LAST WITH_TIME
-
+%token			NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
 
 /* Precedence: lowest to highest */
 %nonassoc	SET				/* see relation_expr_opt_alias */
@@ -9588,20 +9587,42 @@ table_ref:	relation_expr opt_alias_clause
 				{
 					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);
@@ -12575,6 +12596,7 @@ unreserved_keyword:
 			| OPERATOR
 			| OPTION
 			| OPTIONS
+			| ORDINALITY
 			| OVER
 			| OWNED
 			| OWNER
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a9254c8..9d0f3b6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -797,7 +797,7 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
  * physical column numbers.
  */
 static void
-buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
+buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality)
 {
 	int			maxattrs = tupdesc->natts;
 	ListCell   *aliaslc;
@@ -849,6 +849,25 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
 		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,43 +890,42 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
  */
 static void
 buildScalarFunctionAlias(Node *funcexpr, char *funcname,
-						 Alias *alias, Alias *eref)
+						 Alias *alias, Alias *eref, bool ordinality)
 {
-	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)
+		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);
-		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))
+	else
 	{
-		pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid);
-		if (pname)
-		{
-			eref->colnames = list_make1(makeString(pname));
-			return;
-		}
+		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));
 	}
 
-	/*
-	 * Otherwise use the previously-determined alias (not necessarily the
-	 * function name!)
-	 */
-	eref->colnames = list_make1(makeString(eref->aliasname));
+	if (ordinality && list_length(eref->colnames) < 2)
+		eref->colnames = lappend(eref->colnames, makeString(pstrdup("?column?")));
+
+	return;
 }
 
 /*
@@ -1003,7 +1021,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1063,7 +1081,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
 
 	/*
 	 * Set flags and access permissions.
@@ -1234,17 +1252,23 @@ addRangeTableEntryForFunction(ParseState *pstate,
 		/* Composite data type, e.g. a table's row type */
 		Assert(tupdesc);
 		/* Build the column alias list */
-		buildRelationAliases(tupdesc, alias, eref);
+		buildRelationAliases(tupdesc, alias, eref, rangefunc->ordinality);
 	}
 	else if (functypclass == TYPEFUNC_SCALAR)
 	{
 		/* Base data type, i.e. scalar */
-		buildScalarFunctionAlias(funcexpr, funcname, alias, eref);
+		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,6 +1311,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * permissions mechanism).
 	 */
 	rte->lateral = lateral;
+	rte->funcordinality = rangefunc->ordinality;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
@@ -1710,6 +1735,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 				TypeFuncClass functypclass;
 				Oid			funcrettype;
 				TupleDesc	tupdesc;
+				int         ordattno = 0;
 
 				functypclass = get_expr_result_type(rte->funcexpr,
 													&funcrettype,
@@ -1721,6 +1747,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					expandTupleDesc(tupdesc, rte->eref,
 									rtindex, sublevels_up, location,
 									include_dropped, colnames, colvars);
+					ordattno = tupdesc->natts + 1;
 				}
 				else if (functypclass == TYPEFUNC_SCALAR)
 				{
@@ -1741,6 +1768,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 
 						*colvars = lappend(*colvars, varnode);
 					}
+
+					ordattno = 2;
 				}
 				else if (functypclass == TYPEFUNC_RECORD)
 				{
@@ -1779,6 +1808,24 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					/* addRangeTableEntryForFunction should've caught this */
 					elog(ERROR, "function in FROM has unsupported return type");
 				}
+
+				/* tack on the extra ordinality column if present */
+				if (rte->funcordinality)
+				{
+					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,36 +2220,57 @@ 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 (rte->funcordinality && 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)));
 
-					/*
-					 * 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;
+						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);
+					if (rte->funcordinality && 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,7 +2390,11 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 				Oid			funcrettype = exprType(rte->funcexpr);
 				Oid			funcrelid = typeidTypeRelid(funcrettype);
 
-				if (OidIsValid(funcrelid))
+				if (rte->funcordinality && attnum == list_length(rte->eref->colnames))
+				{
+					result = false;
+				}
+				else if (OidIsValid(funcrelid))
 				{
 					/*
 					 * Composite data type, i.e. a table's row type
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index b8ec790..541d364 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -133,7 +133,7 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 		case WITH:
 
 			/*
-			 * WITH TIME must be reduced to one token
+			 * WITH TIME and WITH ORDINALITY must each be reduced to one token
 			 */
 			cur_yylval = lvalp->core_yystype;
 			cur_yylloc = *llocp;
@@ -143,6 +143,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 				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;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 40b565a..7bbd17d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7986,6 +7986,8 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			case RTE_FUNCTION:
 				/* Function RTE */
 				get_rule_expr(rte->funcexpr, context, true);
+				if (rte->funcordinality)
+					appendStringInfoString(buf, " WITH ORDINALITY");
 				break;
 			case RTE_VALUES:
 				/* Values list RTE */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 51c5575..49226b7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -87,6 +87,7 @@ extern TupleDesc CreateTupleDesc(int natts, bool hasoid,
 				Form_pg_attribute *attrs);
 
 extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
+extern TupleDesc CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts);
 
 extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 298af26..9b4be32 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1395,7 +1395,10 @@ typedef struct SubqueryScanState
  *		function appearing in FROM (typically a function returning set).
  *
  *		eflags				node's capability flags
- *		tupdesc				expected return tuple description
+ *		ordinal				column for WITH ORDINALITY
+ *		scan_tupdesc		scan tuple descriptor 
+ *		func_tupdesc		function tuple descriptor 
+ *		func_slot			function slot
  *		tuplestorestate		private state of tuplestore.c
  *		funcexpr			state for function expression being evaluated
  * ----------------
@@ -1404,7 +1407,10 @@ typedef struct FunctionScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
 	int			eflags;
-	TupleDesc	tupdesc;
+	int64       ordinal;
+	TupleDesc	scan_tupdesc;
+	TupleDesc	func_tupdesc;
+	TupleTableSlot *func_slot;
 	Tuplestorestate *tuplestorestate;
 	ExprState  *funcexpr;
 } FunctionScanState;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9415e2c..29631a2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -471,6 +471,7 @@ typedef struct RangeFunction
 {
 	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
@@ -754,6 +755,7 @@ typedef struct RangeTblEntry
 	List	   *funccoltypes;	/* OID list of column type OIDs */
 	List	   *funccoltypmods; /* integer list of column typmods */
 	List	   *funccolcollations;		/* OID list of column collation OIDs */
+	bool		funcordinality;	/* is this called WITH ORDINALITY? */
 
 	/*
 	 * Fields valid for a values RTE (else NIL):
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aa4f12c..44ea0b7 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -425,6 +425,7 @@ typedef struct FunctionScan
 {
 	Scan		scan;
 	Node	   *funcexpr;		/* expression tree for func call */
+	bool        funcordinality; /* WITH ORDINALITY */
 	List	   *funccolnames;	/* output column names (string Value nodes) */
 	List	   *funccoltypes;	/* OID list of column type OIDs */
 	List	   *funccoltypmods; /* integer list of column typmods */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 287f78e..8bd34d6 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -269,6 +269,7 @@ PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD)
 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, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 1678277..9028c03 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -18,7 +18,131 @@ 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;
+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;
+-- ordinality vs. rewind and reverse scan
+begin;
+declare foo scroll cursor for select * from generate_series(1,5) with ordinality as g(i,o);
+fetch all from foo;
+ i | o 
+---+---
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+(5 rows)
+
+fetch backward all from foo;
+ i | o 
+---+---
+ 5 | 5
+ 4 | 4
+ 3 | 3
+ 2 | 2
+ 1 | 1
+(5 rows)
+
+fetch all from foo;
+ i | o 
+---+---
+ 1 | 1
+ 2 | 2
+ 3 | 3
+ 4 | 4
+ 5 | 5
+(5 rows)
+
+fetch next from foo;
+ i | o 
+---+---
+(0 rows)
+
+fetch next from foo;
+ i | o 
+---+---
+(0 rows)
+
+fetch prior from foo;
+ i | o 
+---+---
+ 5 | 5
+(1 row)
+
+fetch absolute 1 from foo;
+ i | o 
+---+---
+ 1 | 1
+(1 row)
+
+commit;
 -- function with implicit LATERAL
 select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
  fooid | f2  | fooid | f2  
@@ -28,6 +152,15 @@ select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
      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,6 +206,12 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +219,14 @@ SELECT * FROM vw_getfoo;
       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,6 +238,13 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +253,15 @@ SELECT * FROM vw_getfoo;
       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,6 +273,13 @@ SELECT * FROM getfoo(1) AS t1;
  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,6 +288,15 @@ SELECT * FROM vw_getfoo;
  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,6 +307,12 @@ SELECT * FROM getfoo(1) AS t1;
      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,6 +320,14 @@ SELECT * FROM vw_getfoo;
      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,6 +339,13 @@ SELECT * FROM getfoo(1) AS t1;
      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,6 +354,16 @@ SELECT * FROM vw_getfoo;
      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,6 +412,12 @@ SELECT * FROM getfoo(1) AS t1;
   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,6 +425,14 @@ SELECT * FROM vw_getfoo;
       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,6 +443,12 @@ SELECT * FROM getfoo(1) AS t1;
      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,181 +457,622 @@ SELECT * FROM vw_getfoo;
 (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 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
+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)
 
-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
+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)
 
-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
+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)
 
-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
+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)
 
-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;
+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,6 +1548,13 @@ SELECT * FROM get_users();
  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;
diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql
index f1a405a..d522cdb 100644
--- a/src/test/regress/sql/rangefuncs.sql
+++ b/src/test/regress/sql/rangefuncs.sql
@@ -5,11 +5,40 @@ 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;
+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;
+-- ordinality vs. rewind and reverse scan
+begin;
+declare foo scroll cursor for select * from generate_series(1,5) with ordinality as g(i,o);
+fetch all from foo;
+fetch backward all from foo;
+fetch all from foo;
+fetch next from foo;
+fetch next from foo;
+fetch prior from foo;
+fetch absolute 1 from foo;
+commit;
 
 -- 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,41 +59,62 @@ INSERT INTO foo VALUES(2,1,'Mary');
 -- 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,16 +138,24 @@ DROP VIEW vw_getfoo;
 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,99 +164,85 @@ 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');
+CREATE TEMPORARY SEQUENCE foo_rescan_seq;
+CREATE TYPE foo_rescan_t AS (i integer, s bigint);
 
-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');
+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;
 
-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');
+--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
 
-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');
+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;
 
-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');
+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;
 
-CREATE FUNCTION foorescan(int,int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid >= $1 and fooid < $2 ;' LANGUAGE SQL;
+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;
 
---invokes ExecReScanFunctionScan
-SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;
+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;
 
-CREATE VIEW vw_foorescan AS SELECT * FROM foorescan(5002,5004);
+--invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL)
 
---invokes ExecReScanFunctionScan
-SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2;
+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);
 
-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);
+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);
 
-CREATE FUNCTION foorescan(int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid = $1;' LANGUAGE SQL;
+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);
 
---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;
+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);
 
-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;
+-- deep nesting
 
-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;
+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 VIEW vw_foorescan;
-DROP VIEW fooview1;
-DROP VIEW fooview2;
-DROP FUNCTION foorescan(int,int);
-DROP FUNCTION foorescan(int);
-DROP TABLE foorescan;
-DROP TABLE barrescan;
+DROP FUNCTION foo_sql(int,int);
+DROP FUNCTION foo_mat(int,int);
+DROP SEQUENCE foo_rescan_seq;
 
 --
 -- Test cases involving OUT parameters
@@ -414,6 +458,7 @@ language sql stable;
 
 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();
#26Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Dean Rasheed (#1)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Tom Lane said:

I haven't read this patch, but that comment scares the heck out of me.
Even if the patch isn't broken today, it will be tomorrow, if it's
making random changes like that in data structure semantics.
Also, if you're confused, so will be everybody else who has to deal with
the code later --- so I don't think fixing the comments and variable
names is optional.

I must admit to finding all of this criticism of unread code a bit
bizarre.

There are no "random changes in data structure semantics". All that
happens is that FunctionScan, in the ordinality case, has two tupdescs
to deal with: the one for the function return, and the one for
FunctionScan's own scan type. Likewise two slots, one of each type.
Absolutely no liberties are taken with any of the semantics. However,
since the scan structure already has a place for the scan result slot,
the "extra" slot that we allocate for this case is the function
result, func_slot, while in the non-ordinality case, we use the scan
result slot for the function result too.

[Greg: we just found a bug (actually two, one code + one docs); I
think David just posted the fixed version. And ironically, the bug in
the code has nothing to do with all of this discussion.]

Here, to hopefully end the issue, is the new version of FunctionNext,
which is the core of the whole patch (everything else is just setup
for this). If anyone wants to point out exactly what is unclear, or
which changes any semantics improperly, then please do indicate
exactly what you are referring to.

/* ----------------------------------------------------------------
* FunctionNext
*
* This is a workhorse for ExecFunctionScan
* ----------------------------------------------------------------
*/
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
*/
estate = node->ss.ps.state;
direction = estate->es_direction;

tuplestorestate = node->tuplestorestate;

/*
* If first time through, read all tuples from function and put them in a
* tuplestore. Subsequent calls just fetch tuples from tuplestore.
*/
if (tuplestorestate == NULL)
{
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 (ScanDirectionIsForward(direction))
node->ordinal++;
else
node->ordinal--;

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];
}

scanslot->tts_values[natts] = Int64GetDatumFast(node->ordinal);
scanslot->tts_isnull[natts] = false;

ExecStoreVirtualTuple(scanslot);
}

return scanslot;
}

--
Andrew (irc:RhodiumToad)

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

#27Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Gierth (#26)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Andrew Gierth <andrew@tao11.riddles.org.uk> writes:

I must admit to finding all of this criticism of unread code a bit
bizarre.

If you yourself are still finding bugs in the code as of this afternoon,
onlookers could be forgiven for doubting that the code is quite as
readable or ready to commit as all that.

regards, tom lane

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

#28Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#27)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 23 July 2013 06:10, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Andrew Gierth <andrew@tao11.riddles.org.uk> writes:

I must admit to finding all of this criticism of unread code a bit
bizarre.

If you yourself are still finding bugs in the code as of this afternoon,
onlookers could be forgiven for doubting that the code is quite as
readable or ready to commit as all that.

I had another look at this --- the bug fix looks reasonable, and
includes a sensible set of additional regression tests.

This was not a bug that implies anything fundamentally wrong with the
patch. Rather it was just a fairly trivial easy-to-overlook bug of
omission --- I certainly overlooked it in my previous reviews (sorry)
and at least 3 more experienced hackers than me overlooked it during
detailed code inspection.

I don't think that really reflects negatively on the quality of the
patch, or the approach taken, which I still think is good. That's also
backed up by the fact that Greg isn't able to find much he wants to
change.

I also don't see much that needs changing in the patch, except
possibly in the area where Greg expressed concerns over the comments
and code clarity. I had similar concerns during my inital review, so I
tend to agree that perhaps it's worth adding a separate boolean
has_ordinality flag to FunctionScanState just to improve code
readability. FWIW, I would probably have extended FunctionScanState
like so:

/* ----------------
* FunctionScanState information
*
* Function nodes are used to scan the results of a
* function appearing in FROM (typically a function returning set).
*
* eflags node's capability flags
* tupdesc node's expected return tuple description
* tuplestorestate private state of tuplestore.c
* funcexpr state for function expression being evaluated
* has_ordinality function scan WITH ORDINALITY?
* func_tupdesc for WITH ORDINALITY, the raw function tuple
* description, without the added ordinality column
* func_slot for WITH ORDINALITY, a slot for the raw function
* return tuples
* ordinal for WITH ORDINALITY, the ordinality of the return
* tuple
* ----------------
*/
typedef struct FunctionScanState
{
ScanState ss; /* its first field is NodeTag */
int eflags;
TupleDesc tupdesc;
Tuplestorestate *tuplestorestate;
ExprState *funcexpr;
bool has_ordinality;
/* these fields are used for a function scan WITH ORDINALITY */
TupleDesc func_tupdesc;
TupleTableSlot *func_slot;
int64 ordinal;
} FunctionScanState;

Regards,
Dean

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

#29Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Dean Rasheed (#1)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Tom Lane said:

If you yourself are still finding bugs in the code as of this afternoon,
onlookers could be forgiven for doubting that the code is quite as
readable or ready to commit as all that.

Right, and we all know that all code is perfect when committed. sheesh.

(This is actually the first time in six months that I've had occasion
to look at that part of the code; that's how long it's been sitting in
the queue. And yes, it was one of my bits, not David's. Maybe I
should have left the bug in to see how long it took you to spot it?)

What I'm very notably not seeing from you is any substantive feedback.
You've repeatedly described this patch as broken on the basis of
nothing more than garbled hearsay while loudly proclaiming not to have
actually looked at it; I find this both incomprehensible and insulting.
If you have legitimate technical concerns then let's hear them, now.

--
Andrew (irc:RhodiumToad)

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

#30Stephen Frost
sfrost@snowman.net
In reply to: Andrew Gierth (#29)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Andrew,

* Andrew Gierth (andrew@tao11.riddles.org.uk) wrote:

Right, and we all know that all code is perfect when committed. sheesh.

That clearly wasn't what was claimed.

(This is actually the first time in six months that I've had occasion
to look at that part of the code; that's how long it's been sitting in
the queue.

While such issues are frustrating for all of us, huffing about it here
isn't particularly useful.

And yes, it was one of my bits, not David's. Maybe I
should have left the bug in to see how long it took you to spot it?)

That attitude is certainly discouraging.

What I'm very notably not seeing from you is any substantive feedback.
You've repeatedly described this patch as broken on the basis of
nothing more than garbled hearsay while loudly proclaiming not to have
actually looked at it; I find this both incomprehensible and insulting.

As Greg is the one looking to possibly commit this, I certainly didn't
consider his comments on the patch to be garbled hearsay. It would have
been great if he had been more clear in his original comments, but I
don't feel that you can fault any of us for reading his email and
voicing what concerns we had from his review. While you might wish that
we all read every patch submitted, none of us has time for that- simply
keeping up with this mailing list requires a significant amount of time.

If you have legitimate technical concerns then let's hear them, now.

Fine- I'd have been as happy leaving this be and letting Greg commit it,
but if you'd really like to hear my concerns, I'd start with pointing
out that it's pretty horrid to have to copy every record around like
this and that the hack of CreateTupleDescCopyExtend is pretty terrible
and likely to catch people by surprise. Having FunctionNext() operate
very differently depending on WITH ORDINALITY is ugly and the cause of
the bug that was found. All-in-all, I'm not convinced that this is
really a good approach and feel it'd be better off implemented at a
different level, eg a node type instead of a hack on top of the existing
SRF code.

Now, what would be great to see would be your response to Dean's
comments and suggestions rather than berating someone who's barely
written 5 sentences on this whole thread.

Thanks,

Stephen

#31Greg Stark
stark@mit.edu
In reply to: Stephen Frost (#30)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Tue, Jul 23, 2013 at 9:27 PM, Stephen Frost <sfrost@snowman.net> wrote:

Fine- I'd have been as happy leaving this be and letting Greg commit it,
but if you'd really like to hear my concerns, I'd start with pointing
out that it's pretty horrid to have to copy every record around like
this and that the hack of CreateTupleDescCopyExtend is pretty terrible
and likely to catch people by surprise. Having FunctionNext() operate
very differently depending on WITH ORDINALITY is ugly and the cause of
the bug that was found. All-in-all, I'm not convinced that this is
really a good approach and feel it'd be better off implemented at a
different level, eg a node type instead of a hack on top of the existing
SRF code.

Fwiw I've been mulling over the same questions and came to the
conclusion that the existing approach makes the most sense.

In an ideal world an extra executor node would be the prettiest,
cleanest implementation. But the amount of extra code and busywork
that would necessitate just isn't justified for the amount of work it
would be doing.

It might be different if WITH ORDINALITY made sense for any other
types of target tables. But it really only makes sense for SRFs. The
whole motivation for having them in the spec is that UNNEST is taking
an ordered list and turning it into a relation which is unordered. You
can't just do row_number() because there's nothing to make it ordered
by.

By the same token for any other data source you would just use
row_number *precisely because* any other data source is unordered and
you should have to specify an order in order to make row_number
produce something meaningful.

So given that WITH ORDINALITY is really only useful for UNNEST and we
can generalize it to all SRFs on the basis that Postgres SRFs do
produce ordered sets not unordered relations it isn't crazy for the
work to be in the Function node.

Now that I've written that though it occurs to me to wonder whether
FDW tables might be usefully said to be ordered too though?

--
greg

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

#32Stephen Frost
sfrost@snowman.net
In reply to: Greg Stark (#31)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

* Greg Stark (stark@mit.edu) wrote:

So given that WITH ORDINALITY is really only useful for UNNEST and we
can generalize it to all SRFs on the basis that Postgres SRFs do
produce ordered sets not unordered relations it isn't crazy for the
work to be in the Function node.

I agree, it isn't *crazy*. :)

Now that I've written that though it occurs to me to wonder whether
FDW tables might be usefully said to be ordered too though?

My thought around this was a VALUES() construct, but FDWs are an
interesting case to consider also; with FDWs it's possible that
something is said in SQL/MED regarding this question- perhaps it would
make sense to look there?

Thanks,

Stephen

#33David Fetter
david@fetter.org
In reply to: Stephen Frost (#32)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Tue, Jul 23, 2013 at 05:23:17PM -0400, Stephen Frost wrote:

* Greg Stark (stark@mit.edu) wrote:

So given that WITH ORDINALITY is really only useful for UNNEST and we
can generalize it to all SRFs on the basis that Postgres SRFs do
produce ordered sets not unordered relations it isn't crazy for the
work to be in the Function node.

I agree, it isn't *crazy*. :)

Now that I've written that though it occurs to me to wonder whether
FDW tables might be usefully said to be ordered too though?

My thought around this was a VALUES() construct, but FDWs are an
interesting case to consider also; with FDWs it's possible that
something is said in SQL/MED regarding this question- perhaps it would
make sense to look there?

There are a lot of ways foreign tables don't yet act like local ones.
Much as I'm a booster for fixing that problem, I'm thinking
improvements in this direction are for a separate patch.

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

#34Stephen Frost
sfrost@snowman.net
In reply to: David Fetter (#33)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

David

On Tuesday, July 23, 2013, David Fetter wrote:

There are a lot of ways foreign tables don't yet act like local ones.
Much as I'm a booster for fixing that problem, I'm thinking
improvements in this direction are for a separate patch.

This isn't about making FDWs more like local tables- indeed, quite the
opposite. The question is if we should declare that WITH ORDINALITY will
only ever be for SRFs or if we should consider that it might be useful with
FDWs specifically because they are not unordered sets as tables are.
Claiming that question is unrelated to the implementation of WITH
ORDINALITY is rather... Bizarre.

Thanks,

Stephen

#35David Fetter
david@fetter.org
In reply to: Stephen Frost (#34)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Tue, Jul 23, 2013 at 06:09:20PM -0400, Stephen Frost wrote:

David

On Tuesday, July 23, 2013, David Fetter wrote:

There are a lot of ways foreign tables don't yet act like local
ones. Much as I'm a booster for fixing that problem, I'm thinking
improvements in this direction are for a separate patch.

This isn't about making FDWs more like local tables- indeed, quite
the opposite. The question is if we should declare that WITH
ORDINALITY will only ever be for SRFs or if we should consider that
it might be useful with FDWs specifically because they are not
unordered sets as tables are. Claiming that question is unrelated
to the implementation of WITH ORDINALITY is rather... Bizarre.

Are you saying that there's stuff that if I don't put it in now will
impede our ability to add this to FTs later?

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

#36Stephen Frost
sfrost@snowman.net
In reply to: David Fetter (#35)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Tuesday, July 23, 2013, David Fetter wrote:

Are you saying that there's stuff that if I don't put it in now will
impede our ability to add this to FTs later?

I'm saying that it'd be a completely different implementation and that this
one would get in the way and essentially have to be ripped out.

No one is saying that this patch wouldn't work for the specific use-case
that it set out to meet, and maybe it's unfair for us to consider possible
use-cases beyond the patch's goal and the spec requirement, but that, IMO,
is also one of the things that makes PG great. MVCC isn't necessary and
isn't required by spec either.

Thanks,

Stephen

#37Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Dean Rasheed (#1)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Stephen Frost said:

[stuff about foreign tables]

I think the issue with foreign tables is probably moot because if you
_did_ want to have some types of foreign tables with a fixed
ordinality, you'd probably also want qual pushdown to work for it
(i.e. so that WHERE rownum=123 doesn't have to filter all the rows),
whereas with SRFs this doesn't really apply.

For this to work, foreign tables with a fixed ordering would have to
provide that in the FDW - which is in any case the only place that
knows whether a fixed order would even make any sense.

So I see no overlap here with the SRF ordinality case.

As for VALUES, the spec regards those as constructing a table and
therefore not having any inherent order - the user can add their own
ordinal column if need be. Even if you did want to add WITH ORDINALITY
for VALUES, though, it would actually make more sense to do it in the
Values Scan node since that maintains its own ordinal position already.

--
Andrew (irc:RhodiumToad)

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

#38Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephen Frost (#30)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Stephen Frost <sfrost@snowman.net> writes:

* Andrew Gierth (andrew@tao11.riddles.org.uk) wrote:

If you have legitimate technical concerns then let's hear them, now.

Fine- I'd have been as happy leaving this be and letting Greg commit it,
but if you'd really like to hear my concerns, I'd start with pointing
out that it's pretty horrid to have to copy every record around like
this and that the hack of CreateTupleDescCopyExtend is pretty terrible
and likely to catch people by surprise. Having FunctionNext() operate
very differently depending on WITH ORDINALITY is ugly and the cause of
the bug that was found. All-in-all, I'm not convinced that this is
really a good approach and feel it'd be better off implemented at a
different level, eg a node type instead of a hack on top of the existing
SRF code.

I took the time to read through this patch, finally. i think the $64
question is pretty much what Stephen says above: do we like tying this
behavior to FunctionScan, as opposed to having it be some kind of
expression node? You could certainly imagine a WithOrdinality
expression node that takes in values of a set-returning expression,
and returns them with an extra column tacked on. This would resolve
the problem that was mentioned awhile back that the current approach
can't support SRFs in targetlists.

If it weren't that we've been speculating for years about deprecating
SRFs-in-tlists once we had LATERAL, I would personally consider this
patch DOA in this form. If we do think we'll probably deprecate that
feature, then not extending WITH ORDINALITY to such cases is at least
defensible. On the other hand, considering that we've yet to ship a
release supporting LATERAL, it's probably premature to commit to such
deprecation --- we don't really know whether people will find LATERAL
to be a convenient and performant substitute.

As far as performance goes, despite Stephen's gripe above, I think this
way is likely better than any alternative. The reason is that once
we've disassembled the function result tuple and tacked on the counter,
we have a reasonable shot at things staying like that (in the form of
a virtual TupleTableSlot). The next higher level of evaluation can
probably use the column Datums as-is. A WithOrdinality expression node
would have to disassemble the input tuple and then make a new tuple,
which the next higher expression level would likely pull apart again :-(.
Also, any other approach would lead to needing to store the ordinality
values inside the FunctionScan's tuplestore, bloating that storage with
rather-low-value data.

The other big technical issue I see is representation of the rowtype of
the result. If we did it with a WithOrdinality expression node, the
result would always be of type RECORD, and we'd have to use blessed tuple
descriptors to keep track of exactly which record type a particular
instance emits. This is certainly do-able (see RowExpr for precedent).
Attaching the functionality to FunctionScan reduces the need for that
because the system mostly avoids trying to associate any type OID with
the rowtype of a FROM item. Instead though, we've got a lot of ad-hoc
code that deals with RangeTblEntry type information. A big part of the
patch is dealing with extending that code, and frankly I've got about
zero confidence that the patch has found everything that needs to be
found in that line. A patch using an expression node would probably
need to touch only a much more localized set of places to handle the
type description issue.

Anyway, on balance I'm satisfied with this overall approach, though it's
not without room for debate.

I am fairly dissatisfied with the patch at a stylistic level, though.
It seems way too short on comments. People griped about the code in
nodeFunctionscan in particular, but I think all the changes in ad-hoc
type-management code elsewhere are even more deserving of comments,
and they mostly didn't get that. Likewise, there's no documentation
anywhere that I can see of the new interrelationships of struct fields,
such as that if a RangeTblEntry has funcordinality set, then its various
column-related fields such as eref->colnames include a trailing INT8
column for the ordinality. Also, maybe I'm misreading the patch (have
I mentioned lately that large patches in -u format are practically
illegible?), but it sure looks like it flat out removed several existing
regression-test cases and a few existing comments as well. How is that
considered acceptable?

FWIW, I concur with the gripe I remember seeing upthread that the
default name of the added column ought not be "?column?".

regards, tom lane

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

#39Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#38)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Tue, Jul 23, 2013 at 9:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

If it weren't that we've been speculating for years about deprecating
SRFs-in-tlists once we had LATERAL, I would personally consider this
patch DOA in this form. If we do think we'll probably deprecate that
feature, then not extending WITH ORDINALITY to such cases is at least
defensible. On the other hand, considering that we've yet to ship a
release supporting LATERAL, it's probably premature to commit to such
deprecation --- we don't really know whether people will find LATERAL
to be a convenient and performant substitute.

I guess I'd sort of assumed that the plan was to continue accepting
SRFs in tlists but rewrite them as lateral joins, rather than getting
rid of them altogether. IIUC that would simplify some things inside
the executor. I'd be a bit more reluctant to just ban SRFs in target
lists outright.

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

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

#40Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#39)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Jul 23, 2013 at 9:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

If it weren't that we've been speculating for years about deprecating
SRFs-in-tlists once we had LATERAL, I would personally consider this
patch DOA in this form.

I guess I'd sort of assumed that the plan was to continue accepting
SRFs in tlists but rewrite them as lateral joins, rather than getting
rid of them altogether.

That seems to me to be unlikely to happen, because it would be
impossible to preserve the current (admittedly bad) semantics.
If we're going to change the behavior at all we might as well just
drop the feature, IMO.

regards, tom lane

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

#41Robert Haas
robertmhaas@gmail.com
In reply to: Greg Stark (#20)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Fri, Jul 19, 2013 at 1:50 PM, Greg Stark <stark@mit.edu> wrote:

My only reservation with this patch is whether the WITH_ORDINALITY
parser hack is the way we want to go. The precedent was already set
with WITH TIME ZONE though and I think this was the best option.

I share this reservation. Lexer hacks are reasonable ways of getting
LALR(2)-ish behavior in very simple cases, but it doesn't take much to
get into trouble. I think the with ordinality as (select 1) select *
from ordinality example you posted is precisely on point. Currently,
we will have four classes of keywords: unreserved, column-name,
type-function, and reserved. There are rules for when each of those
types of keywords needs to be quoted, and those rules are relatively
well-understood.

This patch will introduce, without documentation, a fifth class of
keyword. ORDINALITY will need to be quoted when, and only when, it
immediately follows WITH. Without some change to our deparsing code,
this is a dump/restore hazard; and with some such change it's still
probably not a good idea.

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

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

#42Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#40)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Wed, Jul 24, 2013 at 1:36 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Jul 23, 2013 at 9:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

If it weren't that we've been speculating for years about deprecating
SRFs-in-tlists once we had LATERAL, I would personally consider this
patch DOA in this form.

I guess I'd sort of assumed that the plan was to continue accepting
SRFs in tlists but rewrite them as lateral joins, rather than getting
rid of them altogether.

That seems to me to be unlikely to happen, because it would be
impossible to preserve the current (admittedly bad) semantics.
If we're going to change the behavior at all we might as well just
drop the feature, IMO.

Maybe. I'd be kind of sad to lose some of the simple cases that work
now, like SELECT srf(), in favor of having to write SELECT * FROM
srf(). I'd probably get over it, but I'm sure a lot of people would
be mildly annoyed at having to change their working application code.

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

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

#43Greg Stark
stark@mit.edu
In reply to: Tom Lane (#40)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Wed, Jul 24, 2013 at 6:36 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

That seems to me to be unlikely to happen, because it would be
impossible to preserve the current (admittedly bad) semantics.
If we're going to change the behavior at all we might as well just
drop the feature, IMO.

It would be nice to support a single SRF in the target list. That
would side-step the bad semantics and also make it easier to
implement. But I'm not sure how easy it would be in practice because
I've learned not to underestimate the difficulty of making seemingly
small changes to the planner.

--
greg

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

#44Greg Stark
stark@mit.edu
In reply to: Robert Haas (#41)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Wed, Jul 24, 2013 at 6:39 PM, Robert Haas <robertmhaas@gmail.com> wrote:

This patch will introduce, without documentation, a fifth class of
keyword. ORDINALITY will need to be quoted when, and only when, it
immediately follows WITH. Without some change to our deparsing code,
this is a dump/restore hazard; and with some such change it's still
probably not a good idea.

Strictly speaking this patc doesn't introduce this fifth class of
keyword. We already had TIME in that category (and also FIRST and LAST
in a similar category following NULLS). If we have a solution for WITH
<keyword> then presumably we would implement it for WITH TIME and WITH
ORDINALITY at the same time.

In the interim I suppose we could teach pg_dump to quote any keyword
that follows WITH or NULLS pretty easily. Or just quote those four
words unconditionally.

--
greg

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

#45Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#44)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Greg Stark <stark@mit.edu> writes:

On Wed, Jul 24, 2013 at 6:39 PM, Robert Haas <robertmhaas@gmail.com> wrote:

This patch will introduce, without documentation, a fifth class of
keyword. ORDINALITY will need to be quoted when, and only when, it
immediately follows WITH. Without some change to our deparsing code,
this is a dump/restore hazard; and with some such change it's still
probably not a good idea.

Strictly speaking this patc doesn't introduce this fifth class of
keyword. We already had TIME in that category (and also FIRST and LAST
in a similar category following NULLS). If we have a solution for WITH
<keyword> then presumably we would implement it for WITH TIME and WITH
ORDINALITY at the same time.

It strikes me that we could hack the grammar for CTEs so that it allows
WITH_ORDINALITY and WITH_TIME as the first token, with appropriate
insertion of the proper identifier value. I'm not sure if there are any
other places where WITH can be followed by a random identifier.

I don't see any workable fix that doesn't involve the funny token, though.
Consider

CREATE VIEW v AS SELECT ... FROM UNNEST(...) WITH ORDINALITY;
CREATE VIEW v AS SELECT ... FROM UNNEST(...) WITH NO DATA;

WITH ORDINALITY really needs to be parsed as part of the FROM clause.
WITH NO DATA really needs to *not* be parsed as part of the FROM clause.
Without looking ahead more than one token, there is absolutely no way
for the grammar to decide if it's got the whole FROM clause or not
at the point where WITH is the next token. So our choices are to have
two-token lookahead at the lexer level, or to give up on bison and find
something that can implement a parsing algorithm better than LALR(1).
I know which one seems more likely to get done in the foreseeable future.

regards, tom lane

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

#46Andres Freund
andres@2ndquadrant.com
In reply to: Tom Lane (#40)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 2013-07-24 13:36:39 -0400, Tom Lane wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Jul 23, 2013 at 9:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

If it weren't that we've been speculating for years about deprecating
SRFs-in-tlists once we had LATERAL, I would personally consider this
patch DOA in this form.

I guess I'd sort of assumed that the plan was to continue accepting
SRFs in tlists but rewrite them as lateral joins, rather than getting
rid of them altogether.

That seems to me to be unlikely to happen, because it would be
impossible to preserve the current (admittedly bad) semantics.
If we're going to change the behavior at all we might as well just
drop the feature, IMO.

I think removing the feature will be a rather painful procedure for
users and thus will need a rather long deprecation period. The amount of
code using SRFs in targetlists is quite huge if my experience is
anything to go by.
And much of that can trivially/centrally be rewritten to LATERAL, not
to speak of the cross-version compatibility problem.

Greetings,

Andres Freund

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

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

#47Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Dean Rasheed (#1)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Tom Lane said:

If we did it with a WithOrdinality expression node, the result would
always be of type RECORD, and we'd have to use blessed tuple
descriptors to keep track of exactly which record type a particular
instance emits. This is certainly do-able (see RowExpr for
precedent).

Maybe RowExpr is a precedent for something, but it has this
long-standing problem that makes it very hard to use usefully:

postgres=# select (r).* from (select row(a,b) as r from (values (1,2)) v(a,b)) s;
ERROR: record type has not been registered

It seems way too short on comments. [...]

This can certainly be addressed.

but it sure looks like it flat out removed several existing
regression-test cases

Here's why, in rangefuncs.sql:

--invokes ExecReScanFunctionScan
SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;

I don't think that has invoked ExecReScanFunctionScan since 7.4 or so.
It certainly does not do so now (confirmed by gdb as well as by the
query plan). By all means keep the old tests if you want a
never-remove-tests-for-any-reason policy, but having added tests that
actually _do_ invoke ExecReScanFunctionScan, I figured the old ones
were redundant. (Also, these kinds of tests can be done a bit better
now with values and lateral rather than creating and dropping tables
just for the one test.)

and a few existing comments as well.

I've double-checked, and I don't see any existing comments removed.

FWIW, I concur with the gripe I remember seeing upthread that the
default name of the added column ought not be "?column?".

This seems to be a common complaint, but gives rise to two questions:

1) what should the name be?

2) should users be depending on it?

I've yet to find another db that actually documents a specific column
name for the ordinality column; it's always taken for granted that the
user should always be supplying an alias. (Admittedly there are not
many dbs that support it at all; DB2 does, and I believe Teradata.)

--
Andrew (irc:RhodiumToad)

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

#48David Fetter
david@fetter.org
In reply to: Andrew Gierth (#47)
1 attachment(s)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Wed, Jul 24, 2013 at 09:38:15PM +0000, Andrew Gierth wrote:

Tom Lane said:

If we did it with a WithOrdinality expression node, the result would
always be of type RECORD, and we'd have to use blessed tuple
descriptors to keep track of exactly which record type a particular
instance emits. This is certainly do-able (see RowExpr for
precedent).

Maybe RowExpr is a precedent for something, but it has this
long-standing problem that makes it very hard to use usefully:

postgres=# select (r).* from (select row(a,b) as r from (values (1,2)) v(a,b)) s;
ERROR: record type has not been registered

It seems way too short on comments. [...]

This can certainly be addressed.

but it sure looks like it flat out removed several existing
regression-test cases

Here's why, in rangefuncs.sql:

--invokes ExecReScanFunctionScan
SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;

I don't think that has invoked ExecReScanFunctionScan since 7.4 or so.
It certainly does not do so now (confirmed by gdb as well as by the
query plan). By all means keep the old tests if you want a
never-remove-tests-for-any-reason policy, but having added tests that
actually _do_ invoke ExecReScanFunctionScan, I figured the old ones
were redundant. (Also, these kinds of tests can be done a bit better
now with values and lateral rather than creating and dropping tables
just for the one test.)

and a few existing comments as well.

I've double-checked, and I don't see any existing comments removed.

FWIW, I concur with the gripe I remember seeing upthread that the
default name of the added column ought not be "?column?".

This seems to be a common complaint, but gives rise to two questions:

1) what should the name be?

2) should users be depending on it?

I've yet to find another db that actually documents a specific column
name for the ordinality column; it's always taken for granted that the
user should always be supplying an alias. (Admittedly there are not
many dbs that support it at all; DB2 does, and I believe Teradata.)

Next patch: changes by Andrew Gierth, testing vs up-to-date git master
by Yours Truly.

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_13.difftext/plain; charset=us-asciiDownload
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 13278,13284 **** select $1[i][j]
          generate_subscripts($1,2) g2(j);
  $$ LANGUAGE sql IMMUTABLE;
  CREATE FUNCTION
! postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
   unnest2 
  ---------
         1
--- 13278,13284 ----
          generate_subscripts($1,2) g2(j);
  $$ LANGUAGE sql IMMUTABLE;
  CREATE FUNCTION
! SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
   unnest2 
  ---------
         1
***************
*** 13289,13294 **** postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
--- 13289,13336 ----
  </programlisting>
    </para>
  
+   <indexterm>
+    <primary>ordinality</primary>
+   </indexterm>
+ 
+   <para>
+   When a function in the <literal>FROM</literal> clause is suffixed by
+   <literal>WITH ORDINALITY</literal>, a <type>bigint</type> column is appended to
+   the output which starts from 1 and increments by 1 for each row of
+   the function's output.  This is more useful in the case of
+   set-returning functions than of others.  This functionality is
+   available for functions returning composite types or using
+   <literal>OUT</literal> parameters, but not when using a function
+   returning <literal>RECORD</literal> with an explicit column definition list.
+ 
+ <programlisting>
+ -- SRF WITH ORDINALITY
+ 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>
+ 
   </sect1>
  
   <sect1 id="functions-info">
*** 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,407 ----
          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, including the column added by <literal>ORDINALITY</literal> if present.
!       </para>
! 
!       <para>
!         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.
!         <literal>ORDINALITY</literal> does not work in this case.
         </para>
        </listitem>
       </varlistentry>
*** a/src/backend/access/common/tupdesc.c
--- b/src/backend/access/common/tupdesc.c
***************
*** 158,163 **** CreateTupleDescCopy(TupleDesc tupdesc)
--- 158,197 ----
  }
  
  /*
+  * 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 (and therefore
+  *      does not inherit its typeid/typmod, which instead are left as an
+  *      anonymous record type).
+  *
+  *      The additional column slots are not initialized in any way;
+  *      callers must do their own TupleDescInitEntry on each.
+  *
+  * !!! 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,78 ----
  static TupleTableSlot *
  FunctionNext(FunctionScanState *node)
  {
  	EState	   *estate;
  	ScanDirection direction;
  	Tuplestorestate *tuplestorestate;
+ 	TupleTableSlot *scanslot;
+ 	TupleTableSlot *funcslot;
+ 
+ 	if (node->func_slot)
+ 	{
+ 		/*
+ 		 * ORDINALITY case:
+ 		 *
+ 		 * We fetch the function result into FUNCSLOT (which matches the
+ 		 * function return type), and then copy the values to SCANSLOT
+ 		 * (which matches the scan result type), setting the ordinal
+ 		 * column in the process.
+ 		 */
+ 
+ 		funcslot = node->func_slot;
+ 		scanslot = node->ss.ss_ScanTupleSlot;
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * non-ORDINALITY case: the function return type and scan result
+ 		 * type are the same, so we fetch the function result straight
+ 		 * into the scan result slot.
+ 		 */
+ 
+ 		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;
  }
  
  /*
--- 91,152 ----
  		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);
! 
! 	/*
! 	 * increment or decrement before checking for end-of-data, so that we can
! 	 * move off either end of the result by 1 (and no more than 1) without
! 	 * losing correct count. See PortalRunSelect for why we assume that we
! 	 * won't be called repeatedly in the end-of-data state.
! 	 */
! 
! 	if (ScanDirectionIsForward(direction))
! 		node->ordinal++;
! 	else
! 		node->ordinal--;
! 
! 	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];
! 		}
! 
! 		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));
--- 186,193 ----
  	FunctionScanState *scanstate;
  	Oid			funcrettype;
  	TypeFuncClass functypclass;
! 	TupleDesc	func_tupdesc = NULL;
! 	TupleDesc	scan_tupdesc = NULL;
  
  	/* check for unsupported flags */
  	Assert(!(eflags & EXEC_FLAG_MARK));
***************
*** 149,154 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
--- 220,235 ----
  	ExecInitScanTupleSlot(estate, &scanstate->ss);
  
  	/*
+ 	 * We only need a separate slot for the function result if we are doing
+ 	 * ordinality; otherwise, we fetch function results directly into the
+ 	 * scan slot.
+ 	 */
+ 	if (node->funcordinality)
+ 		scanstate->func_slot = ExecInitExtraTupleSlot(estate);
+ 	else
+ 		scanstate->func_slot = NULL;
+ 
+ 	/*
  	 * initialize child expressions
  	 */
  	scanstate->ss.ps.targetlist = (List *)
***************
*** 159,200 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
  					 (PlanState *) scanstate);
  
  	/*
! 	 * Now determine if the function returns a simple or composite type, and
! 	 * build an appropriate tupdesc.
  	 */
  	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
  	{
--- 240,294 ----
  					 (PlanState *) scanstate);
  
  	/*
! 	 * Now determine if the function returns a simple or composite
! 	 * type, and build an appropriate tupdesc. This tupdesc
! 	 * (func_tupdesc) is the one that matches the shape of the
! 	 * function result, no extra columns.
  	 */
  	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,221 **** 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);
  
--- 301,347 ----
  	 * function should do this for itself, but let's cover things in case it
  	 * doesn't.)
  	 */
! 	BlessTupleDesc(func_tupdesc);
  
! 	/*
! 	 * If doing ordinality, we need a new tupdesc with one additional column
! 	 * tacked on, always of type "bigint". The name to use has already been
! 	 * recorded by the parser as the last element of funccolnames.
! 	 *
! 	 * Without ordinality, the scan result tupdesc is the same as the
! 	 * function result tupdesc. (No need to make a copy.)
! 	 */
! 	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);
! 	}
! 	else
! 		scan_tupdesc = func_tupdesc;
! 
! 	scanstate->scan_tupdesc = scan_tupdesc;
! 	scanstate->func_tupdesc = func_tupdesc;
! 	ExecAssignScanType(&scanstate->ss, scan_tupdesc);
! 
! 	if (scanstate->func_slot)
! 		ExecSetSlotDescriptor(scanstate->func_slot, func_tupdesc);
  
  	/*
  	 * Other node-specific setup
  	 */
+ 	scanstate->ordinal = 0;
  	scanstate->tuplestorestate = NULL;
+ 
  	scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
  									   (PlanState *) scanstate);
  
***************
*** 249,254 **** ExecEndFunctionScan(FunctionScanState *node)
--- 375,382 ----
  	 */
  	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
--- 396,408 ----
  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
***************
*** 509,514 **** _copyFunctionScan(const FunctionScan *from)
--- 509,515 ----
  	COPY_NODE_FIELD(funccoltypes);
  	COPY_NODE_FIELD(funccoltypmods);
  	COPY_NODE_FIELD(funccolcollations);
+ 	COPY_SCALAR_FIELD(funcordinality);
  
  	return newnode;
  }
***************
*** 1983,1988 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 1984,1990 ----
  	COPY_NODE_FIELD(funccoltypes);
  	COPY_NODE_FIELD(funccoltypmods);
  	COPY_NODE_FIELD(funccolcollations);
+ 	COPY_SCALAR_FIELD(funcordinality);
  	COPY_NODE_FIELD(values_lists);
  	COPY_NODE_FIELD(values_collations);
  	COPY_STRING_FIELD(ctename);
***************
*** 2296,2301 **** _copyRangeFunction(const RangeFunction *from)
--- 2298,2304 ----
  {
  	RangeFunction *newnode = makeNode(RangeFunction);
  
+ 	COPY_SCALAR_FIELD(ordinality);
  	COPY_SCALAR_FIELD(lateral);
  	COPY_NODE_FIELD(funccallnode);
  	COPY_NODE_FIELD(alias);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2126,2131 **** _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
--- 2126,2132 ----
  static bool
  _equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
  {
+ 	COMPARE_SCALAR_FIELD(ordinality);
  	COMPARE_SCALAR_FIELD(lateral);
  	COMPARE_NODE_FIELD(funccallnode);
  	COMPARE_NODE_FIELD(alias);
***************
*** 2234,2239 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 2235,2241 ----
  	COMPARE_NODE_FIELD(funccoltypes);
  	COMPARE_NODE_FIELD(funccoltypmods);
  	COMPARE_NODE_FIELD(funccolcollations);
+ 	COMPARE_SCALAR_FIELD(funcordinality);
  	COMPARE_NODE_FIELD(values_lists);
  	COMPARE_NODE_FIELD(values_collations);
  	COMPARE_STRING_FIELD(ctename);
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 126,131 **** makeVarFromTargetEntry(Index varno,
--- 126,135 ----
   * returning a non-composite result type, we produce a normal Var referencing
   * the function's result directly, instead of the single-column composite
   * value that the whole-row notation might otherwise suggest.
+  *
+  * We also handle the specific case of function RTEs with ordinality,
+  * where the additional column has to be added. This forces the result
+  * to be composite and RECORD type.
   */
  Var *
  makeWholeRowVar(RangeTblEntry *rte,
***************
*** 151,159 **** makeWholeRowVar(RangeTblEntry *rte,
  							 InvalidOid,
  							 varlevelsup);
  			break;
  		case RTE_FUNCTION:
  			toid = exprType(rte->funcexpr);
! 			if (type_is_rowtype(toid))
  			{
  				/* func returns composite; same as relation case */
  				result = makeVar(varno,
--- 155,187 ----
  							 InvalidOid,
  							 varlevelsup);
  			break;
+ 
  		case RTE_FUNCTION:
+ 			/*
+ 			 * RTE is a function with or without ordinality. We map the
+ 			 * cases as follows:
+ 			 *
+ 			 * If ordinality is set, we return a composite var even if
+ 			 * the function is a scalar. This var is always of RECORD type.
+ 			 *
+ 			 * If ordinality is not set but the function returns a row,
+ 			 * we keep the function's return type.
+ 			 *
+ 			 * If the function is a scalar, we do what allowScalar requests.
+ 			 */
  			toid = exprType(rte->funcexpr);
! 
! 			if (rte->funcordinality)
! 			{
! 				/* ORDINALITY always produces an anonymous RECORD result */
! 				result = makeVar(varno,
! 								 InvalidAttrNumber,
! 								 RECORDOID,
! 								 -1,
! 								 InvalidOid,
! 								 varlevelsup);
! 			}
! 			else if (type_is_rowtype(toid))
  			{
  				/* func returns composite; same as relation case */
  				result = makeVar(varno,
***************
*** 184,191 **** makeWholeRowVar(RangeTblEntry *rte,
  								 varlevelsup);
  			}
  			break;
  		default:
- 
  			/*
  			 * RTE is a join, subselect, or VALUES.  We represent this as a
  			 * whole-row Var of RECORD type. (Note that in most cases the Var
--- 212,219 ----
  								 varlevelsup);
  			}
  			break;
+ 
  		default:
  			/*
  			 * RTE is a join, subselect, or VALUES.  We represent this as a
  			 * whole-row Var of RECORD type. (Note that in most cases the Var
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 521,526 **** _outFunctionScan(StringInfo str, const FunctionScan *node)
--- 521,527 ----
  	WRITE_NODE_FIELD(funccoltypes);
  	WRITE_NODE_FIELD(funccoltypmods);
  	WRITE_NODE_FIELD(funccolcollations);
+ 	WRITE_BOOL_FIELD(funcordinality);
  }
  
  static void
***************
*** 2382,2387 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2383,2389 ----
  			WRITE_NODE_FIELD(funccoltypes);
  			WRITE_NODE_FIELD(funccoltypmods);
  			WRITE_NODE_FIELD(funccolcollations);
+ 			WRITE_BOOL_FIELD(funcordinality);
  			break;
  		case RTE_VALUES:
  			WRITE_NODE_FIELD(values_lists);
***************
*** 2614,2619 **** _outRangeFunction(StringInfo str, const RangeFunction *node)
--- 2616,2622 ----
  {
  	WRITE_NODE_TYPE("RANGEFUNCTION");
  
+ 	WRITE_BOOL_FIELD(ordinality);
  	WRITE_BOOL_FIELD(lateral);
  	WRITE_NODE_FIELD(funccallnode);
  	WRITE_NODE_FIELD(alias);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1223,1228 **** _readRangeTblEntry(void)
--- 1223,1229 ----
  			READ_NODE_FIELD(funccoltypes);
  			READ_NODE_FIELD(funccoltypmods);
  			READ_NODE_FIELD(funccolcollations);
+ 			READ_BOOL_FIELD(funcordinality);
  			break;
  		case RTE_VALUES:
  			READ_NODE_FIELD(values_lists);
*** 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->funcordinality,
  								  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
***************
*** 4452,4461 **** inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
--- 4452,4466 ----
  	 */
  	check_stack_depth();
  
+ 	/* Fail if the caller wanted ORDINALITY - we don't implement that here. */
+ 	if (rte->funcordinality)
+ 		return NULL;
+ 
  	/* Fail if FROM item isn't a simple FuncExpr */
  	fexpr = (FuncExpr *) rte->funcexpr;
  	if (fexpr == NULL || !IsA(fexpr, FuncExpr))
  		return NULL;
+ 
  	func_oid = fexpr->funcid;
  
  	/*
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 566,572 **** 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
--- 566,572 ----
  	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
***************
*** 609,616 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
   * list and so can never be entered directly.  The filter in parser.c
   * creates these tokens when required.
   */
! %token			NULLS_FIRST NULLS_LAST WITH_TIME
! 
  
  /* Precedence: lowest to highest */
  %nonassoc	SET				/* see relation_expr_opt_alias */
--- 609,615 ----
   * list and so can never be entered directly.  The filter in parser.c
   * creates these tokens when required.
   */
! %token			NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
  
  /* Precedence: lowest to highest */
  %nonassoc	SET				/* see relation_expr_opt_alias */
***************
*** 9588,9607 **** table_ref:	relation_expr opt_alias_clause
--- 9587,9628 ----
  				{
  					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);
***************
*** 12575,12580 **** unreserved_keyword:
--- 12596,12602 ----
  			| OPERATOR
  			| OPTION
  			| OPTIONS
+ 			| ORDINALITY
  			| OVER
  			| OWNED
  			| OWNER
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
***************
*** 787,804 **** markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
   * buildRelationAliases
   *		Construct the eref column name list for a relation RTE.
   *		This code is also used for the case of a function RTE returning
!  *		a named composite type.
   *
   * tupdesc: the physical column information
   * alias: the user-supplied alias, or NULL if none
   * eref: the eref Alias to store column names in
   *
   * eref->colnames is filled in.  Also, alias->colnames is rebuilt to insert
   * empty strings for any dropped columns, so that it will be one-to-one with
   * physical column numbers.
   */
  static void
! buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
  {
  	int			maxattrs = tupdesc->natts;
  	ListCell   *aliaslc;
--- 787,810 ----
   * buildRelationAliases
   *		Construct the eref column name list for a relation RTE.
   *		This code is also used for the case of a function RTE returning
!  *		a named composite type or a registered RECORD type.
   *
   * tupdesc: the physical column information
   * alias: the user-supplied alias, or NULL if none
   * eref: the eref Alias to store column names in
+  * ordinality: true if an ordinality column is to be added
   *
   * eref->colnames is filled in.  Also, alias->colnames is rebuilt to insert
   * empty strings for any dropped columns, so that it will be one-to-one with
   * physical column numbers.
+  *
+  * If we add an ordinality column, its colname comes from the alias if there
+  * is one, otherwise we default it. (We don't add it to alias->colnames.)
+  *
+  * It is an error for there to be more aliases present than required.
   */
  static void
! buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality)
  {
  	int			maxattrs = tupdesc->natts;
  	ListCell   *aliaslc;
***************
*** 850,861 **** buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
  		eref->colnames = lappend(eref->colnames, attrname);
  	}
  
  	/* Too many user-supplied aliases? */
  	if (aliaslc)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
  				 errmsg("table \"%s\" has %d columns available but %d columns specified",
! 						eref->aliasname, maxattrs - numdropped, numaliases)));
  }
  
  /*
--- 856,888 ----
  		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,
  				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
  				 errmsg("table \"%s\" has %d columns available but %d columns specified",
! 						eref->aliasname,
! 						maxattrs - numdropped + (ordinality ? 1 : 0),
! 						numaliases)));
  }
  
  /*
***************
*** 867,914 **** buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
   * funcname: function name (used only for error message)
   * alias: the user-supplied alias, or NULL if none
   * eref: the eref Alias to store column names in
   *
   * eref->colnames is filled in.
   */
  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));
  }
  
  /*
--- 894,953 ----
   * funcname: function name (used only for error message)
   * alias: the user-supplied alias, or NULL if none
   * eref: the eref Alias to store column names in
+  * ordinality: whether to add an ordinality column
   *
   * eref->colnames is filled in.
+  *
+  * The caller must have previously filled in eref->aliasname, which will
+  * be used as the result column name if no alias is given.
+  *
+  * A user-supplied Alias can contain up to two column alias names; one for
+  * the function result, and one for the ordinality column; it is an error
+  * to specify more aliases than required.
   */
  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);
! 
! 		/*
! 		 * Otherwise, use the previously-determined alias name provided by the
! 		 * caller (which is not necessarily the function name!)
! 		 */
! 		if (!pname)
! 			pname = eref->aliasname;
! 
! 		eref->colnames = list_make1(makeString(pname));
  	}
  
! 	/* If we don't have a name for the ordinality column yet, supply a default. */
! 	if (ordinality && list_length(eref->colnames) < 2)
! 		eref->colnames = lappend(eref->colnames, makeString(pstrdup("?column?")));
! 
! 	return;
  }
  
  /*
***************
*** 1004,1010 **** 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
--- 1043,1049 ----
  	 * 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
***************
*** 1064,1070 **** 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.
--- 1103,1109 ----
  	 * and/or actual column names.
  	 */
  	rte->eref = makeAlias(refname, NIL);
! 	buildRelationAliases(rel->rd_att, alias, rte->eref, false);
  
  	/*
  	 * Set flags and access permissions.
***************
*** 1235,1251 **** 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.
--- 1274,1296 ----
  		/* 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.
***************
*** 1288,1293 **** addRangeTableEntryForFunction(ParseState *pstate,
--- 1333,1339 ----
  	 * permissions mechanism).
  	 */
  	rte->lateral = lateral;
+ 	rte->funcordinality = rangefunc->ordinality;
  	rte->inh = false;			/* never true for functions */
  	rte->inFromCl = inFromCl;
  
***************
*** 1643,1648 **** addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
--- 1689,1699 ----
   * The output lists go into *colnames and *colvars.
   * If only one of the two kinds of output list is needed, pass NULL for the
   * output pointer for the unwanted one.
+  *
+  * For function RTEs with ORDINALITY, this expansion includes the
+  * ordinal column, whose type (bigint) had better match the type assumed in the
+  * executor. The colname for the ordinality column must have been set up already
+  * in the RTE; it is always last.
   */
  void
  expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
***************
*** 1711,1716 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1762,1768 ----
  				TypeFuncClass functypclass;
  				Oid			funcrettype;
  				TupleDesc	tupdesc;
+ 				int         ordinality_attno = 0;
  
  				functypclass = get_expr_result_type(rte->funcexpr,
  													&funcrettype,
***************
*** 1719,1727 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1771,1786 ----
  				{
  					/* Composite data type, e.g. a table's row type */
  					Assert(tupdesc);
+ 
+ 					/*
+ 					 * we rely here on the fact that expandTupleDesc doesn't
+ 					 * care about being passed more aliases than it needs.
+ 					 */
  					expandTupleDesc(tupdesc, rte->eref,
  									rtindex, sublevels_up, location,
  									include_dropped, colnames, colvars);
+ 
+ 					ordinality_attno = tupdesc->natts + 1;
  				}
  				else if (functypclass == TYPEFUNC_SCALAR)
  				{
***************
*** 1742,1747 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1801,1808 ----
  
  						*colvars = lappend(*colvars, varnode);
  					}
+ 
+ 					ordinality_attno = 2;
  				}
  				else if (functypclass == TYPEFUNC_RECORD)
  				{
***************
*** 1774,1785 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
--- 1835,1868 ----
  							*colvars = lappend(*colvars, varnode);
  						}
  					}
+ 
+ 					/* note, ordinality is not allowed in this case */
  				}
  				else
  				{
  					/* addRangeTableEntryForFunction should've caught this */
  					elog(ERROR, "function in FROM has unsupported return type");
  				}
+ 
+ 				/* tack on the extra ordinality column if present */
+ 				if (rte->funcordinality)
+ 				{
+ 					Assert(ordinality_attno > 0);
+ 
+ 					if (colnames)
+ 						*colnames = lappend(*colnames, llast(rte->eref->colnames));
+ 
+ 					if (colvars)
+ 					{
+ 						Var *varnode = makeVar(rtindex,
+ 											   ordinality_attno,
+ 											   INT8OID,
+ 											   -1,
+ 											   InvalidOid,
+ 											   sublevels_up);
+ 						*colvars = lappend(*colvars, varnode);
+ 					}
+ 				}
  			}
  			break;
  		case RTE_VALUES:
***************
*** 1955,1960 **** expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
--- 2038,2046 ----
  
  /*
   * expandTupleDesc -- expandRTE subroutine
+  *
+  * Only the required number of column names are used from the Alias;
+  * it is not an error to supply too many. (ordinality depends on this)
   */
  static void
  expandTupleDesc(TupleDesc tupdesc, Alias *eref,
***************
*** 2114,2119 **** get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
--- 2200,2208 ----
  /*
   * get_rte_attribute_type
   *		Get attribute type/typmod/collation information from a RangeTblEntry
+  *
+  * Once again, for function RTEs we may have to synthesize the
+  * ordinality column with the correct type.
   */
  void
  get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
***************
*** 2172,2177 **** get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
--- 2261,2280 ----
  				Oid			funcrettype;
  				TupleDesc	tupdesc;
  
+ 				/*
+ 				 * if ordinality, then a reference to the last column
+ 				 * in the name list must be referring to the
+ 				 * ordinality column
+ 				 */
+ 				if (rte->funcordinality
+ 					&& attnum == list_length(rte->eref->colnames))
+ 				{
+ 					*vartype = INT8OID;
+ 					*vartypmod = -1;
+ 					*varcollid = InvalidOid;
+ 					break;
+ 				}
+ 
  				functypclass = get_expr_result_type(rte->funcexpr,
  													&funcrettype,
  													&tupdesc);
***************
*** 2182,2187 **** get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
--- 2285,2291 ----
  					Form_pg_attribute att_tup;
  
  					Assert(tupdesc);
+ 
  					/* this is probably a can't-happen case */
  					if (attnum < 1 || attnum > tupdesc->natts)
  						ereport(ERROR,
***************
*** 2208,2213 **** get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
--- 2312,2319 ----
  				}
  				else if (functypclass == TYPEFUNC_SCALAR)
  				{
+ 					Assert(attnum == 1);
+ 
  					/* Base data type, i.e. scalar */
  					*vartype = funcrettype;
  					*vartypmod = -1;
***************
*** 2332,2338 **** 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
--- 2438,2454 ----
  				Oid			funcrettype = exprType(rte->funcexpr);
  				Oid			funcrelid = typeidTypeRelid(funcrettype);
  
! 				/*
! 				 * if ordinality, then a reference to the last column
! 				 * in the name list must be referring to the
! 				 * ordinality column, which is not dropped
! 				 */
! 				if (rte->funcordinality
! 					&& 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
***************
*** 8004,8009 **** get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
--- 8004,8011 ----
  			case RTE_FUNCTION:
  				/* Function RTE */
  				get_rule_expr(rte->funcexpr, context, true);
+ 				if (rte->funcordinality)
+ 					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
***************
*** 1395,1401 **** typedef struct SubqueryScanState
   *		function appearing in FROM (typically a function returning set).
   *
   *		eflags				node's capability flags
!  *		tupdesc				expected return tuple description
   *		tuplestorestate		private state of tuplestore.c
   *		funcexpr			state for function expression being evaluated
   * ----------------
--- 1395,1404 ----
   *		function appearing in FROM (typically a function returning set).
   *
   *		eflags				node's capability flags
!  *		ordinal				column value for WITH ORDINALITY
!  *		scan_tupdesc		scan tuple descriptor 
!  *		func_tupdesc		function tuple descriptor 
!  *		func_slot			function result slot, or null
   *		tuplestorestate		private state of tuplestore.c
   *		funcexpr			state for function expression being evaluated
   * ----------------
***************
*** 1404,1410 **** typedef struct FunctionScanState
  {
  	ScanState	ss;				/* its first field is NodeTag */
  	int			eflags;
! 	TupleDesc	tupdesc;
  	Tuplestorestate *tuplestorestate;
  	ExprState  *funcexpr;
  } FunctionScanState;
--- 1407,1416 ----
  {
  	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
***************
*** 471,476 **** typedef struct RangeFunction
--- 471,477 ----
  {
  	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
***************
*** 651,658 **** typedef struct XmlSerialize
   *	  dropped columns.	Note however that a stored rule may have nonempty
   *	  colnames for columns dropped since the rule was created (and for that
   *	  matter the colnames might be out of date due to column renamings).
   *	  The same comments apply to FUNCTION RTEs when the function's return type
!  *	  is a named composite type.
   *
   *	  In JOIN RTEs, the colnames in both alias and eref are one-to-one with
   *	  joinaliasvars entries.  A JOIN RTE will omit columns of its inputs when
--- 652,664 ----
   *	  dropped columns.	Note however that a stored rule may have nonempty
   *	  colnames for columns dropped since the rule was created (and for that
   *	  matter the colnames might be out of date due to column renamings).
+  *
   *	  The same comments apply to FUNCTION RTEs when the function's return type
!  *	  is a named composite type. In addition, for all return types, FUNCTION
!  *    RTEs with ORDINALITY must always have the last colname entry being the
!  *    one for the ordinal column; this is enforced when constructing the RTE.
!  *    Thus when ORDINALITY is used, there will be exactly one more colname
!  *    than would have been present otherwise.
   *
   *	  In JOIN RTEs, the colnames in both alias and eref are one-to-one with
   *	  joinaliasvars entries.  A JOIN RTE will omit columns of its inputs when
***************
*** 751,765 **** typedef struct RangeTblEntry
  	/*
  	 * Fields valid for a function RTE (else NULL):
  	 *
! 	 * If the function returns RECORD, funccoltypes lists the column types
! 	 * declared in the RTE's column type specification, funccoltypmods lists
! 	 * their declared typmods, funccolcollations their collations.	Otherwise,
! 	 * those fields are NIL.
  	 */
  	Node	   *funcexpr;		/* expression tree for func call */
  	List	   *funccoltypes;	/* OID list of column type OIDs */
  	List	   *funccoltypmods; /* integer list of column typmods */
  	List	   *funccolcollations;		/* OID list of column collation OIDs */
  
  	/*
  	 * Fields valid for a values RTE (else NIL):
--- 757,777 ----
  	/*
  	 * Fields valid for a function RTE (else NULL):
  	 *
! 	 * If the function returns an otherwise-unspecified RECORD, funccoltypes
! 	 * lists the column types declared in the RTE's column type specification,
! 	 * funccoltypmods lists their declared typmods, funccolcollations their
! 	 * collations.  Note that in this case, ORDINALITY is not permitted, so
! 	 * there is no extra ordinal column to be allowed for.
! 	 *
!      * Otherwise, those fields are NIL, and the result column types must be
! 	 * derived from the funcexpr while treating the ordinal column, if
! 	 * present, as a special case.  (see get_rte_attribute_*)
  	 */
  	Node	   *funcexpr;		/* expression tree for func call */
  	List	   *funccoltypes;	/* OID list of column type OIDs */
  	List	   *funccoltypmods; /* integer list of column typmods */
  	List	   *funccolcollations;		/* OID list of column collation OIDs */
+ 	bool		funcordinality;	/* is this called WITH ORDINALITY? */
  
  	/*
  	 * Fields valid for a values RTE (else NIL):
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 425,430 **** typedef struct FunctionScan
--- 425,431 ----
  {
  	Scan		scan;
  	Node	   *funcexpr;		/* expression tree for func call */
+ 	bool        funcordinality; /* WITH ORDINALITY */
  	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
***************
*** 269,274 **** PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD)
--- 269,275 ----
  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, UNRESERVED_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,148 ----
  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;
! -- ordinality vs. rewind and reverse scan
! begin;
! declare foo scroll cursor for select * from generate_series(1,5) with ordinality as g(i,o);
! fetch all from foo;
!  i | o 
! ---+---
!  1 | 1
!  2 | 2
!  3 | 3
!  4 | 4
!  5 | 5
! (5 rows)
! 
! fetch backward all from foo;
!  i | o 
! ---+---
!  5 | 5
!  4 | 4
!  3 | 3
!  2 | 2
!  1 | 1
! (5 rows)
! 
! fetch all from foo;
!  i | o 
! ---+---
!  1 | 1
!  2 | 2
!  3 | 3
!  4 | 4
!  5 | 5
! (5 rows)
! 
! fetch next from foo;
!  i | o 
! ---+---
! (0 rows)
! 
! fetch next from foo;
!  i | o 
! ---+---
! (0 rows)
! 
! fetch prior from foo;
!  i | o 
! ---+---
!  5 | 5
! (1 row)
! 
! fetch absolute 1 from foo;
!  i | o 
! ---+---
!  1 | 1
! (1 row)
! 
! commit;
  -- 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;
--- 152,166 ----
       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;
--- 206,217 ----
    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;
--- 219,232 ----
        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;
--- 238,250 ----
    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;
--- 253,267 ----
        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;
--- 273,285 ----
   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;
--- 288,302 ----
   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;
--- 307,318 ----
       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;
--- 320,333 ----
       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;
--- 339,351 ----
       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;
--- 354,369 ----
       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;
--- 412,423 ----
    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;
--- 425,438 ----
        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;
--- 443,454 ----
       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
  --
--- 457,1078 ----
  (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();
--- 1548,1560 ----
   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,44 ----
  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;
! -- ordinality vs. rewind and reverse scan
! begin;
! declare foo scroll cursor for select * from generate_series(1,5) with ordinality as g(i,o);
! fetch all from foo;
! fetch backward all from foo;
! fetch all from foo;
! fetch next from foo;
! fetch next from foo;
! fetch prior from foo;
! fetch absolute 1 from foo;
! commit;
  
  -- 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');
--- 59,120 ----
  -- 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;
--- 138,161 ----
  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
--- 164,248 ----
  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;
--- 458,464 ----
  
  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();
#49Robert Haas
robertmhaas@gmail.com
In reply to: Greg Stark (#44)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Wed, Jul 24, 2013 at 1:50 PM, Greg Stark <stark@mit.edu> wrote:

On Wed, Jul 24, 2013 at 6:39 PM, Robert Haas <robertmhaas@gmail.com> wrote:

This patch will introduce, without documentation, a fifth class of
keyword. ORDINALITY will need to be quoted when, and only when, it
immediately follows WITH. Without some change to our deparsing code,
this is a dump/restore hazard; and with some such change it's still
probably not a good idea.

Strictly speaking this patc doesn't introduce this fifth class of
keyword. We already had TIME in that category (and also FIRST and LAST
in a similar category following NULLS). If we have a solution for WITH
<keyword> then presumably we would implement it for WITH TIME and WITH
ORDINALITY at the same time.

In the interim I suppose we could teach pg_dump to quote any keyword
that follows WITH or NULLS pretty easily. Or just quote those four
words unconditionally.

Making these keywords reserved-enough they get quoted would indeed fix
the problem. It may not be desirable for other reasons, but the fact
that we have existing cases where pg_dump DTWT doesn't seem like a
good reason to add more of them.

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

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

#50David Fetter
david@fetter.org
In reply to: David Fetter (#48)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Thu, Jul 25, 2013 at 10:33:54AM -0700, David Fetter wrote:

On Wed, Jul 24, 2013 at 09:38:15PM +0000, Andrew Gierth wrote:

Tom Lane said:

If we did it with a WithOrdinality expression node, the result would
always be of type RECORD, and we'd have to use blessed tuple
descriptors to keep track of exactly which record type a particular
instance emits. This is certainly do-able (see RowExpr for
precedent).

Maybe RowExpr is a precedent for something, but it has this
long-standing problem that makes it very hard to use usefully:

postgres=# select (r).* from (select row(a,b) as r from (values (1,2)) v(a,b)) s;
ERROR: record type has not been registered

It seems way too short on comments. [...]

This can certainly be addressed.

but it sure looks like it flat out removed several existing
regression-test cases

Here's why, in rangefuncs.sql:

--invokes ExecReScanFunctionScan
SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2;

I don't think that has invoked ExecReScanFunctionScan since 7.4 or so.
It certainly does not do so now (confirmed by gdb as well as by the
query plan). By all means keep the old tests if you want a
never-remove-tests-for-any-reason policy, but having added tests that
actually _do_ invoke ExecReScanFunctionScan, I figured the old ones
were redundant. (Also, these kinds of tests can be done a bit better
now with values and lateral rather than creating and dropping tables
just for the one test.)

and a few existing comments as well.

I've double-checked, and I don't see any existing comments removed.

FWIW, I concur with the gripe I remember seeing upthread that the
default name of the added column ought not be "?column?".

This seems to be a common complaint, but gives rise to two questions:

1) what should the name be?

2) should users be depending on it?

I've yet to find another db that actually documents a specific column
name for the ordinality column; it's always taken for granted that the
user should always be supplying an alias. (Admittedly there are not
many dbs that support it at all; DB2 does, and I believe Teradata.)

Next patch: changes by Andrew Gierth, testing vs up-to-date git master
by Yours Truly.

Greg,

Are you still on this? Do you have questions or concerns?

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

#51Greg Stark
stark@mit.edu
In reply to: David Fetter (#50)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Sun, Jul 28, 2013 at 6:49 AM, David Fetter <david@fetter.org> wrote:

Are you still on this? Do you have questions or concerns?

Still on this, I've just been a bit busy the past few days.

--
greg

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

#52Greg Stark
stark@mit.edu
In reply to: Tom Lane (#45)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Wed, Jul 24, 2013 at 7:00 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I don't see any workable fix that doesn't involve the funny token, though.
Consider

CREATE VIEW v AS SELECT ... FROM UNNEST(...) WITH ORDINALITY;
CREATE VIEW v AS SELECT ... FROM UNNEST(...) WITH NO DATA;

WITH ORDINALITY really needs to be parsed as part of the FROM clause.
WITH NO DATA really needs to *not* be parsed as part of the FROM clause.
Without looking ahead more than one token, there is absolutely no way
for the grammar to decide if it's got the whole FROM clause or not
at the point where WITH is the next token. So our choices are to have
two-token lookahead at the lexer level, or to give up on bison and find
something that can implement a parsing algorithm better than LALR(1).
I know which one seems more likely to get done in the foreseeable future.

It occurs to me we might be being silly here.

Instead of collapsing WITH TIME and WITH ORDINALITY into a single
token why don't we just modify the WITH token to WITH_FOLLOWED_BY_TIME
and WITH_FOLLOWED_BY_ORDINALITY but still keep the following token.
Then we can just include those two tokens everywhere we include WITH.
Basically we would be giving the parser a free extra token of
lookahead whenever it gets WITH.

I think that's isomorphic to what Tom suggested but requires less
surgery on the parser and automatically covers any other cases we
don't need to track down.

--
greg

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

#53Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#52)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Greg Stark <stark@mit.edu> writes:

Instead of collapsing WITH TIME and WITH ORDINALITY into a single
token why don't we just modify the WITH token to WITH_FOLLOWED_BY_TIME
and WITH_FOLLOWED_BY_ORDINALITY but still keep the following token.
Then we can just include those two tokens everywhere we include WITH.
Basically we would be giving the parser a free extra token of
lookahead whenever it gets WITH.

I think that's isomorphic to what Tom suggested but requires less
surgery on the parser and automatically covers any other cases we
don't need to track down.

I suspect it's likely to come out about the same either way once you
account for all the uses of WITH. Might be worth trying both to see
which seems less ugly.

regards, tom lane

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

#54Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Dean Rasheed (#1)
1 attachment(s)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

I propose the following patch (which goes on top of the current
ordinality one) to implement the suggested grammar changes.

I think this is the cleanest way, and I've tested that it both
passes regression and allows constructs like WITH time AS (...)
to work.

--
Andrew (irc:RhodiumToad)

Attachments:

gram-kw2.patchtext/plain; charset=us-asciiDownload
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 608,615 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
   * The grammar thinks these are keywords, but they are not in the kwlist.h
   * list and so can never be entered directly.  The filter in parser.c
   * creates these tokens when required.
   */
! %token			NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
  
  /* Precedence: lowest to highest */
  %nonassoc	SET				/* see relation_expr_opt_alias */
--- 608,645 ----
   * The grammar thinks these are keywords, but they are not in the kwlist.h
   * list and so can never be entered directly.  The filter in parser.c
   * creates these tokens when required.
+  *
+  * The rules for referencing WITH and these special lookahead keywords are
+  * as follows:
+  *
+  * If WITH is followed by a fixed token, such as WITH OIDS, or a non-keyword
+  * token such as '(', then use WITH directly, except as indicated below.
+  *
+  * If WITH could be followed by an object name, then use the with_keyword
+  * production instead. Also, if there are alternative branches in which some
+  * have a fixed keyword following WITH and some have an object name, then
+  * use with_keyword for all of them, overriding the above rule.
+  *
+  * (Similar rules would apply for NULLS_P, but currently there are no
+  * instances in the grammar where this is used other than as a special
+  * case or as an identifier.)
+  *
+  * The productions associated with these special cases are listed under
+  * "Special-case keyword sequences" near the end of the grammar. It is
+  * intended that these be the ONLY places that the special lookahead
+  * keywords appear, in order to avoid complicating the main body of the
+  * grammar.
+  *
+  * To add a new special case:
+  *   - add the special token names here in a %token decl
+  *   - add or extend the productions under "Special-case keyword sequences"
+  *   - add appropriate comparisons in:
+  *       base_yylex in src/backend/parser/parser.c
+  *       filtered_base_yylex in src/interfaces/ecpg/preproc/parser.c
   */
! 
! %token			NULLS_BEFORE_FIRST NULLS_BEFORE_LAST
! %token          WITH_BEFORE_ORDINALITY WITH_BEFORE_TIME
  
  /* Precedence: lowest to highest */
  %nonassoc	SET				/* see relation_expr_opt_alias */
***************
*** 838,848 **** CreateRoleStmt:
  				}
  		;
  
- 
- opt_with:	WITH									{}
- 			| /*EMPTY*/								{}
- 		;
- 
  /*
   * Options for CREATE ROLE and ALTER ROLE (also used by CREATE/ALTER USER
   * for backwards compatibility).  Note: the only option required by SQL99
--- 868,873 ----
***************
*** 3127,3138 **** ExclusionConstraintList:
  													{ $$ = lappend($1, $3); }
  		;
  
! ExclusionConstraintElem: index_elem WITH any_operator
  			{
  				$$ = list_make2($1, $3);
  			}
  			/* allow OPERATOR() decoration for the benefit of ruleutils.c */
! 			| index_elem WITH OPERATOR '(' any_operator ')'
  			{
  				$$ = list_make2($1, $5);
  			}
--- 3152,3163 ----
  													{ $$ = lappend($1, $3); }
  		;
  
! ExclusionConstraintElem: index_elem with_keyword any_operator
  			{
  				$$ = list_make2($1, $3);
  			}
  			/* allow OPERATOR() decoration for the benefit of ruleutils.c */
! 			| index_elem with_keyword OPERATOR '(' any_operator ')'
  			{
  				$$ = list_make2($1, $5);
  			}
***************
*** 6188,6195 **** opt_asc_desc: ASC							{ $$ = SORTBY_ASC; }
  			| /*EMPTY*/						{ $$ = SORTBY_DEFAULT; }
  		;
  
! opt_nulls_order: NULLS_FIRST				{ $$ = SORTBY_NULLS_FIRST; }
! 			| NULLS_LAST					{ $$ = SORTBY_NULLS_LAST; }
  			| /*EMPTY*/						{ $$ = SORTBY_NULLS_DEFAULT; }
  		;
  
--- 6213,6220 ----
  			| /*EMPTY*/						{ $$ = SORTBY_DEFAULT; }
  		;
  
! opt_nulls_order: nulls_first				{ $$ = SORTBY_NULLS_FIRST; }
! 			| nulls_last					{ $$ = SORTBY_NULLS_LAST; }
  			| /*EMPTY*/						{ $$ = SORTBY_NULLS_DEFAULT; }
  		;
  
***************
*** 8348,8354 **** AlterTSDictionaryStmt:
  		;
  
  AlterTSConfigurationStmt:
! 			ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list WITH any_name_list
  				{
  					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
  					n->cfgname = $5;
--- 8373,8379 ----
  		;
  
  AlterTSConfigurationStmt:
! 			ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list with_keyword any_name_list
  				{
  					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
  					n->cfgname = $5;
***************
*** 8358,8364 **** AlterTSConfigurationStmt:
  					n->replace = false;
  					$$ = (Node*)n;
  				}
! 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list WITH any_name_list
  				{
  					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
  					n->cfgname = $5;
--- 8383,8389 ----
  					n->replace = false;
  					$$ = (Node*)n;
  				}
! 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list with_keyword any_name_list
  				{
  					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
  					n->cfgname = $5;
***************
*** 8368,8374 **** AlterTSConfigurationStmt:
  					n->replace = false;
  					$$ = (Node*)n;
  				}
! 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name WITH any_name
  				{
  					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
  					n->cfgname = $5;
--- 8393,8399 ----
  					n->replace = false;
  					$$ = (Node*)n;
  				}
! 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name with_keyword any_name
  				{
  					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
  					n->cfgname = $5;
***************
*** 8378,8384 **** AlterTSConfigurationStmt:
  					n->replace = true;
  					$$ = (Node*)n;
  				}
! 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name WITH any_name
  				{
  					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
  					n->cfgname = $5;
--- 8403,8409 ----
  					n->replace = true;
  					$$ = (Node*)n;
  				}
! 			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name with_keyword any_name
  				{
  					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
  					n->cfgname = $5;
***************
*** 9247,9260 **** simple_select:
   * We don't currently support the SEARCH or CYCLE clause.
   */
  with_clause:
! 		WITH cte_list
  			{
  				$$ = makeNode(WithClause);
  				$$->ctes = $2;
  				$$->recursive = false;
  				$$->location = @1;
  			}
! 		| WITH RECURSIVE cte_list
  			{
  				$$ = makeNode(WithClause);
  				$$->ctes = $3;
--- 9272,9285 ----
   * We don't currently support the SEARCH or CYCLE clause.
   */
  with_clause:
! 		with_keyword cte_list
  			{
  				$$ = makeNode(WithClause);
  				$$->ctes = $2;
  				$$->recursive = false;
  				$$->location = @1;
  			}
! 		| with_keyword RECURSIVE cte_list
  			{
  				$$ = makeNode(WithClause);
  				$$->ctes = $3;
***************
*** 9593,9599 **** table_ref:	relation_expr opt_alias_clause
  					n->coldeflist = lsecond($2);
  					$$ = (Node *) n;
  				}
! 			| func_table WITH_ORDINALITY func_alias_clause
  				{
  					RangeFunction *n = makeNode(RangeFunction);
  					n->lateral = false;
--- 9618,9624 ----
  					n->coldeflist = lsecond($2);
  					$$ = (Node *) n;
  				}
! 			| func_table with_ordinality func_alias_clause
  				{
  					RangeFunction *n = makeNode(RangeFunction);
  					n->lateral = false;
***************
*** 9613,9619 **** table_ref:	relation_expr opt_alias_clause
  					n->coldeflist = lsecond($3);
  					$$ = (Node *) n;
  				}
! 			| LATERAL_P func_table WITH_ORDINALITY func_alias_clause
  				{
  					RangeFunction *n = makeNode(RangeFunction);
  					n->lateral = true;
--- 9638,9644 ----
  					n->coldeflist = lsecond($3);
  					$$ = (Node *) n;
  				}
! 			| LATERAL_P func_table with_ordinality func_alias_clause
  				{
  					RangeFunction *n = makeNode(RangeFunction);
  					n->lateral = true;
***************
*** 10413,10419 **** ConstInterval:
  		;
  
  opt_timezone:
! 			WITH_TIME ZONE							{ $$ = TRUE; }
  			| WITHOUT TIME ZONE						{ $$ = FALSE; }
  			| /*EMPTY*/								{ $$ = FALSE; }
  		;
--- 10438,10444 ----
  		;
  
  opt_timezone:
! 			with_time ZONE							{ $$ = TRUE; }
  			| WITHOUT TIME ZONE						{ $$ = FALSE; }
  			| /*EMPTY*/								{ $$ = FALSE; }
  		;
***************
*** 12431,12436 **** ColLabel:	IDENT									{ $$ = $1; }
--- 12456,12499 ----
  			| reserved_keyword						{ $$ = pstrdup($1); }
  		;
  
+ /*
+  * Special-case keyword sequences.
+  *
+  * To disambiguate WITH TIME, WITH ORDINALITY, NULLS FIRST, NULLS
+  * LAST, which otherwise cause conflicts, the lexer looks ahead one
+  * extra token and may replace the WITH or NULLS keyword by
+  * WITH_BEFORE_* or NULLS_BEFORE_* (the following keyword is not
+  * touched).
+  *
+  * These productions collect the special cases in one place; see the
+  * token declarations at the top of the file for the rules used.
+  */
+ 
+ opt_with: with_keyword
+           | /*EMPTY*/
+         ;
+ 
+ with_keyword: WITH
+               | WITH_BEFORE_TIME
+               | WITH_BEFORE_ORDINALITY
+             ;
+ 
+ with_ordinality: WITH_BEFORE_ORDINALITY ORDINALITY ;
+ 
+ with_time: WITH_BEFORE_TIME TIME ;
+ 
+ /*
+  * not needed since NULLS never occurs alone:
+  *
+  * nulls_keyword: NULLS_P
+  *               | NULLS_BEFORE_FIRST
+  *               | NULLS_BEFORE_LAST
+  *             ;
+  */
+ 
+ nulls_first: NULLS_BEFORE_FIRST FIRST_P ;
+ 
+ nulls_last: NULLS_BEFORE_LAST LAST_P ;
  
  /*
   * Keyword category lists.  Generally, every keyword present in
*** a/src/backend/parser/parser.c
--- b/src/backend/parser/parser.c
***************
*** 65,72 **** raw_parser(const char *str)
   * Intermediate filter between parser and core lexer (core_yylex in scan.l).
   *
   * The filter is needed because in some cases the standard SQL grammar
!  * requires more than one token lookahead.	We reduce these cases to one-token
!  * lookahead by combining tokens here, in order to keep the grammar LALR(1).
   *
   * Using a filter is simpler than trying to recognize multiword tokens
   * directly in scan.l, because we'd have to allow for comments between the
--- 65,80 ----
   * Intermediate filter between parser and core lexer (core_yylex in scan.l).
   *
   * The filter is needed because in some cases the standard SQL grammar
!  * requires more than one token lookahead. We reduce these cases to
!  * one-token lookahead by adding one extra token of lookahead here, and
!  * altering the keyword returned based on what follows, in order to keep
!  * the grammar LALR(1).
!  *
!  * We used to combine keywords, but no longer do. Now, keywords that would
!  * cause conflicts have special <keyword>_BEFORE_<lookahead> forms, which
!  * flag up in advance that the next token is <lookahead>.  This is more
!  * flexible when handling some of the edge cases such as
!  *   WITH ordinality AS (...)
   *
   * Using a filter is simpler than trying to recognize multiword tokens
   * directly in scan.l, because we'd have to allow for comments between the
***************
*** 98,167 **** base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
  	else
  		cur_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
  
! 	/* Do we need to look ahead for a possible multiword token? */
! 	switch (cur_token)
! 	{
! 		case NULLS_P:
! 
! 			/*
! 			 * NULLS FIRST and NULLS LAST must be reduced to one token
! 			 */
! 			cur_yylval = lvalp->core_yystype;
! 			cur_yylloc = *llocp;
! 			next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
! 			switch (next_token)
! 			{
! 				case FIRST_P:
! 					cur_token = NULLS_FIRST;
! 					break;
! 				case LAST_P:
! 					cur_token = NULLS_LAST;
! 					break;
! 				default:
! 					/* save the lookahead token for next time */
! 					yyextra->lookahead_token = next_token;
! 					yyextra->lookahead_yylval = lvalp->core_yystype;
! 					yyextra->lookahead_yylloc = *llocp;
! 					yyextra->have_lookahead = true;
! 					/* and back up the output info to cur_token */
! 					lvalp->core_yystype = cur_yylval;
! 					*llocp = cur_yylloc;
! 					break;
! 			}
! 			break;
! 
! 		case WITH:
! 
! 			/*
! 			 * WITH TIME and WITH ORDINALITY must each be reduced to one token
! 			 */
! 			cur_yylval = lvalp->core_yystype;
! 			cur_yylloc = *llocp;
! 			next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
! 			switch (next_token)
! 			{
! 				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;
! 					yyextra->lookahead_yylval = lvalp->core_yystype;
! 					yyextra->lookahead_yylloc = *llocp;
! 					yyextra->have_lookahead = true;
! 					/* and back up the output info to cur_token */
! 					lvalp->core_yystype = cur_yylval;
! 					*llocp = cur_yylloc;
! 					break;
! 			}
! 			break;
! 
! 		default:
! 			break;
! 	}
  
  	return cur_token;
  }
--- 106,151 ----
  	else
  		cur_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
  
! 	/*
! 	 * Do we need to look ahead for a possible multiword token?
! 	 * If not, we're done here.
! 	 */
! 
! 	if (cur_token != NULLS_P && cur_token != WITH)
! 		return cur_token;
! 
! 	/* Fetch the lookahead token */
! 
! 	cur_yylval = lvalp->core_yystype;
! 	cur_yylloc = *llocp;
! 	next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
! 
! 	/* save the lookahead token for next time */
! 
! 	yyextra->lookahead_token = next_token;
! 	yyextra->lookahead_yylval = lvalp->core_yystype;
! 	yyextra->lookahead_yylloc = *llocp;
! 	yyextra->have_lookahead = true;
! 
! 	/* and back up the output info to cur_token */
! 
! 	lvalp->core_yystype = cur_yylval;
! 	*llocp = cur_yylloc;
! 
! 	/*
! 	 * We don't merge the two tokens into one, but just modify the value of
! 	 * the leading token to reflect what follows.
! 	 */
! 
! 	if (cur_token == NULLS_P && next_token == FIRST_P)
! 		return NULLS_BEFORE_FIRST;
! 	else if (cur_token == NULLS_P && next_token == LAST_P)
! 		return NULLS_BEFORE_LAST;
! 
! 	if (cur_token == WITH && next_token == TIME)
! 		return WITH_BEFORE_TIME;
! 	else if (cur_token == WITH && next_token == ORDINALITY)
! 		return WITH_BEFORE_ORDINALITY;
  
  	return cur_token;
  }
*** a/src/interfaces/ecpg/preproc/parser.c
--- b/src/interfaces/ecpg/preproc/parser.c
***************
*** 35,42 **** static YYLTYPE lookahead_yylloc;	/* yylloc for lookahead token */
   * Intermediate filter between parser and base lexer (base_yylex in scan.l).
   *
   * The filter is needed because in some cases the standard SQL grammar
!  * requires more than one token lookahead.	We reduce these cases to one-token
!  * lookahead by combining tokens here, in order to keep the grammar LALR(1).
   *
   * Using a filter is simpler than trying to recognize multiword tokens
   * directly in scan.l, because we'd have to allow for comments between the
--- 35,50 ----
   * Intermediate filter between parser and base lexer (base_yylex in scan.l).
   *
   * The filter is needed because in some cases the standard SQL grammar
!  * requires more than one token lookahead. We reduce these cases to
!  * one-token lookahead by adding one extra token of lookahead here, and
!  * altering the keyword returned based on what follows, in order to keep
!  * the grammar LALR(1).
!  *
!  * We used to combine keywords, but no longer do. Now, keywords that would
!  * cause conflicts have special <keyword>_BEFORE_<lookahead> forms, which
!  * flag up in advance that the next token is <lookahead>.  This is more
!  * flexible when handling some of the edge cases such as
!  *   WITH ordinality AS (...)
   *
   * Using a filter is simpler than trying to recognize multiword tokens
   * directly in scan.l, because we'd have to allow for comments between the
***************
*** 63,129 **** filtered_base_yylex(void)
  	else
  		cur_token = base_yylex();
  
! 	/* Do we need to look ahead for a possible multiword token? */
! 	switch (cur_token)
! 	{
! 		case NULLS_P:
! 
! 			/*
! 			 * NULLS FIRST and NULLS LAST must be reduced to one token
! 			 */
! 			cur_yylval = base_yylval;
! 			cur_yylloc = base_yylloc;
! 			next_token = base_yylex();
! 			switch (next_token)
! 			{
! 				case FIRST_P:
! 					cur_token = NULLS_FIRST;
! 					break;
! 				case LAST_P:
! 					cur_token = NULLS_LAST;
! 					break;
! 				default:
! 					/* save the lookahead token for next time */
! 					lookahead_token = next_token;
! 					lookahead_yylval = base_yylval;
! 					lookahead_yylloc = base_yylloc;
! 					have_lookahead = true;
! 					/* and back up the output info to cur_token */
! 					base_yylval = cur_yylval;
! 					base_yylloc = cur_yylloc;
! 					break;
! 			}
! 			break;
! 
! 		case WITH:
! 
! 			/*
! 			 * WITH TIME must be reduced to one token
! 			 */
! 			cur_yylval = base_yylval;
! 			cur_yylloc = base_yylloc;
! 			next_token = base_yylex();
! 			switch (next_token)
! 			{
! 				case TIME:
! 					cur_token = WITH_TIME;
! 					break;
! 				default:
! 					/* save the lookahead token for next time */
! 					lookahead_token = next_token;
! 					lookahead_yylval = base_yylval;
! 					lookahead_yylloc = base_yylloc;
! 					have_lookahead = true;
! 					/* and back up the output info to cur_token */
! 					base_yylval = cur_yylval;
! 					base_yylloc = cur_yylloc;
! 					break;
! 			}
! 			break;
! 
! 		default:
! 			break;
! 	}
  
  	return cur_token;
  }
--- 71,116 ----
  	else
  		cur_token = base_yylex();
  
! 	/*
! 	 * Do we need to look ahead for a possible multiword token?
! 	 * If not, we're done here.
! 	 */
! 
! 	if (cur_token != NULLS_P && cur_token != WITH)
! 		return cur_token;
! 
! 	/* fetch the lookahead token */
! 
! 	cur_yylval = base_yylval;
! 	cur_yylloc = base_yylloc;
! 	next_token = base_yylex();
! 
! 	/* save the lookahead token for next time */
! 
! 	lookahead_token = next_token;
! 	lookahead_yylval = base_yylval;
! 	lookahead_yylloc = base_yylloc;
! 	have_lookahead = true;
! 
! 	/* and back up the output info to cur_token */
! 
! 	base_yylval = cur_yylval;
! 	base_yylloc = cur_yylloc;
! 
! 	/*
! 	 * We don't merge the two tokens into one, but just modify the value of
! 	 * the leading token to reflect what follows.
! 	 */
! 
! 	if (cur_token == NULLS_P && next_token == FIRST_P)
! 		return NULLS_BEFORE_FIRST;
! 	else if (cur_token == NULLS_P && next_token == LAST_P)
! 		return NULLS_BEFORE_LAST;
! 
! 	if (cur_token == WITH && next_token == TIME)
! 		return WITH_BEFORE_TIME;
! 	else if (cur_token == WITH && next_token == ORDINALITY)
! 		return WITH_BEFORE_ORDINALITY;
  
  	return cur_token;
  }
#55Craig Ringer
craig@2ndquadrant.com
In reply to: Andres Freund (#46)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 07/25/2013 02:01 AM, Andres Freund wrote:

And much of that can trivially/centrally be rewritten to LATERAL, not
to speak of the cross-version compatibility problem.

An interesting example of that can be found here:

http://stackoverflow.com/q/12414750/398670

where someone's looking for a way to "zip" two arrays pairwise into an
array of arrays.

As far as I can tell LATERAL won't help with this; you'd need unnest
WITH ORDINALITY then a join on the ordinal, or you'd need full support
for SQL UNNEST with multiple array arguments.

Unless LATERAL provides a way to do lock-step iteration through a pair
(or more) of functions I don't think we can get rid of SRFs-in-FROM just
yet.

--
Craig Ringer 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

#56Vik Fearing
vik.fearing@dalibo.com
In reply to: Craig Ringer (#55)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 07/29/2013 09:56 AM, Craig Ringer wrote:

Unless LATERAL provides a way to do lock-step iteration through a pair
(or more) of functions I don't think we can get rid of SRFs-in-FROM just
yet.

I don't think anyone was arguing for that; we're talking about
deprecating SRFs-in-SELECT.

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

#57Craig Ringer
craig@2ndquadrant.com
In reply to: Vik Fearing (#56)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 07/29/2013 04:31 PM, Vik Fearing wrote:

On 07/29/2013 09:56 AM, Craig Ringer wrote:

Unless LATERAL provides a way to do lock-step iteration through a pair
(or more) of functions I don't think we can get rid of SRFs-in-FROM just
yet.

I don't think anyone was arguing for that; we're talking about
deprecating SRFs-in-SELECT.

Argh, as I was I. A thinko; I was *thinking* SELECT and wrote FROM.

--
Craig Ringer 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

#58Greg Stark
stark@mit.edu
In reply to: Craig Ringer (#55)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Mon, Jul 29, 2013 at 8:56 AM, Craig Ringer <craig@2ndquadrant.com> wrote:

Unless LATERAL provides a way to do lock-step iteration through a pair
(or more) of functions I don't think we can get rid of SRFs [in select target lists] yet

You don't even need lateral. This works fine:

postgres=# select * from generate_series(1,10) with ordinality as
a(a,o) natural full outer join generate_series(1,5) with ordinality as
b(b,o) ;

o | a | b
----+----+---
1 | 1 | 1
2 | 2 | 2
3 | 3 | 3
4 | 4 | 4
5 | 5 | 5
6 | 6 |
7 | 7 |
8 | 8 |
9 | 9 |
10 | 10 |
(10 rows)

However it occurs to me that the plan isn't ideal:

postgres=# explain select * from generate_series(1,10) with ordinality
as a(a,o) natural full outer join generate_series(1,5) with ordinality
as b(b,o) ;
QUERY PLAN
---------------------------------------------------------------------------------------
Merge Full Join (cost=119.66..199.66 rows=5000 width=24)
Merge Cond: (a.o = b.o)
-> Sort (cost=59.83..62.33 rows=1000 width=12)
Sort Key: a.o
-> Function Scan on generate_series a (cost=0.00..10.00
rows=1000 width=12)
-> Sort (cost=59.83..62.33 rows=1000 width=12)
Sort Key: b.o
-> Function Scan on generate_series b (cost=0.00..10.00
rows=1000 width=12)
(8 rows)

I think all that's required to avoid the sorts is teaching the planner
that the Path has a PathKey of the ordinal column. I can look at that
later but I'll go ahead and commit it without it at first. I wonder if
it's also useful to teach the planner that the column is unique.

--
greg

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

#59Craig Ringer
craig@2ndquadrant.com
In reply to: Greg Stark (#58)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On 07/29/2013 08:02 PM, Greg Stark wrote:

Unless LATERAL provides a way to do lock-step iteration through a pair
(or more) of functions I don't think we can get rid of SRFs [in select target lists] yet

You don't even need lateral. This works fine:

postgres=# select * from generate_series(1,10) with ordinality as

Exactly - that's why the previous paragraph was:

As far as I can tell LATERAL won't help with this; you'd need unnest
WITH ORDINALITY then a join on the ordinal, or you'd need full support
for SQL UNNEST with multiple array arguments.

;-)

I'm interested to see that it might be possible to evaluate multiple
"WITH ORDINALITY" SRFs in FROM together rather than having to perform a
join. That'd make it a much saner replacement for SRFs in the SELECT list.

--
Craig Ringer 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

#60Greg Stark
stark@mit.edu
In reply to: Tom Lane (#53)
1 attachment(s)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Sun, Jul 28, 2013 at 7:43 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I suspect it's likely to come out about the same either way once you
account for all the uses of WITH. Might be worth trying both to see
which seems less ugly.

So I'm not really sure how to do it the other way. Once you're in
parser rules I don't know how easy it is to start injecting tokens.
But it seems cleaner this way where only the places where accepting
WITH_ORDINALITY and WITH_TIME create conflicts need to worry about it.
Everywhere else can just accept "with" and not worry about the
problem.

I did the same thing to NULLS_FIRST and NULLS_LAST but then I realized
I couldn't actually fix the rules the same way. NULLS_P is in
unreserved_keywords and adding NULLS_FIRST or NULLS_LAST there creates
conflicts of course. This week isn't one of the two weeks of my life
when I grokked LALR grammars and how to resolve conflicts in bison.

Incidentally I noticed a problem that is actually a bug in the WITH
ORDINALITY patch. The ecpg preprocessor perl script is broken now.
Will fix.

--
greg

Attachments:

special_tokens_parsing.diffapplication/octet-stream; name=special_tokens_parsing.diffDownload
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 22e82ba..fda5f41 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -840,9 +840,16 @@ CreateRoleStmt:
 
 
 opt_with:	WITH									{}
+			| WITH_ORDINALITY						{}
+			| WITH_TIME								{}
 			| /*EMPTY*/								{}
 		;
 
+with:		WITH									{}
+			| WITH_ORDINALITY						{}
+			| WITH_TIME								{}
+		;
+
 /*
  * Options for CREATE ROLE and ALTER ROLE (also used by CREATE/ALTER USER
  * for backwards compatibility).  Note: the only option required by SQL99
@@ -3127,7 +3134,16 @@ ExclusionConstraintList:
 													{ $$ = lappend($1, $3); }
 		;
 
-ExclusionConstraintElem: index_elem WITH any_operator
+ExclusionConstraintElem: 
+			index_elem WITH any_operator
+			{
+				$$ = list_make2($1, $3);
+			}
+			| index_elem WITH_TIME any_operator
+			{
+				$$ = list_make2($1, $3);
+			}
+			| index_elem WITH_ORDINALITY any_operator
 			{
 				$$ = list_make2($1, $3);
 			}
@@ -6188,8 +6204,8 @@ opt_asc_desc: ASC							{ $$ = SORTBY_ASC; }
 			| /*EMPTY*/						{ $$ = SORTBY_DEFAULT; }
 		;
 
-opt_nulls_order: NULLS_FIRST				{ $$ = SORTBY_NULLS_FIRST; }
-			| NULLS_LAST					{ $$ = SORTBY_NULLS_LAST; }
+opt_nulls_order: NULLS_FIRST FIRST_P		{ $$ = SORTBY_NULLS_FIRST; }
+			| NULLS_LAST LAST_P				{ $$ = SORTBY_NULLS_LAST; }
 			| /*EMPTY*/						{ $$ = SORTBY_NULLS_DEFAULT; }
 		;
 
@@ -8348,7 +8364,7 @@ AlterTSDictionaryStmt:
 		;
 
 AlterTSConfigurationStmt:
-			ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list WITH any_name_list
+			ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
 					n->cfgname = $5;
@@ -8358,7 +8374,7 @@ AlterTSConfigurationStmt:
 					n->replace = false;
 					$$ = (Node*)n;
 				}
-			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list WITH any_name_list
+			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list with any_name_list
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
 					n->cfgname = $5;
@@ -8368,7 +8384,7 @@ AlterTSConfigurationStmt:
 					n->replace = false;
 					$$ = (Node*)n;
 				}
-			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name WITH any_name
+			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
 					n->cfgname = $5;
@@ -8378,7 +8394,7 @@ AlterTSConfigurationStmt:
 					n->replace = true;
 					$$ = (Node*)n;
 				}
-			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name WITH any_name
+			| ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name with any_name
 				{
 					AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
 					n->cfgname = $5;
@@ -9254,6 +9270,20 @@ with_clause:
 				$$->recursive = false;
 				$$->location = @1;
 			}
+		| WITH_TIME cte_list
+			{
+				$$ = makeNode(WithClause);
+				$$->ctes = $2;
+				$$->recursive = false;
+				$$->location = @1;
+			}
+		| WITH_ORDINALITY cte_list
+			{
+				$$ = makeNode(WithClause);
+				$$->ctes = $2;
+				$$->recursive = false;
+				$$->location = @1;
+			}
 		| WITH RECURSIVE cte_list
 			{
 				$$ = makeNode(WithClause);
@@ -9593,14 +9623,14 @@ table_ref:	relation_expr opt_alias_clause
 					n->coldeflist = lsecond($2);
 					$$ = (Node *) n;
 				}
-			| func_table WITH_ORDINALITY func_alias_clause
+			| func_table WITH_ORDINALITY 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);
+					n->alias = linitial($4);
+					n->coldeflist = lsecond($4);
 					$$ = (Node *) n;
 				}
 			| LATERAL_P func_table func_alias_clause
@@ -9613,14 +9643,14 @@ table_ref:	relation_expr opt_alias_clause
 					n->coldeflist = lsecond($3);
 					$$ = (Node *) n;
 				}
-			| LATERAL_P func_table WITH_ORDINALITY func_alias_clause
+			| LATERAL_P func_table WITH_ORDINALITY 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);
+					n->alias = linitial($5);
+					n->coldeflist = lsecond($5);
 					$$ = (Node *) n;
 				}
 			| select_with_parens opt_alias_clause
@@ -10413,7 +10443,7 @@ ConstInterval:
 		;
 
 opt_timezone:
-			WITH_TIME ZONE							{ $$ = TRUE; }
+			WITH_TIME TIME ZONE							{ $$ = TRUE; }
 			| WITHOUT TIME ZONE						{ $$ = FALSE; }
 			| /*EMPTY*/								{ $$ = FALSE; }
 		;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 541d364..4802b7d 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -104,60 +104,53 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 		case NULLS_P:
 
 			/*
-			 * NULLS FIRST and NULLS LAST must be reduced to one token
+			 * NULLS FIRST and NULLS LAST must each trigger a special token (but
+			 * still output the regular tokens) to give the parser an extra
+			 * token of lookahead
 			 */
 			cur_yylval = lvalp->core_yystype;
 			cur_yylloc = *llocp;
 			next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
-			switch (next_token)
-			{
-				case FIRST_P:
-					cur_token = NULLS_FIRST;
-					break;
-				case LAST_P:
-					cur_token = NULLS_LAST;
-					break;
-				default:
-					/* save the lookahead token for next time */
-					yyextra->lookahead_token = next_token;
-					yyextra->lookahead_yylval = lvalp->core_yystype;
-					yyextra->lookahead_yylloc = *llocp;
-					yyextra->have_lookahead = true;
-					/* and back up the output info to cur_token */
-					lvalp->core_yystype = cur_yylval;
-					*llocp = cur_yylloc;
-					break;
+			if (next_token == FIRST_P) {
+				cur_token = NULLS_FIRST;
+			} else if (next_token == LAST_P) {
+				cur_token = NULLS_LAST;
 			}
+
+			/* save the lookahead token for next time */
+			yyextra->lookahead_token = next_token;
+			yyextra->lookahead_yylval = lvalp->core_yystype;
+			yyextra->lookahead_yylloc = *llocp;
+			yyextra->have_lookahead = true;
+			/* and back up the output info to cur_token */
+			lvalp->core_yystype = cur_yylval;
+			*llocp = cur_yylloc;
+
 			break;
 
 		case WITH:
 
 			/*
-			 * WITH TIME and WITH ORDINALITY must each be reduced to one token
+			 * WITH TIME and WITH ORDINALITY must each trigger the special
+			 * "WITH_FOO" token (but still output the regular token)
 			 */
 			cur_yylval = lvalp->core_yystype;
 			cur_yylloc = *llocp;
 			next_token = core_yylex(&(lvalp->core_yystype), llocp, yyscanner);
-			switch (next_token)
-			{
-				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;
-					yyextra->lookahead_yylval = lvalp->core_yystype;
-					yyextra->lookahead_yylloc = *llocp;
-					yyextra->have_lookahead = true;
-					/* and back up the output info to cur_token */
-					lvalp->core_yystype = cur_yylval;
-					*llocp = cur_yylloc;
-					break;
+			if (next_token == TIME) {
+				cur_token = WITH_TIME;
+			} else if (next_token == ORDINALITY) {
+				cur_token = WITH_ORDINALITY;
 			}
-			break;
+
+			/* save the lookahead token for next time */
+			yyextra->lookahead_token = next_token;
+			yyextra->lookahead_yylval = lvalp->core_yystype;
+			yyextra->lookahead_yylloc = *llocp;
+			yyextra->have_lookahead = true;
+			/* and back up the output info to cur_token */
+			lvalp->core_yystype = cur_yylval;
+			*llocp = cur_yylloc;
 
 		default:
 			break;
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index f4b51d6..ed0674d 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -42,9 +42,10 @@ my %replace_token = (
 
 # or in the block
 my %replace_string = (
-	'WITH_TIME'    => 'with time',
-	'NULLS_FIRST'  => 'nulls first',
-	'NULLS_LAST'   => 'nulls last',
+	'WITH_TIME'    => 'with',
+	'WITH_ORDINALITY' => 'with',
+	'NULLS_FIRST'  => 'nulls',
+	'NULLS_LAST'   => 'nulls',
 	'TYPECAST'     => '::',
 	'DOT_DOT'      => '..',
 	'COLON_EQUALS' => ':=',);
diff --git a/src/test/regress/expected/special_tokens.out b/src/test/regress/expected/special_tokens.out
new file mode 100644
index 0000000..fc3e908
--- /dev/null
+++ b/src/test/regress/expected/special_tokens.out
@@ -0,0 +1,41 @@
+-- ordinality and time should not require quoting here
+WITH ordinality AS (SELECT 1) SELECT * FROM ordinality;
+ ?column? 
+----------
+        1
+(1 row)
+
+WITH time AS (SELECT 1) SELECT * FROM time;
+ ?column? 
+----------
+        1
+(1 row)
+
+-- These should produce the same error unless there's actually a dictionary
+-- called "ordinality" present in which case it should just work.
+ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH slkdf;
+ERROR:  text search dictionary "slkdf" does not exist
+ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH ordinality;
+ERROR:  text search dictionary "ordinality" does not exist
+-- first should not require quoting here
+SELECT nulls first FROM (SELECT 1 AS nulls) AS x;
+ first 
+-------
+     1
+(1 row)
+
+SELECT nulls last FROM (SELECT 1 AS nulls) AS x;
+ last 
+------
+    1
+(1 row)
+
+CREATE COLLATION nulls (locale='C');
+ALTER OPERATOR CLASS text_ops USING btree RENAME TO first;
+CREATE TABLE nulls_first(t text);
+-- Neither "nulls" nor "first" should require quoting here. This should
+-- correctly find the "nulls" collation and the "first" operator class.
+CREATE INDEX nulls_first_i ON nulls_first(t COLLATE nulls first);
+ALTER OPERATOR CLASS first USING btree RENAME TO text_ops;
+DROP TABLE nulls_first;
+DROP COLLATION nulls;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fd08e8d..8f89208 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -88,7 +88,7 @@ test: privileges security_label collate matview lock
 # ----------
 # Another group of parallel tests
 # ----------
-test: alter_generic misc psql async
+test: special_tokens alter_generic misc psql async
 
 # rules cannot run concurrently with any test that creates a view
 test: rules
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 1ed059b..063c470 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -97,6 +97,7 @@ test: security_label
 test: collate
 test: matview
 test: lock
+test: special_tokens
 test: alter_generic
 test: misc
 test: psql
diff --git a/src/test/regress/sql/special_tokens.sql b/src/test/regress/sql/special_tokens.sql
new file mode 100644
index 0000000..0e6b6c8
--- /dev/null
+++ b/src/test/regress/sql/special_tokens.sql
@@ -0,0 +1,26 @@
+-- ordinality and time should not require quoting here
+WITH ordinality AS (SELECT 1) SELECT * FROM ordinality;
+WITH time AS (SELECT 1) SELECT * FROM time;
+
+-- These should produce the same error unless there's actually a dictionary
+-- called "ordinality" present in which case it should just work.
+ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH slkdf;
+ALTER TEXT SEARCH CONFIGURATION english ADD MAPPING FOR word WITH ordinality;
+
+
+
+-- first should not require quoting here
+SELECT nulls xirst FROM (SELECT 1 AS nulls) AS x;
+SELECT nulls xast FROM (SELECT 1 AS nulls) AS x;
+
+CREATE COLLATION nulls (locale='C');
+ALTER OPERATOR CLASS text_ops USING btree RENAME TO xirst;
+CREATE TABLE nulls_first(t text);
+
+-- Neither "nulls" nor "first" should require quoting here. This should
+-- correctly find the "nulls" collation and the "first" operator class.
+CREATE INDEX nulls_first_i ON nulls_first(t COLLATE nulls xirst);
+
+ALTER OPERATOR CLASS xirst USING btree RENAME TO text_ops;
+DROP TABLE nulls_first;
+DROP COLLATION nulls;
#61Josh Berkus
josh@agliodbs.com
In reply to: Dean Rasheed (#1)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

All:

Because this patch is still being discussed and tinkered with, I have
moved it to 9.4CF2.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

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

#62Greg Stark
stark@mit.edu
In reply to: Josh Berkus (#61)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Mon, Jul 29, 2013 at 8:45 PM, Josh Berkus <josh@agliodbs.com> wrote:

Because this patch is still being discussed and tinkered with, I have
moved it to 9.4CF2.

Fwiw I already committed it. In the end I made only trivial changes
the most significant of which was changing the column name to
"ordinality". I found the changes I was making didn't really make much
difference and were turning into bike shedding.

There are two followup changes that were discussed in this thread:

1) Changing the WITH_* and NULLS_* tokens to not eat the following
token if it's not used by the grammar so that it doesn't interfere
with syntax like "select nulls first from t as a(nulls)".

2) Teaching the parser that the functionscan is ordered by ordinality
so it can do a merge join without resorting the inputs. That would
relieve the one remaining piece of functionality that multiple SRFs in
the target list give over SRFs in the from list.

I have patches for both of these in progress but frankly they're both
stuck and I'm not likely to finish either without some advice. I'll
start new threads (or already have) for them though.

--
greg

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

#63Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Greg Stark (#62)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

Greg Stark escribi�:

There are two followup changes that were discussed in this thread:

1) Changing the WITH_* and NULLS_* tokens to not eat the following
token if it's not used by the grammar so that it doesn't interfere
with syntax like "select nulls first from t as a(nulls)".

I have patches for both of these in progress but frankly they're both
stuck and I'm not likely to finish either without some advice. I'll
start new threads (or already have) for them though.

Andrew Gierth submitted a patch for this ...

--
�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

#64Robert Haas
robertmhaas@gmail.com
In reply to: Andrew Gierth (#54)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Mon, Jul 29, 2013 at 1:42 AM, Andrew Gierth
<andrew@tao11.riddles.org.uk> wrote:

I propose the following patch (which goes on top of the current
ordinality one) to implement the suggested grammar changes.

I think this is the cleanest way, and I've tested that it both
passes regression and allows constructs like WITH time AS (...)
to work.

This looks like really nice work.

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

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

#65Greg Stark
stark@mit.edu
In reply to: Robert Haas (#64)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Mon, Aug 5, 2013 at 1:31 AM, Robert Haas <robertmhaas@gmail.com> wrote:

This looks like really nice work.

It does. It's functionally equivalent to my attempt but with much better
comments and cleaner code.

But it doesn't seem to cover the case I was stumped on, namely "nulls
first" appearing in a place where two unreserved keywords can appear
consecutively. This doesn't happen for WITH_* before "with" is a reserved
keyword.

Looking into it a bit I see that the case I was most worried about is
actually a non-issue. We don't support column aliases without "AS" unless
the alias is completely unknown to the parser. That seems a bit of a
strange rule that must make the syntax with the missing AS pretty
unreliable if people are looking for code that will continue to work in
future versions. I never knew about this.

The only other case I could come up with in my regression tests is pretty
esoteric:

CREATE COLLATION nulls (locale='C');
ALTER OPERATOR CLASS text_ops USING btree RENAME TO first;
CREATE TABLE nulls_first(t text);
CREATE INDEX nulls_first_i ON nulls_first(t COLLATE nulls first);

I'm not 100% sure there aren't other cases where this can occur though.

--
greg

#66David Fetter
david@fetter.org
In reply to: Greg Stark (#65)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Tue, Aug 06, 2013 at 11:10:11PM +0100, Greg Stark wrote:

On Mon, Aug 5, 2013 at 1:31 AM, Robert Haas <robertmhaas@gmail.com> wrote:

This looks like really nice work.

It does. It's functionally equivalent to my attempt but with much better
comments and cleaner code.

But it doesn't seem to cover the case I was stumped on, namely "nulls
first" appearing in a place where two unreserved keywords can appear
consecutively. This doesn't happen for WITH_* before "with" is a reserved
keyword.

Looking into it a bit I see that the case I was most worried about is
actually a non-issue. We don't support column aliases without "AS" unless
the alias is completely unknown to the parser. That seems a bit of a
strange rule that must make the syntax with the missing AS pretty
unreliable if people are looking for code that will continue to work in
future versions. I never knew about this.

The only other case I could come up with in my regression tests is pretty
esoteric:

CREATE COLLATION nulls (locale='C');
ALTER OPERATOR CLASS text_ops USING btree RENAME TO first;
CREATE TABLE nulls_first(t text);
CREATE INDEX nulls_first_i ON nulls_first(t COLLATE nulls first);

I am pretty sure we dismiss as "pilot error" foolishness at levels
much lower than this.

I'm not 100% sure there aren't other cases where this can occur though.

If you don't find one considerably simpler, I'm inclined to say we
should let it lie, possibly with docs--even user-visible ones if you
think it's appropriate.

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

#67Robert Haas
robertmhaas@gmail.com
In reply to: Greg Stark (#65)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Tue, Aug 6, 2013 at 6:10 PM, Greg Stark <stark@mit.edu> wrote:

The only other case I could come up with in my regression tests is pretty
esoteric:

CREATE COLLATION nulls (locale='C');
ALTER OPERATOR CLASS text_ops USING btree RENAME TO first;
CREATE TABLE nulls_first(t text);
CREATE INDEX nulls_first_i ON nulls_first(t COLLATE nulls first);

I'm not 100% sure there aren't other cases where this can occur though.

Blech. Well, that's why we need to stop hacking the lexer before we shoot
a hole through our foot that's too large to ignore. But it's not this
patch's job to fix that problem.

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

#68Greg Stark
stark@mit.edu
In reply to: Robert Haas (#67)
Re: Review: UNNEST (and other functions) WITH ORDINALITY

On Tue, Aug 13, 2013 at 8:20 PM, Robert Haas <robertmhaas@gmail.com> wrote:

Blech. Well, that's why we need to stop hacking the lexer before we shoot a
hole through our foot that's too large to ignore. But it's not this patch's
job to fix that problem.

Hm. I thought it was. However it turns out the NULLS FIRST and the
WITH* problems are not exactly analogous. Because NULLS and FIRST are
both unreserved keywords whereas WITH is a reserved keyword the
problems are really different. Whereas WITH can be fixed by going
through all the places in the grammar where WITH appears, NULLS FIRST
really can't be fixed without reserving NULLS.

--
greg

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