*** 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 ----
+
+ ordinality
+
+
+
+ When a function in the FROM clause is suffixed by
+ WITH ORDINALITY, a bigint 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
+ OUT parameters, but not when using a function
+ returning RECORD with an explicit column definition list.
+
+
+ -- 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)
+
+
+
*** a/doc/src/sgml/ref/select.sgml
--- b/doc/src/sgml/ref/select.sgml
***************
*** 52,58 **** SELECT [ ALL | DISTINCT [ ON ( expressiontable_name [ * ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
[ LATERAL ] ( select ) [ AS ] alias [ ( column_alias [, ...] ) ]
with_query_name [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
! [ LATERAL ] function_name ( [ argument [, ...] ] ) [ AS ] alias [ ( column_alias [, ...] | column_definition [, ...] ) ]
[ LATERAL ] function_name ( [ argument [, ...] ] ) AS ( column_definition [, ...] )
from_item [ NATURAL ] join_type from_item [ ON join_condition | USING ( join_column [, ...] ) ]
--- 52,59 ----
[ ONLY ] table_name [ * ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
[ LATERAL ] ( select ) [ AS ] alias [ ( column_alias [, ...] ) ]
with_query_name [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
! [ LATERAL ] function_name ( [ argument [, ...] ] ) [ WITH ORDINALITY ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
! [ LATERAL ] function_name ( [ argument [, ...] ] ) [ AS ] alias ( column_definition [, ...] )
[ LATERAL ] function_name ( [ argument [, ...] ] ) AS ( column_definition [, ...] )
from_item [ NATURAL ] join_type from_item [ ON join_condition | USING ( join_column [, ...] ) ]
***************
*** 368,385 **** TABLE [ ONLY ] table_name [ * ]
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 SELECT 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 record>
! data type, then an alias or the key word AS> must
! be present, followed by a column definition list in the form
! ( column_name data_type , ... >
! ). The column definition list must match the actual
! number and types of columns returned by the function.
--- 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 SELECT command.
! When the optional WITH ORDINALITY 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:
!
! 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)
!
! 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 ORDINALITY if present.
!
!
!
! If the function has been defined as returning the
! record> data type, then an alias or the key word
! AS> must be present, followed by a column
! definition list in the form ( column_name data_type , ...
! >). The column definition list must match the
! actual number and types of columns returned by the function.
! ORDINALITY does not work in this case.
*** 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();