Add SQL Standard WITH ORDINALITY support for UNNEST (and any other SRF)

Author: Andrew Gierth, David Fetter
Reviewers: Dean Rasheed, Jeevan Chalke, Stephen Frost
This commit is contained in:
Greg Stark 2013-07-29 16:38:01 +01:00
parent 55cbfa5366
commit c62736cc37
22 changed files with 1486 additions and 323 deletions

View File

@ -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
@ -13286,6 +13286,48 @@ postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
3
4
(4 rows)
</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 most useful in the case of set returning functions
such as UNNEST(). 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>
-- set returning function 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>

View File

@ -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,40 @@ 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 | ordinality
--------+----------
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>

View File

@ -157,6 +157,40 @@ CreateTupleDescCopy(TupleDesc tupdesc)
return desc;
}
/*
* 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

View File

@ -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,37 @@ 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:
*
* 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,19 +91,62 @@ 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);
/*
* 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,7 +186,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 +219,16 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
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
*/
@ -159,42 +240,55 @@ 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.
* 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,
&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,15 +301,47 @@ 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 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,6 +375,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 +396,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.
*/

View File

@ -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);

View File

@ -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);

View File

@ -126,6 +126,10 @@ makeVarFromTargetEntry(Index varno,
* 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,9 +155,33 @@ makeWholeRowVar(RangeTblEntry *rte,
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 (type_is_rowtype(toid))
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,8 +212,8 @@ makeWholeRowVar(RangeTblEntry *rte,
varlevelsup);
}
break;
default:
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

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -4452,10 +4452,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;
/*

View File

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

View File

@ -787,18 +787,24 @@ 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.
* 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)
buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality)
{
int maxattrs = tupdesc->natts;
ListCell *aliaslc;
@ -850,12 +856,33 @@ 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("ordinality"));
}
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)));
eref->aliasname,
maxattrs - numdropped + (ordinality ? 1 : 0),
numaliases)));
}
/*
@ -867,48 +894,60 @@ 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
* 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)
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);
/*
* 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));
}
/*
* Otherwise use the previously-determined alias (not necessarily the
* function name!)
*/
eref->colnames = list_make1(makeString(eref->aliasname));
/* 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("ordinality")));
return;
}
/*
@ -1004,7 +1043,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
@ -1064,7 +1103,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.
@ -1235,17 +1274,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.
@ -1288,6 +1333,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
* permissions mechanism).
*/
rte->lateral = lateral;
rte->funcordinality = rangefunc->ordinality;
rte->inh = false; /* never true for functions */
rte->inFromCl = inFromCl;
@ -1643,6 +1689,11 @@ addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
* 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,6 +1762,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
TypeFuncClass functypclass;
Oid funcrettype;
TupleDesc tupdesc;
int ordinality_attno = 0;
functypclass = get_expr_result_type(rte->funcexpr,
&funcrettype,
@ -1719,9 +1771,16 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
{
/* 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,6 +1801,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
*colvars = lappend(*colvars, varnode);
}
ordinality_attno = 2;
}
else if (functypclass == TYPEFUNC_RECORD)
{
@ -1774,12 +1835,34 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
*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,6 +2038,9 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
/*
* 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,6 +2200,9 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
/*
* 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,6 +2261,20 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
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,6 +2285,7 @@ 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,
@ -2208,6 +2312,8 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
}
else if (functypclass == TYPEFUNC_SCALAR)
{
Assert(attnum == 1);
/* Base data type, i.e. scalar */
*vartype = funcrettype;
*vartypmod = -1;
@ -2332,7 +2438,17 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
Oid funcrettype = exprType(rte->funcexpr);
Oid funcrelid = typeidTypeRelid(funcrettype);
if (OidIsValid(funcrelid))
/*
* 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

View File

@ -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;

View File

@ -8004,6 +8004,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 */

View File

@ -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);

View File

@ -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 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,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;

View File

@ -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
@ -651,8 +652,13 @@ 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.
* 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,15 +757,21 @@ 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.
* 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):

View File

@ -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 */

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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();