diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/access/common/tupdesc.c | 34 | ||||
-rw-r--r-- | src/backend/executor/nodeFunctionscan.c | 176 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 3 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 2 | ||||
-rw-r--r-- | src/backend/nodes/makefuncs.c | 32 | ||||
-rw-r--r-- | src/backend/nodes/outfuncs.c | 3 | ||||
-rw-r--r-- | src/backend/nodes/readfuncs.c | 1 | ||||
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 7 | ||||
-rw-r--r-- | src/backend/optimizer/util/clauses.c | 5 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 28 | ||||
-rw-r--r-- | src/backend/parser/parse_relation.c | 176 | ||||
-rw-r--r-- | src/backend/parser/parser.c | 5 | ||||
-rw-r--r-- | src/backend/utils/adt/ruleutils.c | 2 |
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 */ |