aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/access/common/tupdesc.c34
-rw-r--r--src/backend/executor/nodeFunctionscan.c176
-rw-r--r--src/backend/nodes/copyfuncs.c3
-rw-r--r--src/backend/nodes/equalfuncs.c2
-rw-r--r--src/backend/nodes/makefuncs.c32
-rw-r--r--src/backend/nodes/outfuncs.c3
-rw-r--r--src/backend/nodes/readfuncs.c1
-rw-r--r--src/backend/optimizer/plan/createplan.c7
-rw-r--r--src/backend/optimizer/util/clauses.c5
-rw-r--r--src/backend/parser/gram.y28
-rw-r--r--src/backend/parser/parse_relation.c176
-rw-r--r--src/backend/parser/parser.c5
-rw-r--r--src/backend/utils/adt/ruleutils.c2
13 files changed, 414 insertions, 60 deletions
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index fb5c199c0c4..11c31d8fe92 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -158,6 +158,40 @@ CreateTupleDescCopy(TupleDesc tupdesc)
}
/*
+ * CreateTupleDescCopyExtend
+ * This function creates a new TupleDesc by copying from an existing
+ * TupleDesc, but adding space for more columns. The new tupdesc is
+ * not regarded as the same record type as the old one (and therefore
+ * does not inherit its typeid/typmod, which instead are left as an
+ * anonymous record type).
+ *
+ * The additional column slots are not initialized in any way;
+ * callers must do their own TupleDescInitEntry on each.
+ *
+ * !!! Constraints and defaults are not copied !!!
+ */
+TupleDesc
+CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts)
+{
+ TupleDesc desc;
+ int i;
+ int src_natts = tupdesc->natts;
+
+ Assert(moreatts >= 0);
+
+ desc = CreateTemplateTupleDesc(src_natts + moreatts, tupdesc->tdhasoid);
+
+ for (i = 0; i < src_natts; i++)
+ {
+ memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
+ desc->attrs[i]->attnotnull = false;
+ desc->attrs[i]->atthasdef = false;
+ }
+
+ return desc;
+}
+
+/*
* CreateTupleDescCopyConstr
* This function creates a new TupleDesc by copying from an existing
* TupleDesc (including its constraints and defaults).
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 24325fee6eb..423e02f3541 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -25,7 +25,7 @@
#include "executor/nodeFunctionscan.h"
#include "funcapi.h"
#include "nodes/nodeFuncs.h"
-
+#include "catalog/pg_type.h"
static TupleTableSlot *FunctionNext(FunctionScanState *node);
@@ -42,10 +42,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));
@@ -149,6 +220,16 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
ExecInitScanTupleSlot(estate, &scanstate->ss);
/*
+ * We only need a separate slot for the function result if we are doing
+ * ordinality; otherwise, we fetch function results directly into the
+ * scan slot.
+ */
+ if (node->funcordinality)
+ scanstate->func_slot = ExecInitExtraTupleSlot(estate);
+ else
+ scanstate->func_slot = NULL;
+
+ /*
* initialize child expressions
*/
scanstate->ss.ps.targetlist = (List *)
@@ -159,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);
+
+ /*
+ * 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);
- scanstate->tupdesc = tupdesc;
- ExecAssignScanType(&scanstate->ss, tupdesc);
+ 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.
*/
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bcc6496a952..71e305804db 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -509,6 +509,7 @@ _copyFunctionScan(const FunctionScan *from)
COPY_NODE_FIELD(funccoltypes);
COPY_NODE_FIELD(funccoltypmods);
COPY_NODE_FIELD(funccolcollations);
+ COPY_SCALAR_FIELD(funcordinality);
return newnode;
}
@@ -1983,6 +1984,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
COPY_NODE_FIELD(funccoltypes);
COPY_NODE_FIELD(funccoltypmods);
COPY_NODE_FIELD(funccolcollations);
+ COPY_SCALAR_FIELD(funcordinality);
COPY_NODE_FIELD(values_lists);
COPY_NODE_FIELD(values_collations);
COPY_STRING_FIELD(ctename);
@@ -2296,6 +2298,7 @@ _copyRangeFunction(const RangeFunction *from)
{
RangeFunction *newnode = makeNode(RangeFunction);
+ COPY_SCALAR_FIELD(ordinality);
COPY_SCALAR_FIELD(lateral);
COPY_NODE_FIELD(funccallnode);
COPY_NODE_FIELD(alias);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7f9737ee8e8..3183ccff4d3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2126,6 +2126,7 @@ _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
static bool
_equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
{
+ COMPARE_SCALAR_FIELD(ordinality);
COMPARE_SCALAR_FIELD(lateral);
COMPARE_NODE_FIELD(funccallnode);
COMPARE_NODE_FIELD(alias);
@@ -2234,6 +2235,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
COMPARE_NODE_FIELD(funccoltypes);
COMPARE_NODE_FIELD(funccoltypmods);
COMPARE_NODE_FIELD(funccolcollations);
+ COMPARE_SCALAR_FIELD(funcordinality);
COMPARE_NODE_FIELD(values_lists);
COMPARE_NODE_FIELD(values_collations);
COMPARE_STRING_FIELD(ctename);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 0f8a282ec81..b742ec95324 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -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
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 48cd9dcb8d4..bad22399bf8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -521,6 +521,7 @@ _outFunctionScan(StringInfo str, const FunctionScan *node)
WRITE_NODE_FIELD(funccoltypes);
WRITE_NODE_FIELD(funccoltypmods);
WRITE_NODE_FIELD(funccolcollations);
+ WRITE_BOOL_FIELD(funcordinality);
}
static void
@@ -2382,6 +2383,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
WRITE_NODE_FIELD(funccoltypes);
WRITE_NODE_FIELD(funccoltypmods);
WRITE_NODE_FIELD(funccolcollations);
+ WRITE_BOOL_FIELD(funcordinality);
break;
case RTE_VALUES:
WRITE_NODE_FIELD(values_lists);
@@ -2614,6 +2616,7 @@ _outRangeFunction(StringInfo str, const RangeFunction *node)
{
WRITE_NODE_TYPE("RANGEFUNCTION");
+ WRITE_BOOL_FIELD(ordinality);
WRITE_BOOL_FIELD(lateral);
WRITE_NODE_FIELD(funccallnode);
WRITE_NODE_FIELD(alias);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index dc9cb3ebd2c..aad63e58cfd 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1223,6 +1223,7 @@ _readRangeTblEntry(void)
READ_NODE_FIELD(funccoltypes);
READ_NODE_FIELD(funccoltypmods);
READ_NODE_FIELD(funccolcollations);
+ READ_BOOL_FIELD(funcordinality);
break;
case RTE_VALUES:
READ_NODE_FIELD(values_lists);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 7fed5e97945..617cb19a043 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -115,8 +115,8 @@ static BitmapHeapScan *make_bitmap_heapscan(List *qptlist,
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
List *tidquals);
static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
- Index scanrelid, Node *funcexpr, List *funccolnames,
- List *funccoltypes, List *funccoltypmods,
+ Index scanrelid, Node *funcexpr, bool ordinality,
+ List *funccolnames, List *funccoltypes, List *funccoltypmods,
List *funccolcollations);
static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
Index scanrelid, List *values_lists);
@@ -1733,6 +1733,7 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
funcexpr,
+ rte->funcordinality,
rte->eref->colnames,
rte->funccoltypes,
rte->funccoltypmods,
@@ -3366,6 +3367,7 @@ make_functionscan(List *qptlist,
List *qpqual,
Index scanrelid,
Node *funcexpr,
+ bool ordinality,
List *funccolnames,
List *funccoltypes,
List *funccoltypmods,
@@ -3381,6 +3383,7 @@ make_functionscan(List *qptlist,
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
node->funcexpr = funcexpr;
+ node->funcordinality = ordinality;
node->funccolnames = funccolnames;
node->funccoltypes = funccoltypes;
node->funccoltypmods = funccoltypmods;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 506e9d49fc3..76c032c5698 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -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;
/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d8d2bdf09a4..22e82ba146b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -566,7 +566,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
NULLS_P NUMERIC
OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
- ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
+ ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
@@ -609,8 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* list and so can never be entered directly. The filter in parser.c
* creates these tokens when required.
*/
-%token NULLS_FIRST NULLS_LAST WITH_TIME
-
+%token NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME
/* Precedence: lowest to highest */
%nonassoc SET /* see relation_expr_opt_alias */
@@ -9588,20 +9587,42 @@ table_ref: relation_expr opt_alias_clause
{
RangeFunction *n = makeNode(RangeFunction);
n->lateral = false;
+ n->ordinality = false;
n->funccallnode = $1;
n->alias = linitial($2);
n->coldeflist = lsecond($2);
$$ = (Node *) n;
}
+ | func_table WITH_ORDINALITY func_alias_clause
+ {
+ RangeFunction *n = makeNode(RangeFunction);
+ n->lateral = false;
+ n->ordinality = true;
+ n->funccallnode = $1;
+ n->alias = linitial($3);
+ n->coldeflist = lsecond($3);
+ $$ = (Node *) n;
+ }
| LATERAL_P func_table func_alias_clause
{
RangeFunction *n = makeNode(RangeFunction);
n->lateral = true;
+ n->ordinality = false;
n->funccallnode = $2;
n->alias = linitial($3);
n->coldeflist = lsecond($3);
$$ = (Node *) n;
}
+ | LATERAL_P func_table WITH_ORDINALITY func_alias_clause
+ {
+ RangeFunction *n = makeNode(RangeFunction);
+ n->lateral = true;
+ n->ordinality = true;
+ n->funccallnode = $2;
+ n->alias = linitial($4);
+ n->coldeflist = lsecond($4);
+ $$ = (Node *) n;
+ }
| select_with_parens opt_alias_clause
{
RangeSubselect *n = makeNode(RangeSubselect);
@@ -12575,6 +12596,7 @@ unreserved_keyword:
| OPERATOR
| OPTION
| OPTIONS
+ | ORDINALITY
| OVER
| OWNED
| OWNER
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index b2b88fc6a97..39922d32c5c 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -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
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index b8ec7904666..541d3643a10 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -133,7 +133,7 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
case WITH:
/*
- * WITH TIME must be reduced to one token
+ * WITH TIME and WITH ORDINALITY must each be reduced to one token
*/
cur_yylval = lvalp->core_yystype;
cur_yylloc = *llocp;
@@ -143,6 +143,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
case TIME:
cur_token = WITH_TIME;
break;
+ case ORDINALITY:
+ cur_token = WITH_ORDINALITY;
+ break;
default:
/* save the lookahead token for next time */
yyextra->lookahead_token = next_token;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index e6a20e3821c..2b005d6e973 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -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 */