mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
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:
parent
55cbfa5366
commit
c62736cc37
@ -13278,7 +13278,7 @@ select $1[i][j]
|
|||||||
generate_subscripts($1,2) g2(j);
|
generate_subscripts($1,2) g2(j);
|
||||||
$$ LANGUAGE sql IMMUTABLE;
|
$$ LANGUAGE sql IMMUTABLE;
|
||||||
CREATE FUNCTION
|
CREATE FUNCTION
|
||||||
postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
|
SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
|
||||||
unnest2
|
unnest2
|
||||||
---------
|
---------
|
||||||
1
|
1
|
||||||
@ -13286,6 +13286,48 @@ postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]);
|
|||||||
3
|
3
|
||||||
4
|
4
|
||||||
(4 rows)
|
(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>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
@ -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> [, ...] ) ] ]
|
[ 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> [, ...] ) ]
|
[ 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> [, ...] ) ] ]
|
<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> [, ...] )
|
[ 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> [, ...] ) ]
|
<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
|
clause. (This is especially useful for functions that return
|
||||||
result sets, but any function can be used.) This acts as
|
result sets, but any function can be used.) This acts as
|
||||||
though its output were created as a temporary table for the
|
though its output were created as a temporary table for the
|
||||||
duration of this single <command>SELECT</command> command. An
|
duration of this single <command>SELECT</command> command.
|
||||||
alias can also be used. If an alias is written, a column alias
|
When the optional <command>WITH ORDINALITY</command> is
|
||||||
list can also be written to provide substitute names for one
|
appended to the function call, a new column is appended after
|
||||||
or more attributes of the function's composite return type. If
|
all the function call's columns with numbering for each row.
|
||||||
the function has been defined as returning the <type>record</>
|
For example:
|
||||||
data type, then an alias or the key word <literal>AS</> must
|
<programlisting>
|
||||||
be present, followed by a column definition list in the form
|
SELECT * FROM unnest(ARRAY['a','b','c','d','e','f']) WITH ORDINALITY;
|
||||||
<literal>( <replaceable
|
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">column_name</replaceable> <replaceable
|
||||||
class="parameter">data_type</replaceable> <optional>, ... </>
|
class="parameter">data_type</replaceable> <optional>, ...
|
||||||
)</literal>. The column definition list must match the actual
|
</>)</literal>. The column definition list must match the
|
||||||
number and types of columns returned by the function.
|
actual number and types of columns returned by the function.
|
||||||
|
<literal>ORDINALITY</literal> does not work in this case.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -157,6 +157,40 @@ CreateTupleDescCopy(TupleDesc tupdesc)
|
|||||||
return desc;
|
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
|
* CreateTupleDescCopyConstr
|
||||||
* This function creates a new TupleDesc by copying from an existing
|
* This function creates a new TupleDesc by copying from an existing
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
#include "executor/nodeFunctionscan.h"
|
#include "executor/nodeFunctionscan.h"
|
||||||
#include "funcapi.h"
|
#include "funcapi.h"
|
||||||
#include "nodes/nodeFuncs.h"
|
#include "nodes/nodeFuncs.h"
|
||||||
|
#include "catalog/pg_type.h"
|
||||||
|
|
||||||
static TupleTableSlot *FunctionNext(FunctionScanState *node);
|
static TupleTableSlot *FunctionNext(FunctionScanState *node);
|
||||||
|
|
||||||
@ -42,10 +42,37 @@ static TupleTableSlot *FunctionNext(FunctionScanState *node);
|
|||||||
static TupleTableSlot *
|
static TupleTableSlot *
|
||||||
FunctionNext(FunctionScanState *node)
|
FunctionNext(FunctionScanState *node)
|
||||||
{
|
{
|
||||||
TupleTableSlot *slot;
|
|
||||||
EState *estate;
|
EState *estate;
|
||||||
ScanDirection direction;
|
ScanDirection direction;
|
||||||
Tuplestorestate *tuplestorestate;
|
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
|
* get information from the estate and scan state
|
||||||
@ -64,19 +91,62 @@ FunctionNext(FunctionScanState *node)
|
|||||||
node->tuplestorestate = tuplestorestate =
|
node->tuplestorestate = tuplestorestate =
|
||||||
ExecMakeTableFunctionResult(node->funcexpr,
|
ExecMakeTableFunctionResult(node->funcexpr,
|
||||||
node->ss.ps.ps_ExprContext,
|
node->ss.ps.ps_ExprContext,
|
||||||
node->tupdesc,
|
node->func_tupdesc,
|
||||||
node->eflags & EXEC_FLAG_BACKWARD);
|
node->eflags & EXEC_FLAG_BACKWARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the next tuple from tuplestore. Return NULL if no more tuples.
|
* Get the next tuple from tuplestore. Return NULL if no more tuples.
|
||||||
*/
|
*/
|
||||||
slot = node->ss.ss_ScanTupleSlot;
|
|
||||||
(void) tuplestore_gettupleslot(tuplestorestate,
|
(void) tuplestore_gettupleslot(tuplestorestate,
|
||||||
ScanDirectionIsForward(direction),
|
ScanDirectionIsForward(direction),
|
||||||
false,
|
false,
|
||||||
slot);
|
funcslot);
|
||||||
return slot;
|
|
||||||
|
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;
|
FunctionScanState *scanstate;
|
||||||
Oid funcrettype;
|
Oid funcrettype;
|
||||||
TypeFuncClass functypclass;
|
TypeFuncClass functypclass;
|
||||||
TupleDesc tupdesc = NULL;
|
TupleDesc func_tupdesc = NULL;
|
||||||
|
TupleDesc scan_tupdesc = NULL;
|
||||||
|
|
||||||
/* check for unsupported flags */
|
/* check for unsupported flags */
|
||||||
Assert(!(eflags & EXEC_FLAG_MARK));
|
Assert(!(eflags & EXEC_FLAG_MARK));
|
||||||
@ -148,6 +219,16 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
|
|||||||
ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
|
ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
|
||||||
ExecInitScanTupleSlot(estate, &scanstate->ss);
|
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
|
* initialize child expressions
|
||||||
*/
|
*/
|
||||||
@ -159,42 +240,55 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
|
|||||||
(PlanState *) scanstate);
|
(PlanState *) scanstate);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now determine if the function returns a simple or composite type, and
|
* Now determine if the function returns a simple or composite
|
||||||
* build an appropriate tupdesc.
|
* 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,
|
functypclass = get_expr_result_type(node->funcexpr,
|
||||||
&funcrettype,
|
&funcrettype,
|
||||||
&tupdesc);
|
&func_tupdesc);
|
||||||
|
|
||||||
if (functypclass == TYPEFUNC_COMPOSITE)
|
if (functypclass == TYPEFUNC_COMPOSITE)
|
||||||
{
|
{
|
||||||
/* Composite data type, e.g. a table's row type */
|
/* 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 */
|
/* Must copy it out of typcache for safety */
|
||||||
tupdesc = CreateTupleDescCopy(tupdesc);
|
func_tupdesc = CreateTupleDescCopy(func_tupdesc);
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_SCALAR)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
/* Base data type, i.e. scalar */
|
/* Base data type, i.e. scalar */
|
||||||
char *attname = strVal(linitial(node->funccolnames));
|
char *attname = strVal(linitial(node->funccolnames));
|
||||||
|
|
||||||
tupdesc = CreateTemplateTupleDesc(1, false);
|
func_tupdesc = CreateTemplateTupleDesc(1, false);
|
||||||
TupleDescInitEntry(tupdesc,
|
TupleDescInitEntry(func_tupdesc,
|
||||||
(AttrNumber) 1,
|
(AttrNumber) 1,
|
||||||
attname,
|
attname,
|
||||||
funcrettype,
|
funcrettype,
|
||||||
-1,
|
-1,
|
||||||
0);
|
0);
|
||||||
TupleDescInitEntryCollation(tupdesc,
|
TupleDescInitEntryCollation(func_tupdesc,
|
||||||
(AttrNumber) 1,
|
(AttrNumber) 1,
|
||||||
exprCollation(node->funcexpr));
|
exprCollation(node->funcexpr));
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_RECORD)
|
else if (functypclass == TYPEFUNC_RECORD)
|
||||||
{
|
{
|
||||||
tupdesc = BuildDescFromLists(node->funccolnames,
|
func_tupdesc = BuildDescFromLists(node->funccolnames,
|
||||||
node->funccoltypes,
|
node->funccoltypes,
|
||||||
node->funccoltypmods,
|
node->funccoltypmods,
|
||||||
node->funccolcollations);
|
node->funccolcollations);
|
||||||
}
|
}
|
||||||
else
|
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
|
* function should do this for itself, but let's cover things in case it
|
||||||
* doesn't.)
|
* 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
|
* Other node-specific setup
|
||||||
*/
|
*/
|
||||||
|
scanstate->ordinal = 0;
|
||||||
scanstate->tuplestorestate = NULL;
|
scanstate->tuplestorestate = NULL;
|
||||||
|
|
||||||
scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
|
scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
|
||||||
(PlanState *) scanstate);
|
(PlanState *) scanstate);
|
||||||
|
|
||||||
@ -249,6 +375,8 @@ ExecEndFunctionScan(FunctionScanState *node)
|
|||||||
*/
|
*/
|
||||||
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
|
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
|
||||||
ExecClearTuple(node->ss.ss_ScanTupleSlot);
|
ExecClearTuple(node->ss.ss_ScanTupleSlot);
|
||||||
|
if (node->func_slot)
|
||||||
|
ExecClearTuple(node->func_slot);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Release tuplestore resources
|
* Release tuplestore resources
|
||||||
@ -268,9 +396,13 @@ void
|
|||||||
ExecReScanFunctionScan(FunctionScanState *node)
|
ExecReScanFunctionScan(FunctionScanState *node)
|
||||||
{
|
{
|
||||||
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
|
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
|
||||||
|
if (node->func_slot)
|
||||||
|
ExecClearTuple(node->func_slot);
|
||||||
|
|
||||||
ExecScanReScan(&node->ss);
|
ExecScanReScan(&node->ss);
|
||||||
|
|
||||||
|
node->ordinal = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we haven't materialized yet, just return.
|
* If we haven't materialized yet, just return.
|
||||||
*/
|
*/
|
||||||
|
@ -509,6 +509,7 @@ _copyFunctionScan(const FunctionScan *from)
|
|||||||
COPY_NODE_FIELD(funccoltypes);
|
COPY_NODE_FIELD(funccoltypes);
|
||||||
COPY_NODE_FIELD(funccoltypmods);
|
COPY_NODE_FIELD(funccoltypmods);
|
||||||
COPY_NODE_FIELD(funccolcollations);
|
COPY_NODE_FIELD(funccolcollations);
|
||||||
|
COPY_SCALAR_FIELD(funcordinality);
|
||||||
|
|
||||||
return newnode;
|
return newnode;
|
||||||
}
|
}
|
||||||
@ -1983,6 +1984,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
|
|||||||
COPY_NODE_FIELD(funccoltypes);
|
COPY_NODE_FIELD(funccoltypes);
|
||||||
COPY_NODE_FIELD(funccoltypmods);
|
COPY_NODE_FIELD(funccoltypmods);
|
||||||
COPY_NODE_FIELD(funccolcollations);
|
COPY_NODE_FIELD(funccolcollations);
|
||||||
|
COPY_SCALAR_FIELD(funcordinality);
|
||||||
COPY_NODE_FIELD(values_lists);
|
COPY_NODE_FIELD(values_lists);
|
||||||
COPY_NODE_FIELD(values_collations);
|
COPY_NODE_FIELD(values_collations);
|
||||||
COPY_STRING_FIELD(ctename);
|
COPY_STRING_FIELD(ctename);
|
||||||
@ -2296,6 +2298,7 @@ _copyRangeFunction(const RangeFunction *from)
|
|||||||
{
|
{
|
||||||
RangeFunction *newnode = makeNode(RangeFunction);
|
RangeFunction *newnode = makeNode(RangeFunction);
|
||||||
|
|
||||||
|
COPY_SCALAR_FIELD(ordinality);
|
||||||
COPY_SCALAR_FIELD(lateral);
|
COPY_SCALAR_FIELD(lateral);
|
||||||
COPY_NODE_FIELD(funccallnode);
|
COPY_NODE_FIELD(funccallnode);
|
||||||
COPY_NODE_FIELD(alias);
|
COPY_NODE_FIELD(alias);
|
||||||
|
@ -2126,6 +2126,7 @@ _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
|
|||||||
static bool
|
static bool
|
||||||
_equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
|
_equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
|
||||||
{
|
{
|
||||||
|
COMPARE_SCALAR_FIELD(ordinality);
|
||||||
COMPARE_SCALAR_FIELD(lateral);
|
COMPARE_SCALAR_FIELD(lateral);
|
||||||
COMPARE_NODE_FIELD(funccallnode);
|
COMPARE_NODE_FIELD(funccallnode);
|
||||||
COMPARE_NODE_FIELD(alias);
|
COMPARE_NODE_FIELD(alias);
|
||||||
@ -2234,6 +2235,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
|
|||||||
COMPARE_NODE_FIELD(funccoltypes);
|
COMPARE_NODE_FIELD(funccoltypes);
|
||||||
COMPARE_NODE_FIELD(funccoltypmods);
|
COMPARE_NODE_FIELD(funccoltypmods);
|
||||||
COMPARE_NODE_FIELD(funccolcollations);
|
COMPARE_NODE_FIELD(funccolcollations);
|
||||||
|
COMPARE_SCALAR_FIELD(funcordinality);
|
||||||
COMPARE_NODE_FIELD(values_lists);
|
COMPARE_NODE_FIELD(values_lists);
|
||||||
COMPARE_NODE_FIELD(values_collations);
|
COMPARE_NODE_FIELD(values_collations);
|
||||||
COMPARE_STRING_FIELD(ctename);
|
COMPARE_STRING_FIELD(ctename);
|
||||||
|
@ -126,6 +126,10 @@ makeVarFromTargetEntry(Index varno,
|
|||||||
* returning a non-composite result type, we produce a normal Var referencing
|
* returning a non-composite result type, we produce a normal Var referencing
|
||||||
* the function's result directly, instead of the single-column composite
|
* the function's result directly, instead of the single-column composite
|
||||||
* value that the whole-row notation might otherwise suggest.
|
* 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 *
|
Var *
|
||||||
makeWholeRowVar(RangeTblEntry *rte,
|
makeWholeRowVar(RangeTblEntry *rte,
|
||||||
@ -151,9 +155,33 @@ makeWholeRowVar(RangeTblEntry *rte,
|
|||||||
InvalidOid,
|
InvalidOid,
|
||||||
varlevelsup);
|
varlevelsup);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RTE_FUNCTION:
|
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);
|
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 */
|
/* func returns composite; same as relation case */
|
||||||
result = makeVar(varno,
|
result = makeVar(varno,
|
||||||
@ -184,8 +212,8 @@ makeWholeRowVar(RangeTblEntry *rte,
|
|||||||
varlevelsup);
|
varlevelsup);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
|
|
||||||
|
default:
|
||||||
/*
|
/*
|
||||||
* RTE is a join, subselect, or VALUES. We represent this as a
|
* 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
|
* whole-row Var of RECORD type. (Note that in most cases the Var
|
||||||
|
@ -521,6 +521,7 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
|
|||||||
WRITE_NODE_FIELD(funccoltypes);
|
WRITE_NODE_FIELD(funccoltypes);
|
||||||
WRITE_NODE_FIELD(funccoltypmods);
|
WRITE_NODE_FIELD(funccoltypmods);
|
||||||
WRITE_NODE_FIELD(funccolcollations);
|
WRITE_NODE_FIELD(funccolcollations);
|
||||||
|
WRITE_BOOL_FIELD(funcordinality);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -2382,6 +2383,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
|
|||||||
WRITE_NODE_FIELD(funccoltypes);
|
WRITE_NODE_FIELD(funccoltypes);
|
||||||
WRITE_NODE_FIELD(funccoltypmods);
|
WRITE_NODE_FIELD(funccoltypmods);
|
||||||
WRITE_NODE_FIELD(funccolcollations);
|
WRITE_NODE_FIELD(funccolcollations);
|
||||||
|
WRITE_BOOL_FIELD(funcordinality);
|
||||||
break;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
WRITE_NODE_FIELD(values_lists);
|
WRITE_NODE_FIELD(values_lists);
|
||||||
@ -2614,6 +2616,7 @@ _outRangeFunction(StringInfo str, const RangeFunction *node)
|
|||||||
{
|
{
|
||||||
WRITE_NODE_TYPE("RANGEFUNCTION");
|
WRITE_NODE_TYPE("RANGEFUNCTION");
|
||||||
|
|
||||||
|
WRITE_BOOL_FIELD(ordinality);
|
||||||
WRITE_BOOL_FIELD(lateral);
|
WRITE_BOOL_FIELD(lateral);
|
||||||
WRITE_NODE_FIELD(funccallnode);
|
WRITE_NODE_FIELD(funccallnode);
|
||||||
WRITE_NODE_FIELD(alias);
|
WRITE_NODE_FIELD(alias);
|
||||||
|
@ -1223,6 +1223,7 @@ _readRangeTblEntry(void)
|
|||||||
READ_NODE_FIELD(funccoltypes);
|
READ_NODE_FIELD(funccoltypes);
|
||||||
READ_NODE_FIELD(funccoltypmods);
|
READ_NODE_FIELD(funccoltypmods);
|
||||||
READ_NODE_FIELD(funccolcollations);
|
READ_NODE_FIELD(funccolcollations);
|
||||||
|
READ_BOOL_FIELD(funcordinality);
|
||||||
break;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
READ_NODE_FIELD(values_lists);
|
READ_NODE_FIELD(values_lists);
|
||||||
|
@ -115,8 +115,8 @@ static BitmapHeapScan *make_bitmap_heapscan(List *qptlist,
|
|||||||
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
|
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
|
||||||
List *tidquals);
|
List *tidquals);
|
||||||
static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
|
static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
|
||||||
Index scanrelid, Node *funcexpr, List *funccolnames,
|
Index scanrelid, Node *funcexpr, bool ordinality,
|
||||||
List *funccoltypes, List *funccoltypmods,
|
List *funccolnames, List *funccoltypes, List *funccoltypmods,
|
||||||
List *funccolcollations);
|
List *funccolcollations);
|
||||||
static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
|
static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
|
||||||
Index scanrelid, List *values_lists);
|
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,
|
scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
|
||||||
funcexpr,
|
funcexpr,
|
||||||
|
rte->funcordinality,
|
||||||
rte->eref->colnames,
|
rte->eref->colnames,
|
||||||
rte->funccoltypes,
|
rte->funccoltypes,
|
||||||
rte->funccoltypmods,
|
rte->funccoltypmods,
|
||||||
@ -3366,6 +3367,7 @@ make_functionscan(List *qptlist,
|
|||||||
List *qpqual,
|
List *qpqual,
|
||||||
Index scanrelid,
|
Index scanrelid,
|
||||||
Node *funcexpr,
|
Node *funcexpr,
|
||||||
|
bool ordinality,
|
||||||
List *funccolnames,
|
List *funccolnames,
|
||||||
List *funccoltypes,
|
List *funccoltypes,
|
||||||
List *funccoltypmods,
|
List *funccoltypmods,
|
||||||
@ -3381,6 +3383,7 @@ make_functionscan(List *qptlist,
|
|||||||
plan->righttree = NULL;
|
plan->righttree = NULL;
|
||||||
node->scan.scanrelid = scanrelid;
|
node->scan.scanrelid = scanrelid;
|
||||||
node->funcexpr = funcexpr;
|
node->funcexpr = funcexpr;
|
||||||
|
node->funcordinality = ordinality;
|
||||||
node->funccolnames = funccolnames;
|
node->funccolnames = funccolnames;
|
||||||
node->funccoltypes = funccoltypes;
|
node->funccoltypes = funccoltypes;
|
||||||
node->funccoltypmods = funccoltypmods;
|
node->funccoltypmods = funccoltypmods;
|
||||||
|
@ -4452,10 +4452,15 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
|
|||||||
*/
|
*/
|
||||||
check_stack_depth();
|
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 */
|
/* Fail if FROM item isn't a simple FuncExpr */
|
||||||
fexpr = (FuncExpr *) rte->funcexpr;
|
fexpr = (FuncExpr *) rte->funcexpr;
|
||||||
if (fexpr == NULL || !IsA(fexpr, FuncExpr))
|
if (fexpr == NULL || !IsA(fexpr, FuncExpr))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
func_oid = fexpr->funcid;
|
func_oid = fexpr->funcid;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -566,7 +566,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
|||||||
NULLS_P NUMERIC
|
NULLS_P NUMERIC
|
||||||
|
|
||||||
OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
|
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
|
PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
|
||||||
PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
|
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
|
* list and so can never be entered directly. The filter in parser.c
|
||||||
* creates these tokens when required.
|
* 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 */
|
/* Precedence: lowest to highest */
|
||||||
%nonassoc SET /* see relation_expr_opt_alias */
|
%nonassoc SET /* see relation_expr_opt_alias */
|
||||||
@ -9588,20 +9587,42 @@ table_ref: relation_expr opt_alias_clause
|
|||||||
{
|
{
|
||||||
RangeFunction *n = makeNode(RangeFunction);
|
RangeFunction *n = makeNode(RangeFunction);
|
||||||
n->lateral = false;
|
n->lateral = false;
|
||||||
|
n->ordinality = false;
|
||||||
n->funccallnode = $1;
|
n->funccallnode = $1;
|
||||||
n->alias = linitial($2);
|
n->alias = linitial($2);
|
||||||
n->coldeflist = lsecond($2);
|
n->coldeflist = lsecond($2);
|
||||||
$$ = (Node *) n;
|
$$ = (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
|
| LATERAL_P func_table func_alias_clause
|
||||||
{
|
{
|
||||||
RangeFunction *n = makeNode(RangeFunction);
|
RangeFunction *n = makeNode(RangeFunction);
|
||||||
n->lateral = true;
|
n->lateral = true;
|
||||||
|
n->ordinality = false;
|
||||||
n->funccallnode = $2;
|
n->funccallnode = $2;
|
||||||
n->alias = linitial($3);
|
n->alias = linitial($3);
|
||||||
n->coldeflist = lsecond($3);
|
n->coldeflist = lsecond($3);
|
||||||
$$ = (Node *) n;
|
$$ = (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
|
| select_with_parens opt_alias_clause
|
||||||
{
|
{
|
||||||
RangeSubselect *n = makeNode(RangeSubselect);
|
RangeSubselect *n = makeNode(RangeSubselect);
|
||||||
@ -12575,6 +12596,7 @@ unreserved_keyword:
|
|||||||
| OPERATOR
|
| OPERATOR
|
||||||
| OPTION
|
| OPTION
|
||||||
| OPTIONS
|
| OPTIONS
|
||||||
|
| ORDINALITY
|
||||||
| OVER
|
| OVER
|
||||||
| OWNED
|
| OWNED
|
||||||
| OWNER
|
| OWNER
|
||||||
|
@ -787,18 +787,24 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
|
|||||||
* buildRelationAliases
|
* buildRelationAliases
|
||||||
* Construct the eref column name list for a relation RTE.
|
* Construct the eref column name list for a relation RTE.
|
||||||
* This code is also used for the case of a function RTE returning
|
* 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
|
* tupdesc: the physical column information
|
||||||
* alias: the user-supplied alias, or NULL if none
|
* alias: the user-supplied alias, or NULL if none
|
||||||
* eref: the eref Alias to store column names in
|
* 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
|
* 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
|
* empty strings for any dropped columns, so that it will be one-to-one with
|
||||||
* physical column numbers.
|
* 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
|
static void
|
||||||
buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
|
buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality)
|
||||||
{
|
{
|
||||||
int maxattrs = tupdesc->natts;
|
int maxattrs = tupdesc->natts;
|
||||||
ListCell *aliaslc;
|
ListCell *aliaslc;
|
||||||
@ -850,12 +856,33 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
|
|||||||
eref->colnames = lappend(eref->colnames, attrname);
|
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? */
|
/* Too many user-supplied aliases? */
|
||||||
if (aliaslc)
|
if (aliaslc)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
||||||
errmsg("table \"%s\" has %d columns available but %d columns specified",
|
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)
|
* funcname: function name (used only for error message)
|
||||||
* alias: the user-supplied alias, or NULL if none
|
* alias: the user-supplied alias, or NULL if none
|
||||||
* eref: the eref Alias to store column names in
|
* eref: the eref Alias to store column names in
|
||||||
|
* ordinality: whether to add an ordinality column
|
||||||
*
|
*
|
||||||
* eref->colnames is filled in.
|
* 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
|
static void
|
||||||
buildScalarFunctionAlias(Node *funcexpr, char *funcname,
|
buildScalarFunctionAlias(Node *funcexpr, char *funcname,
|
||||||
Alias *alias, Alias *eref)
|
Alias *alias, Alias *eref, bool ordinality)
|
||||||
{
|
{
|
||||||
char *pname;
|
|
||||||
|
|
||||||
Assert(eref->colnames == NIL);
|
Assert(eref->colnames == NIL);
|
||||||
|
|
||||||
/* Use user-specified column alias if there is one. */
|
/* Use user-specified column alias if there is one. */
|
||||||
if (alias && alias->colnames != NIL)
|
if (alias && alias->colnames != NIL)
|
||||||
{
|
{
|
||||||
if (list_length(alias->colnames) != 1)
|
if (list_length(alias->colnames) > (ordinality ? 2 : 1))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
||||||
errmsg("too many column aliases specified for function %s",
|
errmsg("too many column aliases specified for function %s",
|
||||||
funcname)));
|
funcname)));
|
||||||
|
|
||||||
eref->colnames = copyObject(alias->colnames);
|
eref->colnames = copyObject(alias->colnames);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
/*
|
|
||||||
* 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);
|
char *pname = NULL;
|
||||||
if (pname)
|
|
||||||
{
|
/*
|
||||||
eref->colnames = list_make1(makeString(pname));
|
* If the expression is a simple function call, and the function has a
|
||||||
return;
|
* 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. */
|
||||||
* Otherwise use the previously-determined alias (not necessarily the
|
if (ordinality && list_length(eref->colnames) < 2)
|
||||||
* function name!)
|
eref->colnames = lappend(eref->colnames, makeString(pstrdup("ordinality")));
|
||||||
*/
|
|
||||||
eref->colnames = list_make1(makeString(eref->aliasname));
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1004,7 +1043,7 @@ addRangeTableEntry(ParseState *pstate,
|
|||||||
* and/or actual column names.
|
* and/or actual column names.
|
||||||
*/
|
*/
|
||||||
rte->eref = makeAlias(refname, NIL);
|
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
|
* 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.
|
* and/or actual column names.
|
||||||
*/
|
*/
|
||||||
rte->eref = makeAlias(refname, NIL);
|
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.
|
* Set flags and access permissions.
|
||||||
@ -1235,17 +1274,23 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
|||||||
/* Composite data type, e.g. a table's row type */
|
/* Composite data type, e.g. a table's row type */
|
||||||
Assert(tupdesc);
|
Assert(tupdesc);
|
||||||
/* Build the column alias list */
|
/* Build the column alias list */
|
||||||
buildRelationAliases(tupdesc, alias, eref);
|
buildRelationAliases(tupdesc, alias, eref, rangefunc->ordinality);
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_SCALAR)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
/* Base data type, i.e. scalar */
|
/* Base data type, i.e. scalar */
|
||||||
buildScalarFunctionAlias(funcexpr, funcname, alias, eref);
|
buildScalarFunctionAlias(funcexpr, funcname, alias, eref, rangefunc->ordinality);
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_RECORD)
|
else if (functypclass == TYPEFUNC_RECORD)
|
||||||
{
|
{
|
||||||
ListCell *col;
|
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
|
* Use the column definition list to form the alias list and
|
||||||
* funccoltypes/funccoltypmods/funccolcollations lists.
|
* funccoltypes/funccoltypmods/funccolcollations lists.
|
||||||
@ -1288,6 +1333,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
|||||||
* permissions mechanism).
|
* permissions mechanism).
|
||||||
*/
|
*/
|
||||||
rte->lateral = lateral;
|
rte->lateral = lateral;
|
||||||
|
rte->funcordinality = rangefunc->ordinality;
|
||||||
rte->inh = false; /* never true for functions */
|
rte->inh = false; /* never true for functions */
|
||||||
rte->inFromCl = inFromCl;
|
rte->inFromCl = inFromCl;
|
||||||
|
|
||||||
@ -1643,6 +1689,11 @@ addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
|
|||||||
* The output lists go into *colnames and *colvars.
|
* The output lists go into *colnames and *colvars.
|
||||||
* If only one of the two kinds of output list is needed, pass NULL for the
|
* If only one of the two kinds of output list is needed, pass NULL for the
|
||||||
* output pointer for the unwanted one.
|
* 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
|
void
|
||||||
expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
|
expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
|
||||||
@ -1711,6 +1762,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
|
|||||||
TypeFuncClass functypclass;
|
TypeFuncClass functypclass;
|
||||||
Oid funcrettype;
|
Oid funcrettype;
|
||||||
TupleDesc tupdesc;
|
TupleDesc tupdesc;
|
||||||
|
int ordinality_attno = 0;
|
||||||
|
|
||||||
functypclass = get_expr_result_type(rte->funcexpr,
|
functypclass = get_expr_result_type(rte->funcexpr,
|
||||||
&funcrettype,
|
&funcrettype,
|
||||||
@ -1719,9 +1771,16 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
|
|||||||
{
|
{
|
||||||
/* Composite data type, e.g. a table's row type */
|
/* Composite data type, e.g. a table's row type */
|
||||||
Assert(tupdesc);
|
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,
|
expandTupleDesc(tupdesc, rte->eref,
|
||||||
rtindex, sublevels_up, location,
|
rtindex, sublevels_up, location,
|
||||||
include_dropped, colnames, colvars);
|
include_dropped, colnames, colvars);
|
||||||
|
|
||||||
|
ordinality_attno = tupdesc->natts + 1;
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_SCALAR)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
@ -1742,6 +1801,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
|
|||||||
|
|
||||||
*colvars = lappend(*colvars, varnode);
|
*colvars = lappend(*colvars, varnode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ordinality_attno = 2;
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_RECORD)
|
else if (functypclass == TYPEFUNC_RECORD)
|
||||||
{
|
{
|
||||||
@ -1774,12 +1835,34 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
|
|||||||
*colvars = lappend(*colvars, varnode);
|
*colvars = lappend(*colvars, varnode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* note, ordinality is not allowed in this case */
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* addRangeTableEntryForFunction should've caught this */
|
/* addRangeTableEntryForFunction should've caught this */
|
||||||
elog(ERROR, "function in FROM has unsupported return type");
|
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;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
@ -1955,6 +2038,9 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* expandTupleDesc -- expandRTE subroutine
|
* 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
|
static void
|
||||||
expandTupleDesc(TupleDesc tupdesc, Alias *eref,
|
expandTupleDesc(TupleDesc tupdesc, Alias *eref,
|
||||||
@ -2114,6 +2200,9 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
|
|||||||
/*
|
/*
|
||||||
* get_rte_attribute_type
|
* get_rte_attribute_type
|
||||||
* Get attribute type/typmod/collation information from a RangeTblEntry
|
* 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
|
void
|
||||||
get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
||||||
@ -2172,6 +2261,20 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
|||||||
Oid funcrettype;
|
Oid funcrettype;
|
||||||
TupleDesc tupdesc;
|
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,
|
functypclass = get_expr_result_type(rte->funcexpr,
|
||||||
&funcrettype,
|
&funcrettype,
|
||||||
&tupdesc);
|
&tupdesc);
|
||||||
@ -2182,6 +2285,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
|||||||
Form_pg_attribute att_tup;
|
Form_pg_attribute att_tup;
|
||||||
|
|
||||||
Assert(tupdesc);
|
Assert(tupdesc);
|
||||||
|
|
||||||
/* this is probably a can't-happen case */
|
/* this is probably a can't-happen case */
|
||||||
if (attnum < 1 || attnum > tupdesc->natts)
|
if (attnum < 1 || attnum > tupdesc->natts)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -2208,6 +2312,8 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
|||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_SCALAR)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
|
Assert(attnum == 1);
|
||||||
|
|
||||||
/* Base data type, i.e. scalar */
|
/* Base data type, i.e. scalar */
|
||||||
*vartype = funcrettype;
|
*vartype = funcrettype;
|
||||||
*vartypmod = -1;
|
*vartypmod = -1;
|
||||||
@ -2332,7 +2438,17 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
|
|||||||
Oid funcrettype = exprType(rte->funcexpr);
|
Oid funcrettype = exprType(rte->funcexpr);
|
||||||
Oid funcrelid = typeidTypeRelid(funcrettype);
|
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
|
* Composite data type, i.e. a table's row type
|
||||||
|
@ -133,7 +133,7 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
|
|||||||
case WITH:
|
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_yylval = lvalp->core_yystype;
|
||||||
cur_yylloc = *llocp;
|
cur_yylloc = *llocp;
|
||||||
@ -143,6 +143,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
|
|||||||
case TIME:
|
case TIME:
|
||||||
cur_token = WITH_TIME;
|
cur_token = WITH_TIME;
|
||||||
break;
|
break;
|
||||||
|
case ORDINALITY:
|
||||||
|
cur_token = WITH_ORDINALITY;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
/* save the lookahead token for next time */
|
/* save the lookahead token for next time */
|
||||||
yyextra->lookahead_token = next_token;
|
yyextra->lookahead_token = next_token;
|
||||||
|
@ -8004,6 +8004,8 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
|
|||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
/* Function RTE */
|
/* Function RTE */
|
||||||
get_rule_expr(rte->funcexpr, context, true);
|
get_rule_expr(rte->funcexpr, context, true);
|
||||||
|
if (rte->funcordinality)
|
||||||
|
appendStringInfoString(buf, " WITH ORDINALITY");
|
||||||
break;
|
break;
|
||||||
case RTE_VALUES:
|
case RTE_VALUES:
|
||||||
/* Values list RTE */
|
/* Values list RTE */
|
||||||
|
@ -87,6 +87,7 @@ extern TupleDesc CreateTupleDesc(int natts, bool hasoid,
|
|||||||
Form_pg_attribute *attrs);
|
Form_pg_attribute *attrs);
|
||||||
|
|
||||||
extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
|
extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
|
||||||
|
extern TupleDesc CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts);
|
||||||
|
|
||||||
extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
|
extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
|
||||||
|
|
||||||
|
@ -1395,7 +1395,10 @@ typedef struct SubqueryScanState
|
|||||||
* function appearing in FROM (typically a function returning set).
|
* function appearing in FROM (typically a function returning set).
|
||||||
*
|
*
|
||||||
* eflags node's capability flags
|
* 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
|
* tuplestorestate private state of tuplestore.c
|
||||||
* funcexpr state for function expression being evaluated
|
* funcexpr state for function expression being evaluated
|
||||||
* ----------------
|
* ----------------
|
||||||
@ -1404,7 +1407,10 @@ typedef struct FunctionScanState
|
|||||||
{
|
{
|
||||||
ScanState ss; /* its first field is NodeTag */
|
ScanState ss; /* its first field is NodeTag */
|
||||||
int eflags;
|
int eflags;
|
||||||
TupleDesc tupdesc;
|
int64 ordinal;
|
||||||
|
TupleDesc scan_tupdesc;
|
||||||
|
TupleDesc func_tupdesc;
|
||||||
|
TupleTableSlot *func_slot;
|
||||||
Tuplestorestate *tuplestorestate;
|
Tuplestorestate *tuplestorestate;
|
||||||
ExprState *funcexpr;
|
ExprState *funcexpr;
|
||||||
} FunctionScanState;
|
} FunctionScanState;
|
||||||
|
@ -471,6 +471,7 @@ typedef struct RangeFunction
|
|||||||
{
|
{
|
||||||
NodeTag type;
|
NodeTag type;
|
||||||
bool lateral; /* does it have LATERAL prefix? */
|
bool lateral; /* does it have LATERAL prefix? */
|
||||||
|
bool ordinality; /* does it have WITH ORDINALITY suffix? */
|
||||||
Node *funccallnode; /* untransformed function call tree */
|
Node *funccallnode; /* untransformed function call tree */
|
||||||
Alias *alias; /* table alias & optional column aliases */
|
Alias *alias; /* table alias & optional column aliases */
|
||||||
List *coldeflist; /* list of ColumnDef nodes to describe result
|
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
|
* dropped columns. Note however that a stored rule may have nonempty
|
||||||
* colnames for columns dropped since the rule was created (and for that
|
* colnames for columns dropped since the rule was created (and for that
|
||||||
* matter the colnames might be out of date due to column renamings).
|
* 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
|
* 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
|
* 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
|
* 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):
|
* Fields valid for a function RTE (else NULL):
|
||||||
*
|
*
|
||||||
* If the function returns RECORD, funccoltypes lists the column types
|
* If the function returns an otherwise-unspecified RECORD, funccoltypes
|
||||||
* declared in the RTE's column type specification, funccoltypmods lists
|
* lists the column types declared in the RTE's column type specification,
|
||||||
* their declared typmods, funccolcollations their collations. Otherwise,
|
* funccoltypmods lists their declared typmods, funccolcollations their
|
||||||
* those fields are NIL.
|
* 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 */
|
Node *funcexpr; /* expression tree for func call */
|
||||||
List *funccoltypes; /* OID list of column type OIDs */
|
List *funccoltypes; /* OID list of column type OIDs */
|
||||||
List *funccoltypmods; /* integer list of column typmods */
|
List *funccoltypmods; /* integer list of column typmods */
|
||||||
List *funccolcollations; /* OID list of column collation OIDs */
|
List *funccolcollations; /* OID list of column collation OIDs */
|
||||||
|
bool funcordinality; /* is this called WITH ORDINALITY? */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fields valid for a values RTE (else NIL):
|
* Fields valid for a values RTE (else NIL):
|
||||||
|
@ -425,6 +425,7 @@ typedef struct FunctionScan
|
|||||||
{
|
{
|
||||||
Scan scan;
|
Scan scan;
|
||||||
Node *funcexpr; /* expression tree for func call */
|
Node *funcexpr; /* expression tree for func call */
|
||||||
|
bool funcordinality; /* WITH ORDINALITY */
|
||||||
List *funccolnames; /* output column names (string Value nodes) */
|
List *funccolnames; /* output column names (string Value nodes) */
|
||||||
List *funccoltypes; /* OID list of column type OIDs */
|
List *funccoltypes; /* OID list of column type OIDs */
|
||||||
List *funccoltypmods; /* integer list of column typmods */
|
List *funccoltypmods; /* integer list of column typmods */
|
||||||
|
@ -269,6 +269,7 @@ PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD)
|
|||||||
PG_KEYWORD("options", OPTIONS, UNRESERVED_KEYWORD)
|
PG_KEYWORD("options", OPTIONS, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("or", OR, RESERVED_KEYWORD)
|
PG_KEYWORD("or", OR, RESERVED_KEYWORD)
|
||||||
PG_KEYWORD("order", ORDER, RESERVED_KEYWORD)
|
PG_KEYWORD("order", ORDER, RESERVED_KEYWORD)
|
||||||
|
PG_KEYWORD("ordinality", ORDINALITY, UNRESERVED_KEYWORD)
|
||||||
PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD)
|
PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD)
|
||||||
PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD)
|
PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD)
|
||||||
PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD)
|
PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -5,11 +5,40 @@ INSERT INTO foo2 VALUES(1, 11);
|
|||||||
INSERT INTO foo2 VALUES(2, 22);
|
INSERT INTO foo2 VALUES(2, 22);
|
||||||
INSERT INTO foo2 VALUES(1, 111);
|
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
|
-- function with implicit LATERAL
|
||||||
select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
|
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
|
-- 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;
|
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
|
-- sql, proretset = f, prorettype = b
|
||||||
CREATE FUNCTION getfoo(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
|
CREATE FUNCTION getfoo(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL;
|
||||||
SELECT * FROM getfoo(1) AS t1;
|
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);
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
||||||
SELECT * FROM vw_getfoo;
|
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
|
-- sql, proretset = t, prorettype = b
|
||||||
DROP VIEW vw_getfoo;
|
DROP VIEW vw_getfoo;
|
||||||
DROP FUNCTION getfoo(int);
|
DROP FUNCTION getfoo(int);
|
||||||
CREATE FUNCTION getfoo(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
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) AS t1;
|
||||||
|
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
||||||
SELECT * FROM vw_getfoo;
|
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
|
-- sql, proretset = t, prorettype = b
|
||||||
DROP VIEW vw_getfoo;
|
DROP VIEW vw_getfoo;
|
||||||
DROP FUNCTION getfoo(int);
|
DROP FUNCTION getfoo(int);
|
||||||
CREATE FUNCTION getfoo(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
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) AS t1;
|
||||||
|
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
||||||
SELECT * FROM vw_getfoo;
|
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
|
-- sql, proretset = f, prorettype = c
|
||||||
DROP VIEW vw_getfoo;
|
DROP VIEW vw_getfoo;
|
||||||
DROP FUNCTION getfoo(int);
|
DROP FUNCTION getfoo(int);
|
||||||
CREATE FUNCTION getfoo(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
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) AS t1;
|
||||||
|
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
||||||
SELECT * FROM vw_getfoo;
|
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
|
-- sql, proretset = t, prorettype = c
|
||||||
DROP VIEW vw_getfoo;
|
DROP VIEW vw_getfoo;
|
||||||
DROP FUNCTION getfoo(int);
|
DROP FUNCTION getfoo(int);
|
||||||
CREATE FUNCTION getfoo(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL;
|
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) AS t1;
|
||||||
|
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
||||||
SELECT * FROM vw_getfoo;
|
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
|
-- sql, proretset = f, prorettype = record
|
||||||
DROP VIEW vw_getfoo;
|
DROP VIEW vw_getfoo;
|
||||||
DROP FUNCTION getfoo(int);
|
DROP FUNCTION getfoo(int);
|
||||||
@ -88,16 +138,24 @@ DROP VIEW vw_getfoo;
|
|||||||
DROP FUNCTION getfoo(int);
|
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;
|
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) AS t1;
|
||||||
|
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o);
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
||||||
SELECT * FROM vw_getfoo;
|
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
|
-- plpgsql, proretset = f, prorettype = c
|
||||||
DROP VIEW vw_getfoo;
|
DROP VIEW vw_getfoo;
|
||||||
DROP FUNCTION getfoo(int);
|
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;
|
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) AS t1;
|
||||||
|
SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o);
|
||||||
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
|
||||||
SELECT * FROM vw_getfoo;
|
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 VIEW vw_getfoo;
|
||||||
DROP FUNCTION getfoo(int);
|
DROP FUNCTION getfoo(int);
|
||||||
@ -106,99 +164,85 @@ DROP TABLE foo2;
|
|||||||
DROP TABLE foo;
|
DROP TABLE foo;
|
||||||
|
|
||||||
-- Rescan tests --
|
-- Rescan tests --
|
||||||
CREATE TABLE foorescan (fooid int, foosubid int, fooname text, primary key(fooid,foosubid));
|
CREATE TEMPORARY SEQUENCE foo_rescan_seq;
|
||||||
INSERT INTO foorescan values(5000,1,'abc.5000.1');
|
CREATE TYPE foo_rescan_t AS (i integer, s bigint);
|
||||||
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');
|
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;
|
||||||
INSERT INTO foorescan values(5001,2,'abc.5001.2');
|
-- plpgsql functions use materialize mode
|
||||||
INSERT INTO foorescan values(5002,2,'abc.5002.2');
|
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(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');
|
--invokes ExecReScanFunctionScan - all these cases should materialize the function only once
|
||||||
INSERT INTO foorescan values(5001,3,'abc.5001.3');
|
-- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function
|
||||||
INSERT INTO foorescan values(5002,3,'abc.5002.3');
|
-- is on the inner path of a nestloop join
|
||||||
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');
|
SELECT setval('foo_rescan_seq',1,false);
|
||||||
INSERT INTO foorescan values(5001,4,'abc.5001.4');
|
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100;
|
||||||
INSERT INTO foorescan values(5002,4,'abc.5002.4');
|
SELECT setval('foo_rescan_seq',1,false);
|
||||||
INSERT INTO foorescan values(5003,4,'abc.5003.4');
|
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(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');
|
SELECT setval('foo_rescan_seq',1,false);
|
||||||
INSERT INTO foorescan values(5001,5,'abc.5001.5');
|
SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100;
|
||||||
INSERT INTO foorescan values(5002,5,'abc.5002.5');
|
SELECT setval('foo_rescan_seq',1,false);
|
||||||
INSERT INTO foorescan values(5003,5,'abc.5003.5');
|
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;
|
||||||
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;
|
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 (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) f(i) ON (r+i)<100;
|
||||||
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]) 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 setval('foo_rescan_seq',1,false);
|
||||||
SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2;
|
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);
|
SELECT setval('foo_rescan_seq',1,false);
|
||||||
INSERT INTO barrescan values(5003);
|
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13);
|
||||||
INSERT INTO barrescan values(5004);
|
SELECT setval('foo_rescan_seq',1,false);
|
||||||
INSERT INTO barrescan values(5005);
|
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o);
|
||||||
INSERT INTO barrescan values(5006);
|
SELECT setval('foo_rescan_seq',1,false);
|
||||||
INSERT INTO barrescan values(5007);
|
SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r);
|
||||||
INSERT INTO barrescan values(5008);
|
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 * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) f(i);
|
||||||
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 (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) WITH ORDINALITY AS f(i,o);
|
||||||
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;
|
-- deep nesting
|
||||||
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 (VALUES (1),(2),(3)) v1(r1),
|
||||||
SELECT * FROM fooview2 AS fv WHERE fv.maxsubid = 5;
|
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 FUNCTION foo_sql(int,int);
|
||||||
DROP VIEW fooview1;
|
DROP FUNCTION foo_mat(int,int);
|
||||||
DROP VIEW fooview2;
|
DROP SEQUENCE foo_rescan_seq;
|
||||||
DROP FUNCTION foorescan(int,int);
|
|
||||||
DROP FUNCTION foorescan(int);
|
|
||||||
DROP TABLE foorescan;
|
|
||||||
DROP TABLE barrescan;
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Test cases involving OUT parameters
|
-- Test cases involving OUT parameters
|
||||||
@ -414,6 +458,7 @@ language sql stable;
|
|||||||
|
|
||||||
SELECT get_users();
|
SELECT get_users();
|
||||||
SELECT * FROM get_users();
|
SELECT * FROM get_users();
|
||||||
|
SELECT * FROM get_users() WITH ORDINALITY; -- make sure ordinality copes
|
||||||
|
|
||||||
drop function get_first_user();
|
drop function get_first_user();
|
||||||
drop function get_users();
|
drop function get_users();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user