*** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** *** 13278,13284 **** select $1[i][j] generate_subscripts($1,2) g2(j); $$ LANGUAGE sql IMMUTABLE; CREATE FUNCTION ! postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]); unnest2 --------- 1 --- 13278,13284 ---- generate_subscripts($1,2) g2(j); $$ LANGUAGE sql IMMUTABLE; CREATE FUNCTION ! SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]); unnest2 --------- 1 *************** *** 13289,13294 **** postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]); --- 13289,13336 ---- + + ordinality + + + + When a function in the FROM clause is suffixed by + WITH ORDINALITY, a bigint column is appended to + the output which starts from 1 and increments by 1 for each row of + the function's output. This is more useful in the case of + set-returning functions than of others. This functionality is + available for functions returning composite types or using + OUT parameters, but not when using a function + returning RECORD with an explicit column definition list. + + + -- SRF WITH ORDINALITY + SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n); + ls | n + -----------------+---- + pg_serial | 1 + pg_twophase | 2 + postmaster.opts | 3 + pg_notify | 4 + postgresql.conf | 5 + pg_tblspc | 6 + logfile | 7 + base | 8 + postmaster.pid | 9 + pg_ident.conf | 10 + global | 11 + pg_clog | 12 + pg_snapshots | 13 + pg_multixact | 14 + PG_VERSION | 15 + pg_xlog | 16 + pg_hba.conf | 17 + pg_stat_tmp | 18 + pg_subtrans | 19 + (19 rows) + + + *** a/doc/src/sgml/ref/select.sgml --- b/doc/src/sgml/ref/select.sgml *************** *** 52,58 **** SELECT [ ALL | DISTINCT [ ON ( expressiontable_name [ * ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ] [ LATERAL ] ( select ) [ AS ] alias [ ( column_alias [, ...] ) ] with_query_name [ [ AS ] alias [ ( column_alias [, ...] ) ] ] ! [ LATERAL ] function_name ( [ argument [, ...] ] ) [ AS ] alias [ ( column_alias [, ...] | column_definition [, ...] ) ] [ LATERAL ] function_name ( [ argument [, ...] ] ) AS ( column_definition [, ...] ) from_item [ NATURAL ] join_type from_item [ ON join_condition | USING ( join_column [, ...] ) ] --- 52,59 ---- [ ONLY ] table_name [ * ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ] [ LATERAL ] ( select ) [ AS ] alias [ ( column_alias [, ...] ) ] with_query_name [ [ AS ] alias [ ( column_alias [, ...] ) ] ] ! [ LATERAL ] function_name ( [ argument [, ...] ] ) [ WITH ORDINALITY ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ] ! [ LATERAL ] function_name ( [ argument [, ...] ] ) [ AS ] alias ( column_definition [, ...] ) [ LATERAL ] function_name ( [ argument [, ...] ] ) AS ( column_definition [, ...] ) from_item [ NATURAL ] join_type from_item [ ON join_condition | USING ( join_column [, ...] ) ] *************** *** 368,385 **** TABLE [ ONLY ] table_name [ * ] clause. (This is especially useful for functions that return result sets, but any function can be used.) This acts as though its output were created as a temporary table for the ! duration of this single SELECT command. An ! alias can also be used. If an alias is written, a column alias ! list can also be written to provide substitute names for one ! or more attributes of the function's composite return type. If ! the function has been defined as returning the record ! data type, then an alias or the key word AS must ! be present, followed by a column definition list in the form ! ( column_name data_type , ... ! ). The column definition list must match the actual ! number and types of columns returned by the function. --- 369,407 ---- clause. (This is especially useful for functions that return result sets, but any function can be used.) This acts as though its output were created as a temporary table for the ! duration of this single SELECT command. ! When the optional WITH ORDINALITY is ! appended to the function call, a new column is appended after ! all the function call's columns with numbering for each row. ! For example: ! ! SELECT * FROM unnest(ARRAY['a','b','c','d','e','f']) WITH ORDINALITY; ! unnest | ?column? ! --------+---------- ! a | 1 ! b | 2 ! c | 3 ! d | 4 ! e | 5 ! f | 6 ! (6 rows) ! ! An alias can also be used. If an alias is written, a column ! alias list can also be written to provide substitute names for ! one or more attributes of the function's composite return ! type, including the column added by ORDINALITY if present. ! ! ! ! If the function has been defined as returning the ! record data type, then an alias or the key word ! AS must be present, followed by a column ! definition list in the form ( column_name data_type , ... ! ). The column definition list must match the ! actual number and types of columns returned by the function. ! ORDINALITY does not work in this case. *** a/src/backend/access/common/tupdesc.c --- b/src/backend/access/common/tupdesc.c *************** *** 158,163 **** CreateTupleDescCopy(TupleDesc tupdesc) --- 158,197 ---- } /* + * CreateTupleDescCopyExtend + * This function creates a new TupleDesc by copying from an existing + * TupleDesc, but adding space for more columns. The new tupdesc is + * not regarded as the same record type as the old one (and therefore + * does not inherit its typeid/typmod, which instead are left as an + * anonymous record type). + * + * The additional column slots are not initialized in any way; + * callers must do their own TupleDescInitEntry on each. + * + * !!! Constraints and defaults are not copied !!! + */ + TupleDesc + CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts) + { + TupleDesc desc; + int i; + int src_natts = tupdesc->natts; + + Assert(moreatts >= 0); + + desc = CreateTemplateTupleDesc(src_natts + moreatts, tupdesc->tdhasoid); + + for (i = 0; i < src_natts; i++) + { + memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_FIXED_PART_SIZE); + desc->attrs[i]->attnotnull = false; + desc->attrs[i]->atthasdef = false; + } + + return desc; + } + + /* * CreateTupleDescCopyConstr * This function creates a new TupleDesc by copying from an existing * TupleDesc (including its constraints and defaults). *** a/src/backend/executor/nodeFunctionscan.c --- b/src/backend/executor/nodeFunctionscan.c *************** *** 25,31 **** #include "executor/nodeFunctionscan.h" #include "funcapi.h" #include "nodes/nodeFuncs.h" ! static TupleTableSlot *FunctionNext(FunctionScanState *node); --- 25,31 ---- #include "executor/nodeFunctionscan.h" #include "funcapi.h" #include "nodes/nodeFuncs.h" ! #include "catalog/pg_type.h" static TupleTableSlot *FunctionNext(FunctionScanState *node); *************** *** 42,51 **** static TupleTableSlot *FunctionNext(FunctionScanState *node); static TupleTableSlot * FunctionNext(FunctionScanState *node) { - TupleTableSlot *slot; EState *estate; ScanDirection direction; Tuplestorestate *tuplestorestate; /* * get information from the estate and scan state --- 42,78 ---- static TupleTableSlot * FunctionNext(FunctionScanState *node) { EState *estate; ScanDirection direction; Tuplestorestate *tuplestorestate; + TupleTableSlot *scanslot; + TupleTableSlot *funcslot; + + if (node->func_slot) + { + /* + * ORDINALITY case: + * + * We fetch the function result into FUNCSLOT (which matches the + * function return type), and then copy the values to SCANSLOT + * (which matches the scan result type), setting the ordinal + * column in the process. + */ + + funcslot = node->func_slot; + scanslot = node->ss.ss_ScanTupleSlot; + } + else + { + /* + * non-ORDINALITY case: the function return type and scan result + * type are the same, so we fetch the function result straight + * into the scan result slot. + */ + + funcslot = node->ss.ss_ScanTupleSlot; + scanslot = NULL; + } /* * get information from the estate and scan state *************** *** 64,82 **** FunctionNext(FunctionScanState *node) node->tuplestorestate = tuplestorestate = ExecMakeTableFunctionResult(node->funcexpr, node->ss.ps.ps_ExprContext, ! node->tupdesc, node->eflags & EXEC_FLAG_BACKWARD); } /* * Get the next tuple from tuplestore. Return NULL if no more tuples. */ - slot = node->ss.ss_ScanTupleSlot; (void) tuplestore_gettupleslot(tuplestorestate, ScanDirectionIsForward(direction), false, ! slot); ! return slot; } /* --- 91,152 ---- node->tuplestorestate = tuplestorestate = ExecMakeTableFunctionResult(node->funcexpr, node->ss.ps.ps_ExprContext, ! node->func_tupdesc, node->eflags & EXEC_FLAG_BACKWARD); } /* * Get the next tuple from tuplestore. Return NULL if no more tuples. */ (void) tuplestore_gettupleslot(tuplestorestate, ScanDirectionIsForward(direction), false, ! funcslot); ! ! if (!scanslot) ! return funcslot; ! ! /* ! * we're doing ordinality, so we copy the values from the function return ! * slot to the (distinct) scan slot. We can do this because the lifetimes ! * of the values in each slot are the same; until we reset the scan or ! * fetch the next tuple, both will be valid. ! */ ! ! ExecClearTuple(scanslot); ! ! /* ! * increment or decrement before checking for end-of-data, so that we can ! * move off either end of the result by 1 (and no more than 1) without ! * losing correct count. See PortalRunSelect for why we assume that we ! * won't be called repeatedly in the end-of-data state. ! */ ! ! if (ScanDirectionIsForward(direction)) ! node->ordinal++; ! else ! node->ordinal--; ! ! if (!TupIsNull(funcslot)) ! { ! int natts = funcslot->tts_tupleDescriptor->natts; ! int i; ! ! slot_getallattrs(funcslot); ! ! for (i = 0; i < natts; ++i) ! { ! scanslot->tts_values[i] = funcslot->tts_values[i]; ! scanslot->tts_isnull[i] = funcslot->tts_isnull[i]; ! } ! ! scanslot->tts_values[natts] = Int64GetDatumFast(node->ordinal); ! scanslot->tts_isnull[natts] = false; ! ! ExecStoreVirtualTuple(scanslot); ! } ! ! return scanslot; } /* *************** *** 116,122 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) FunctionScanState *scanstate; Oid funcrettype; TypeFuncClass functypclass; ! TupleDesc tupdesc = NULL; /* check for unsupported flags */ Assert(!(eflags & EXEC_FLAG_MARK)); --- 186,193 ---- FunctionScanState *scanstate; Oid funcrettype; TypeFuncClass functypclass; ! TupleDesc func_tupdesc = NULL; ! TupleDesc scan_tupdesc = NULL; /* check for unsupported flags */ Assert(!(eflags & EXEC_FLAG_MARK)); *************** *** 149,154 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) --- 220,235 ---- ExecInitScanTupleSlot(estate, &scanstate->ss); /* + * We only need a separate slot for the function result if we are doing + * ordinality; otherwise, we fetch function results directly into the + * scan slot. + */ + if (node->funcordinality) + scanstate->func_slot = ExecInitExtraTupleSlot(estate); + else + scanstate->func_slot = NULL; + + /* * initialize child expressions */ scanstate->ss.ps.targetlist = (List *) *************** *** 159,200 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) (PlanState *) scanstate); /* ! * Now determine if the function returns a simple or composite type, and ! * build an appropriate tupdesc. */ functypclass = get_expr_result_type(node->funcexpr, &funcrettype, ! &tupdesc); if (functypclass == TYPEFUNC_COMPOSITE) { /* Composite data type, e.g. a table's row type */ ! Assert(tupdesc); /* Must copy it out of typcache for safety */ ! tupdesc = CreateTupleDescCopy(tupdesc); } else if (functypclass == TYPEFUNC_SCALAR) { /* Base data type, i.e. scalar */ char *attname = strVal(linitial(node->funccolnames)); ! tupdesc = CreateTemplateTupleDesc(1, false); ! TupleDescInitEntry(tupdesc, (AttrNumber) 1, attname, funcrettype, -1, 0); ! TupleDescInitEntryCollation(tupdesc, (AttrNumber) 1, exprCollation(node->funcexpr)); } else if (functypclass == TYPEFUNC_RECORD) { ! tupdesc = BuildDescFromLists(node->funccolnames, ! node->funccoltypes, ! node->funccoltypmods, ! node->funccolcollations); } else { --- 240,294 ---- (PlanState *) scanstate); /* ! * Now determine if the function returns a simple or composite ! * type, and build an appropriate tupdesc. This tupdesc ! * (func_tupdesc) is the one that matches the shape of the ! * function result, no extra columns. */ functypclass = get_expr_result_type(node->funcexpr, &funcrettype, ! &func_tupdesc); if (functypclass == TYPEFUNC_COMPOSITE) { /* Composite data type, e.g. a table's row type */ ! Assert(func_tupdesc); ! ! /* ! * XXX ! * Existing behaviour is a bit inconsistent with regard to aliases and ! * whole-row Vars of the function result. If the function returns a ! * composite type, then the whole-row Var will refer to this tupdesc, ! * which has the type's own column names rather than the alias column ! * names given in the query. This affects the output of constructs like ! * row_to_json which read the column names from the passed-in values. ! */ ! /* Must copy it out of typcache for safety */ ! func_tupdesc = CreateTupleDescCopy(func_tupdesc); } else if (functypclass == TYPEFUNC_SCALAR) { /* Base data type, i.e. scalar */ char *attname = strVal(linitial(node->funccolnames)); ! func_tupdesc = CreateTemplateTupleDesc(1, false); ! TupleDescInitEntry(func_tupdesc, (AttrNumber) 1, attname, funcrettype, -1, 0); ! TupleDescInitEntryCollation(func_tupdesc, (AttrNumber) 1, exprCollation(node->funcexpr)); } else if (functypclass == TYPEFUNC_RECORD) { ! func_tupdesc = BuildDescFromLists(node->funccolnames, ! node->funccoltypes, ! node->funccoltypmods, ! node->funccolcollations); } else { *************** *** 207,221 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) * function should do this for itself, but let's cover things in case it * doesn't.) */ ! BlessTupleDesc(tupdesc); ! scanstate->tupdesc = tupdesc; ! ExecAssignScanType(&scanstate->ss, tupdesc); /* * Other node-specific setup */ scanstate->tuplestorestate = NULL; scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr, (PlanState *) scanstate); --- 301,347 ---- * function should do this for itself, but let's cover things in case it * doesn't.) */ ! BlessTupleDesc(func_tupdesc); ! /* ! * If doing ordinality, we need a new tupdesc with one additional column ! * tacked on, always of type "bigint". The name to use has already been ! * recorded by the parser as the last element of funccolnames. ! * ! * Without ordinality, the scan result tupdesc is the same as the ! * function result tupdesc. (No need to make a copy.) ! */ ! if (node->funcordinality) ! { ! int natts = func_tupdesc->natts; ! ! scan_tupdesc = CreateTupleDescCopyExtend(func_tupdesc, 1); ! ! TupleDescInitEntry(scan_tupdesc, ! natts + 1, ! strVal(llast(node->funccolnames)), ! INT8OID, ! -1, ! 0); ! ! BlessTupleDesc(scan_tupdesc); ! } ! else ! scan_tupdesc = func_tupdesc; ! ! scanstate->scan_tupdesc = scan_tupdesc; ! scanstate->func_tupdesc = func_tupdesc; ! ExecAssignScanType(&scanstate->ss, scan_tupdesc); ! ! if (scanstate->func_slot) ! ExecSetSlotDescriptor(scanstate->func_slot, func_tupdesc); /* * Other node-specific setup */ + scanstate->ordinal = 0; scanstate->tuplestorestate = NULL; + scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr, (PlanState *) scanstate); *************** *** 249,254 **** ExecEndFunctionScan(FunctionScanState *node) --- 375,382 ---- */ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); ExecClearTuple(node->ss.ss_ScanTupleSlot); + if (node->func_slot) + ExecClearTuple(node->func_slot); /* * Release tuplestore resources *************** *** 268,276 **** void --- 396,408 ---- ExecReScanFunctionScan(FunctionScanState *node) { ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); + if (node->func_slot) + ExecClearTuple(node->func_slot); ExecScanReScan(&node->ss); + node->ordinal = 0; + /* * If we haven't materialized yet, just return. */ *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 509,514 **** _copyFunctionScan(const FunctionScan *from) --- 509,515 ---- COPY_NODE_FIELD(funccoltypes); COPY_NODE_FIELD(funccoltypmods); COPY_NODE_FIELD(funccolcollations); + COPY_SCALAR_FIELD(funcordinality); return newnode; } *************** *** 1983,1988 **** _copyRangeTblEntry(const RangeTblEntry *from) --- 1984,1990 ---- COPY_NODE_FIELD(funccoltypes); COPY_NODE_FIELD(funccoltypmods); COPY_NODE_FIELD(funccolcollations); + COPY_SCALAR_FIELD(funcordinality); COPY_NODE_FIELD(values_lists); COPY_NODE_FIELD(values_collations); COPY_STRING_FIELD(ctename); *************** *** 2296,2301 **** _copyRangeFunction(const RangeFunction *from) --- 2298,2304 ---- { RangeFunction *newnode = makeNode(RangeFunction); + COPY_SCALAR_FIELD(ordinality); COPY_SCALAR_FIELD(lateral); COPY_NODE_FIELD(funccallnode); COPY_NODE_FIELD(alias); *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** *** 2126,2131 **** _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b) --- 2126,2132 ---- static bool _equalRangeFunction(const RangeFunction *a, const RangeFunction *b) { + COMPARE_SCALAR_FIELD(ordinality); COMPARE_SCALAR_FIELD(lateral); COMPARE_NODE_FIELD(funccallnode); COMPARE_NODE_FIELD(alias); *************** *** 2234,2239 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b) --- 2235,2241 ---- COMPARE_NODE_FIELD(funccoltypes); COMPARE_NODE_FIELD(funccoltypmods); COMPARE_NODE_FIELD(funccolcollations); + COMPARE_SCALAR_FIELD(funcordinality); COMPARE_NODE_FIELD(values_lists); COMPARE_NODE_FIELD(values_collations); COMPARE_STRING_FIELD(ctename); *** a/src/backend/nodes/makefuncs.c --- b/src/backend/nodes/makefuncs.c *************** *** 126,131 **** makeVarFromTargetEntry(Index varno, --- 126,135 ---- * returning a non-composite result type, we produce a normal Var referencing * the function's result directly, instead of the single-column composite * value that the whole-row notation might otherwise suggest. + * + * We also handle the specific case of function RTEs with ordinality, + * where the additional column has to be added. This forces the result + * to be composite and RECORD type. */ Var * makeWholeRowVar(RangeTblEntry *rte, *************** *** 151,159 **** makeWholeRowVar(RangeTblEntry *rte, InvalidOid, varlevelsup); break; case RTE_FUNCTION: toid = exprType(rte->funcexpr); ! if (type_is_rowtype(toid)) { /* func returns composite; same as relation case */ result = makeVar(varno, --- 155,187 ---- InvalidOid, varlevelsup); break; + case RTE_FUNCTION: + /* + * RTE is a function with or without ordinality. We map the + * cases as follows: + * + * If ordinality is set, we return a composite var even if + * the function is a scalar. This var is always of RECORD type. + * + * If ordinality is not set but the function returns a row, + * we keep the function's return type. + * + * If the function is a scalar, we do what allowScalar requests. + */ toid = exprType(rte->funcexpr); ! ! if (rte->funcordinality) ! { ! /* ORDINALITY always produces an anonymous RECORD result */ ! result = makeVar(varno, ! InvalidAttrNumber, ! RECORDOID, ! -1, ! InvalidOid, ! varlevelsup); ! } ! else if (type_is_rowtype(toid)) { /* func returns composite; same as relation case */ result = makeVar(varno, *************** *** 184,191 **** makeWholeRowVar(RangeTblEntry *rte, varlevelsup); } break; default: - /* * RTE is a join, subselect, or VALUES. We represent this as a * whole-row Var of RECORD type. (Note that in most cases the Var --- 212,219 ---- varlevelsup); } break; + default: /* * RTE is a join, subselect, or VALUES. We represent this as a * whole-row Var of RECORD type. (Note that in most cases the Var *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** *** 521,526 **** _outFunctionScan(StringInfo str, const FunctionScan *node) --- 521,527 ---- WRITE_NODE_FIELD(funccoltypes); WRITE_NODE_FIELD(funccoltypmods); WRITE_NODE_FIELD(funccolcollations); + WRITE_BOOL_FIELD(funcordinality); } static void *************** *** 2382,2387 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) --- 2383,2389 ---- WRITE_NODE_FIELD(funccoltypes); WRITE_NODE_FIELD(funccoltypmods); WRITE_NODE_FIELD(funccolcollations); + WRITE_BOOL_FIELD(funcordinality); break; case RTE_VALUES: WRITE_NODE_FIELD(values_lists); *************** *** 2614,2619 **** _outRangeFunction(StringInfo str, const RangeFunction *node) --- 2616,2622 ---- { WRITE_NODE_TYPE("RANGEFUNCTION"); + WRITE_BOOL_FIELD(ordinality); WRITE_BOOL_FIELD(lateral); WRITE_NODE_FIELD(funccallnode); WRITE_NODE_FIELD(alias); *** a/src/backend/nodes/readfuncs.c --- b/src/backend/nodes/readfuncs.c *************** *** 1223,1228 **** _readRangeTblEntry(void) --- 1223,1229 ---- READ_NODE_FIELD(funccoltypes); READ_NODE_FIELD(funccoltypmods); READ_NODE_FIELD(funccolcollations); + READ_BOOL_FIELD(funcordinality); break; case RTE_VALUES: READ_NODE_FIELD(values_lists); *** a/src/backend/optimizer/plan/createplan.c --- b/src/backend/optimizer/plan/createplan.c *************** *** 115,122 **** static BitmapHeapScan *make_bitmap_heapscan(List *qptlist, static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid, List *tidquals); static FunctionScan *make_functionscan(List *qptlist, List *qpqual, ! Index scanrelid, Node *funcexpr, List *funccolnames, ! List *funccoltypes, List *funccoltypmods, List *funccolcollations); static ValuesScan *make_valuesscan(List *qptlist, List *qpqual, Index scanrelid, List *values_lists); --- 115,122 ---- static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid, List *tidquals); static FunctionScan *make_functionscan(List *qptlist, List *qpqual, ! Index scanrelid, Node *funcexpr, bool ordinality, ! List *funccolnames, List *funccoltypes, List *funccoltypmods, List *funccolcollations); static ValuesScan *make_valuesscan(List *qptlist, List *qpqual, Index scanrelid, List *values_lists); *************** *** 1733,1738 **** create_functionscan_plan(PlannerInfo *root, Path *best_path, --- 1733,1739 ---- scan_plan = make_functionscan(tlist, scan_clauses, scan_relid, funcexpr, + rte->funcordinality, rte->eref->colnames, rte->funccoltypes, rte->funccoltypmods, *************** *** 3366,3371 **** make_functionscan(List *qptlist, --- 3367,3373 ---- List *qpqual, Index scanrelid, Node *funcexpr, + bool ordinality, List *funccolnames, List *funccoltypes, List *funccoltypmods, *************** *** 3381,3386 **** make_functionscan(List *qptlist, --- 3383,3389 ---- plan->righttree = NULL; node->scan.scanrelid = scanrelid; node->funcexpr = funcexpr; + node->funcordinality = ordinality; node->funccolnames = funccolnames; node->funccoltypes = funccoltypes; node->funccoltypmods = funccoltypmods; *** a/src/backend/optimizer/util/clauses.c --- b/src/backend/optimizer/util/clauses.c *************** *** 4452,4461 **** inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) --- 4452,4466 ---- */ check_stack_depth(); + /* Fail if the caller wanted ORDINALITY - we don't implement that here. */ + if (rte->funcordinality) + return NULL; + /* Fail if FROM item isn't a simple FuncExpr */ fexpr = (FuncExpr *) rte->funcexpr; if (fexpr == NULL || !IsA(fexpr, FuncExpr)) return NULL; + func_oid = fexpr->funcid; /* *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 566,572 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); NULLS_P NUMERIC OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR ! ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY --- 566,572 ---- NULLS_P NUMERIC OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR ! ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY *************** *** 609,616 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * list and so can never be entered directly. The filter in parser.c * creates these tokens when required. */ ! %token NULLS_FIRST NULLS_LAST WITH_TIME ! /* Precedence: lowest to highest */ %nonassoc SET /* see relation_expr_opt_alias */ --- 609,615 ---- * list and so can never be entered directly. The filter in parser.c * creates these tokens when required. */ ! %token NULLS_FIRST NULLS_LAST WITH_ORDINALITY WITH_TIME /* Precedence: lowest to highest */ %nonassoc SET /* see relation_expr_opt_alias */ *************** *** 9588,9607 **** table_ref: relation_expr opt_alias_clause --- 9587,9628 ---- { RangeFunction *n = makeNode(RangeFunction); n->lateral = false; + n->ordinality = false; n->funccallnode = $1; n->alias = linitial($2); n->coldeflist = lsecond($2); $$ = (Node *) n; } + | func_table WITH_ORDINALITY func_alias_clause + { + RangeFunction *n = makeNode(RangeFunction); + n->lateral = false; + n->ordinality = true; + n->funccallnode = $1; + n->alias = linitial($3); + n->coldeflist = lsecond($3); + $$ = (Node *) n; + } | LATERAL_P func_table func_alias_clause { RangeFunction *n = makeNode(RangeFunction); n->lateral = true; + n->ordinality = false; n->funccallnode = $2; n->alias = linitial($3); n->coldeflist = lsecond($3); $$ = (Node *) n; } + | LATERAL_P func_table WITH_ORDINALITY func_alias_clause + { + RangeFunction *n = makeNode(RangeFunction); + n->lateral = true; + n->ordinality = true; + n->funccallnode = $2; + n->alias = linitial($4); + n->coldeflist = lsecond($4); + $$ = (Node *) n; + } | select_with_parens opt_alias_clause { RangeSubselect *n = makeNode(RangeSubselect); *************** *** 12575,12580 **** unreserved_keyword: --- 12596,12602 ---- | OPERATOR | OPTION | OPTIONS + | ORDINALITY | OVER | OWNED | OWNER *** a/src/backend/parser/parse_relation.c --- b/src/backend/parser/parse_relation.c *************** *** 787,804 **** markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte) * buildRelationAliases * Construct the eref column name list for a relation RTE. * This code is also used for the case of a function RTE returning ! * a named composite type. * * tupdesc: the physical column information * alias: the user-supplied alias, or NULL if none * eref: the eref Alias to store column names in * * eref->colnames is filled in. Also, alias->colnames is rebuilt to insert * empty strings for any dropped columns, so that it will be one-to-one with * physical column numbers. */ static void ! buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref) { int maxattrs = tupdesc->natts; ListCell *aliaslc; --- 787,810 ---- * buildRelationAliases * Construct the eref column name list for a relation RTE. * This code is also used for the case of a function RTE returning ! * a named composite type or a registered RECORD type. * * tupdesc: the physical column information * alias: the user-supplied alias, or NULL if none * eref: the eref Alias to store column names in + * ordinality: true if an ordinality column is to be added * * eref->colnames is filled in. Also, alias->colnames is rebuilt to insert * empty strings for any dropped columns, so that it will be one-to-one with * physical column numbers. + * + * If we add an ordinality column, its colname comes from the alias if there + * is one, otherwise we default it. (We don't add it to alias->colnames.) + * + * It is an error for there to be more aliases present than required. */ static void ! buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinality) { int maxattrs = tupdesc->natts; ListCell *aliaslc; *************** *** 850,861 **** buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref) eref->colnames = lappend(eref->colnames, attrname); } /* Too many user-supplied aliases? */ if (aliaslc) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("table \"%s\" has %d columns available but %d columns specified", ! eref->aliasname, maxattrs - numdropped, numaliases))); } /* --- 856,888 ---- eref->colnames = lappend(eref->colnames, attrname); } + /* tack on the ordinality column at the end */ + if (ordinality) + { + Value *attrname; + + if (aliaslc) + { + attrname = (Value *) lfirst(aliaslc); + aliaslc = lnext(aliaslc); + alias->colnames = lappend(alias->colnames, attrname); + } + else + { + attrname = makeString(pstrdup("?column?")); + } + + eref->colnames = lappend(eref->colnames, attrname); + } + /* Too many user-supplied aliases? */ if (aliaslc) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("table \"%s\" has %d columns available but %d columns specified", ! eref->aliasname, ! maxattrs - numdropped + (ordinality ? 1 : 0), ! numaliases))); } /* *************** *** 867,914 **** buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref) * funcname: function name (used only for error message) * alias: the user-supplied alias, or NULL if none * eref: the eref Alias to store column names in * * eref->colnames is filled in. */ static void buildScalarFunctionAlias(Node *funcexpr, char *funcname, ! Alias *alias, Alias *eref) { - char *pname; - Assert(eref->colnames == NIL); /* Use user-specified column alias if there is one. */ if (alias && alias->colnames != NIL) { ! if (list_length(alias->colnames) != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("too many column aliases specified for function %s", funcname))); eref->colnames = copyObject(alias->colnames); - return; } ! ! /* ! * If the expression is a simple function call, and the function has a ! * single OUT parameter that is named, use the parameter's name. ! */ ! if (funcexpr && IsA(funcexpr, FuncExpr)) { ! pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid); ! if (pname) ! { ! eref->colnames = list_make1(makeString(pname)); ! return; ! } } ! /* ! * Otherwise use the previously-determined alias (not necessarily the ! * function name!) ! */ ! eref->colnames = list_make1(makeString(eref->aliasname)); } /* --- 894,953 ---- * funcname: function name (used only for error message) * alias: the user-supplied alias, or NULL if none * eref: the eref Alias to store column names in + * ordinality: whether to add an ordinality column * * eref->colnames is filled in. + * + * The caller must have previously filled in eref->aliasname, which will + * be used as the result column name if no alias is given. + * + * A user-supplied Alias can contain up to two column alias names; one for + * the function result, and one for the ordinality column; it is an error + * to specify more aliases than required. */ static void buildScalarFunctionAlias(Node *funcexpr, char *funcname, ! Alias *alias, Alias *eref, bool ordinality) { Assert(eref->colnames == NIL); /* Use user-specified column alias if there is one. */ if (alias && alias->colnames != NIL) { ! if (list_length(alias->colnames) > (ordinality ? 2 : 1)) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("too many column aliases specified for function %s", funcname))); + eref->colnames = copyObject(alias->colnames); } ! else { ! char *pname = NULL; ! ! /* ! * If the expression is a simple function call, and the function has a ! * single OUT parameter that is named, use the parameter's name. ! */ ! if (funcexpr && IsA(funcexpr, FuncExpr)) ! pname = get_func_result_name(((FuncExpr *) funcexpr)->funcid); ! ! /* ! * Otherwise, use the previously-determined alias name provided by the ! * caller (which is not necessarily the function name!) ! */ ! if (!pname) ! pname = eref->aliasname; ! ! eref->colnames = list_make1(makeString(pname)); } ! /* If we don't have a name for the ordinality column yet, supply a default. */ ! if (ordinality && list_length(eref->colnames) < 2) ! eref->colnames = lappend(eref->colnames, makeString(pstrdup("?column?"))); ! ! return; } /* *************** *** 1004,1010 **** addRangeTableEntry(ParseState *pstate, * and/or actual column names. */ rte->eref = makeAlias(refname, NIL); ! buildRelationAliases(rel->rd_att, alias, rte->eref); /* * Drop the rel refcount, but keep the access lock till end of transaction --- 1043,1049 ---- * and/or actual column names. */ rte->eref = makeAlias(refname, NIL); ! buildRelationAliases(rel->rd_att, alias, rte->eref, false); /* * Drop the rel refcount, but keep the access lock till end of transaction *************** *** 1064,1070 **** addRangeTableEntryForRelation(ParseState *pstate, * and/or actual column names. */ rte->eref = makeAlias(refname, NIL); ! buildRelationAliases(rel->rd_att, alias, rte->eref); /* * Set flags and access permissions. --- 1103,1109 ---- * and/or actual column names. */ rte->eref = makeAlias(refname, NIL); ! buildRelationAliases(rel->rd_att, alias, rte->eref, false); /* * Set flags and access permissions. *************** *** 1235,1251 **** addRangeTableEntryForFunction(ParseState *pstate, /* Composite data type, e.g. a table's row type */ Assert(tupdesc); /* Build the column alias list */ ! buildRelationAliases(tupdesc, alias, eref); } else if (functypclass == TYPEFUNC_SCALAR) { /* Base data type, i.e. scalar */ ! buildScalarFunctionAlias(funcexpr, funcname, alias, eref); } else if (functypclass == TYPEFUNC_RECORD) { ListCell *col; /* * Use the column definition list to form the alias list and * funccoltypes/funccoltypmods/funccolcollations lists. --- 1274,1296 ---- /* Composite data type, e.g. a table's row type */ Assert(tupdesc); /* Build the column alias list */ ! buildRelationAliases(tupdesc, alias, eref, rangefunc->ordinality); } else if (functypclass == TYPEFUNC_SCALAR) { /* Base data type, i.e. scalar */ ! buildScalarFunctionAlias(funcexpr, funcname, alias, eref, rangefunc->ordinality); } else if (functypclass == TYPEFUNC_RECORD) { ListCell *col; + if (rangefunc->ordinality) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WITH ORDINALITY is not supported for functions returning \"record\""), + parser_errposition(pstate, exprLocation(funcexpr)))); + /* * Use the column definition list to form the alias list and * funccoltypes/funccoltypmods/funccolcollations lists. *************** *** 1288,1293 **** addRangeTableEntryForFunction(ParseState *pstate, --- 1333,1339 ---- * permissions mechanism). */ rte->lateral = lateral; + rte->funcordinality = rangefunc->ordinality; rte->inh = false; /* never true for functions */ rte->inFromCl = inFromCl; *************** *** 1643,1648 **** addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte, --- 1689,1699 ---- * The output lists go into *colnames and *colvars. * If only one of the two kinds of output list is needed, pass NULL for the * output pointer for the unwanted one. + * + * For function RTEs with ORDINALITY, this expansion includes the + * ordinal column, whose type (bigint) had better match the type assumed in the + * executor. The colname for the ordinality column must have been set up already + * in the RTE; it is always last. */ void expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, *************** *** 1711,1716 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, --- 1762,1768 ---- TypeFuncClass functypclass; Oid funcrettype; TupleDesc tupdesc; + int ordinality_attno = 0; functypclass = get_expr_result_type(rte->funcexpr, &funcrettype, *************** *** 1719,1727 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, --- 1771,1786 ---- { /* Composite data type, e.g. a table's row type */ Assert(tupdesc); + + /* + * we rely here on the fact that expandTupleDesc doesn't + * care about being passed more aliases than it needs. + */ expandTupleDesc(tupdesc, rte->eref, rtindex, sublevels_up, location, include_dropped, colnames, colvars); + + ordinality_attno = tupdesc->natts + 1; } else if (functypclass == TYPEFUNC_SCALAR) { *************** *** 1742,1747 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, --- 1801,1808 ---- *colvars = lappend(*colvars, varnode); } + + ordinality_attno = 2; } else if (functypclass == TYPEFUNC_RECORD) { *************** *** 1774,1785 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, --- 1835,1868 ---- *colvars = lappend(*colvars, varnode); } } + + /* note, ordinality is not allowed in this case */ } else { /* addRangeTableEntryForFunction should've caught this */ elog(ERROR, "function in FROM has unsupported return type"); } + + /* tack on the extra ordinality column if present */ + if (rte->funcordinality) + { + Assert(ordinality_attno > 0); + + if (colnames) + *colnames = lappend(*colnames, llast(rte->eref->colnames)); + + if (colvars) + { + Var *varnode = makeVar(rtindex, + ordinality_attno, + INT8OID, + -1, + InvalidOid, + sublevels_up); + *colvars = lappend(*colvars, varnode); + } + } } break; case RTE_VALUES: *************** *** 1955,1960 **** expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, --- 2038,2046 ---- /* * expandTupleDesc -- expandRTE subroutine + * + * Only the required number of column names are used from the Alias; + * it is not an error to supply too many. (ordinality depends on this) */ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, *************** *** 2114,2119 **** get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum) --- 2200,2208 ---- /* * get_rte_attribute_type * Get attribute type/typmod/collation information from a RangeTblEntry + * + * Once again, for function RTEs we may have to synthesize the + * ordinality column with the correct type. */ void get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, *************** *** 2172,2177 **** get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, --- 2261,2280 ---- Oid funcrettype; TupleDesc tupdesc; + /* + * if ordinality, then a reference to the last column + * in the name list must be referring to the + * ordinality column + */ + if (rte->funcordinality + && attnum == list_length(rte->eref->colnames)) + { + *vartype = INT8OID; + *vartypmod = -1; + *varcollid = InvalidOid; + break; + } + functypclass = get_expr_result_type(rte->funcexpr, &funcrettype, &tupdesc); *************** *** 2182,2187 **** get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, --- 2285,2291 ---- Form_pg_attribute att_tup; Assert(tupdesc); + /* this is probably a can't-happen case */ if (attnum < 1 || attnum > tupdesc->natts) ereport(ERROR, *************** *** 2208,2213 **** get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, --- 2312,2319 ---- } else if (functypclass == TYPEFUNC_SCALAR) { + Assert(attnum == 1); + /* Base data type, i.e. scalar */ *vartype = funcrettype; *vartypmod = -1; *************** *** 2332,2338 **** get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) Oid funcrettype = exprType(rte->funcexpr); Oid funcrelid = typeidTypeRelid(funcrettype); ! if (OidIsValid(funcrelid)) { /* * Composite data type, i.e. a table's row type --- 2438,2454 ---- Oid funcrettype = exprType(rte->funcexpr); Oid funcrelid = typeidTypeRelid(funcrettype); ! /* ! * if ordinality, then a reference to the last column ! * in the name list must be referring to the ! * ordinality column, which is not dropped ! */ ! if (rte->funcordinality ! && attnum == list_length(rte->eref->colnames)) ! { ! result = false; ! } ! else if (OidIsValid(funcrelid)) { /* * Composite data type, i.e. a table's row type *** a/src/backend/parser/parser.c --- b/src/backend/parser/parser.c *************** *** 133,139 **** base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner) case WITH: /* ! * WITH TIME must be reduced to one token */ cur_yylval = lvalp->core_yystype; cur_yylloc = *llocp; --- 133,139 ---- case WITH: /* ! * WITH TIME and WITH ORDINALITY must each be reduced to one token */ cur_yylval = lvalp->core_yystype; cur_yylloc = *llocp; *************** *** 143,148 **** base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner) --- 143,151 ---- case TIME: cur_token = WITH_TIME; break; + case ORDINALITY: + cur_token = WITH_ORDINALITY; + break; default: /* save the lookahead token for next time */ yyextra->lookahead_token = next_token; *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** *** 8004,8009 **** get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) --- 8004,8011 ---- case RTE_FUNCTION: /* Function RTE */ get_rule_expr(rte->funcexpr, context, true); + if (rte->funcordinality) + appendStringInfoString(buf, " WITH ORDINALITY"); break; case RTE_VALUES: /* Values list RTE */ *** a/src/include/access/tupdesc.h --- b/src/include/access/tupdesc.h *************** *** 87,92 **** extern TupleDesc CreateTupleDesc(int natts, bool hasoid, --- 87,93 ---- Form_pg_attribute *attrs); extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc); + extern TupleDesc CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts); extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc); *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 1395,1401 **** typedef struct SubqueryScanState * function appearing in FROM (typically a function returning set). * * eflags node's capability flags ! * tupdesc expected return tuple description * tuplestorestate private state of tuplestore.c * funcexpr state for function expression being evaluated * ---------------- --- 1395,1404 ---- * function appearing in FROM (typically a function returning set). * * eflags node's capability flags ! * ordinal column value for WITH ORDINALITY ! * scan_tupdesc scan tuple descriptor ! * func_tupdesc function tuple descriptor ! * func_slot function result slot, or null * tuplestorestate private state of tuplestore.c * funcexpr state for function expression being evaluated * ---------------- *************** *** 1404,1410 **** typedef struct FunctionScanState { ScanState ss; /* its first field is NodeTag */ int eflags; ! TupleDesc tupdesc; Tuplestorestate *tuplestorestate; ExprState *funcexpr; } FunctionScanState; --- 1407,1416 ---- { ScanState ss; /* its first field is NodeTag */ int eflags; ! int64 ordinal; ! TupleDesc scan_tupdesc; ! TupleDesc func_tupdesc; ! TupleTableSlot *func_slot; Tuplestorestate *tuplestorestate; ExprState *funcexpr; } FunctionScanState; *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 471,476 **** typedef struct RangeFunction --- 471,477 ---- { NodeTag type; bool lateral; /* does it have LATERAL prefix? */ + bool ordinality; /* does it have WITH ORDINALITY suffix? */ Node *funccallnode; /* untransformed function call tree */ Alias *alias; /* table alias & optional column aliases */ List *coldeflist; /* list of ColumnDef nodes to describe result *************** *** 651,658 **** typedef struct XmlSerialize * dropped columns. Note however that a stored rule may have nonempty * colnames for columns dropped since the rule was created (and for that * matter the colnames might be out of date due to column renamings). * The same comments apply to FUNCTION RTEs when the function's return type ! * is a named composite type. * * In JOIN RTEs, the colnames in both alias and eref are one-to-one with * joinaliasvars entries. A JOIN RTE will omit columns of its inputs when --- 652,664 ---- * dropped columns. Note however that a stored rule may have nonempty * colnames for columns dropped since the rule was created (and for that * matter the colnames might be out of date due to column renamings). + * * The same comments apply to FUNCTION RTEs when the function's return type ! * is a named composite type. In addition, for all return types, FUNCTION ! * RTEs with ORDINALITY must always have the last colname entry being the ! * one for the ordinal column; this is enforced when constructing the RTE. ! * Thus when ORDINALITY is used, there will be exactly one more colname ! * than would have been present otherwise. * * In JOIN RTEs, the colnames in both alias and eref are one-to-one with * joinaliasvars entries. A JOIN RTE will omit columns of its inputs when *************** *** 751,765 **** typedef struct RangeTblEntry /* * Fields valid for a function RTE (else NULL): * ! * If the function returns RECORD, funccoltypes lists the column types ! * declared in the RTE's column type specification, funccoltypmods lists ! * their declared typmods, funccolcollations their collations. Otherwise, ! * those fields are NIL. */ Node *funcexpr; /* expression tree for func call */ List *funccoltypes; /* OID list of column type OIDs */ List *funccoltypmods; /* integer list of column typmods */ List *funccolcollations; /* OID list of column collation OIDs */ /* * Fields valid for a values RTE (else NIL): --- 757,777 ---- /* * Fields valid for a function RTE (else NULL): * ! * If the function returns an otherwise-unspecified RECORD, funccoltypes ! * lists the column types declared in the RTE's column type specification, ! * funccoltypmods lists their declared typmods, funccolcollations their ! * collations. Note that in this case, ORDINALITY is not permitted, so ! * there is no extra ordinal column to be allowed for. ! * ! * Otherwise, those fields are NIL, and the result column types must be ! * derived from the funcexpr while treating the ordinal column, if ! * present, as a special case. (see get_rte_attribute_*) */ Node *funcexpr; /* expression tree for func call */ List *funccoltypes; /* OID list of column type OIDs */ List *funccoltypmods; /* integer list of column typmods */ List *funccolcollations; /* OID list of column collation OIDs */ + bool funcordinality; /* is this called WITH ORDINALITY? */ /* * Fields valid for a values RTE (else NIL): *** a/src/include/nodes/plannodes.h --- b/src/include/nodes/plannodes.h *************** *** 425,430 **** typedef struct FunctionScan --- 425,431 ---- { Scan scan; Node *funcexpr; /* expression tree for func call */ + bool funcordinality; /* WITH ORDINALITY */ List *funccolnames; /* output column names (string Value nodes) */ List *funccoltypes; /* OID list of column type OIDs */ List *funccoltypmods; /* integer list of column typmods */ *** a/src/include/parser/kwlist.h --- b/src/include/parser/kwlist.h *************** *** 269,274 **** PG_KEYWORD("option", OPTION, UNRESERVED_KEYWORD) --- 269,275 ---- PG_KEYWORD("options", OPTIONS, UNRESERVED_KEYWORD) PG_KEYWORD("or", OR, RESERVED_KEYWORD) PG_KEYWORD("order", ORDER, RESERVED_KEYWORD) + PG_KEYWORD("ordinality", ORDINALITY, UNRESERVED_KEYWORD) PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD) PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD) *** a/src/test/regress/expected/rangefuncs.out --- b/src/test/regress/expected/rangefuncs.out *************** *** 18,24 **** CREATE TABLE foo2(fooid int, f2 int); INSERT INTO foo2 VALUES(1, 11); INSERT INTO foo2 VALUES(2, 22); INSERT INTO foo2 VALUES(1, 111); ! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1;' LANGUAGE SQL; -- function with implicit LATERAL select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2; fooid | f2 | fooid | f2 --- 18,148 ---- INSERT INTO foo2 VALUES(1, 11); INSERT INTO foo2 VALUES(2, 22); INSERT INTO foo2 VALUES(1, 111); ! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1 ORDER BY f2;' LANGUAGE SQL; ! -- function with ORDINALITY ! select * from foot(1) with ordinality as z(a,b,ord); ! a | b | ord ! ---+-----+----- ! 1 | 11 | 1 ! 1 | 111 | 2 ! (2 rows) ! ! select * from foot(1) with ordinality as z(a,b,ord) where b > 100; -- ordinal 2, not 1 ! a | b | ord ! ---+-----+----- ! 1 | 111 | 2 ! (1 row) ! ! -- ordinality vs. column names and types ! select a,b,ord from foot(1) with ordinality as z(a,b,ord); ! a | b | ord ! ---+-----+----- ! 1 | 11 | 1 ! 1 | 111 | 2 ! (2 rows) ! ! select a,ord from unnest(array['a','b']) with ordinality as z(a,ord); ! a | ord ! ---+----- ! a | 1 ! b | 2 ! (2 rows) ! ! select * from unnest(array['a','b']) with ordinality as z(a,ord); ! a | ord ! ---+----- ! a | 1 ! b | 2 ! (2 rows) ! ! select a,ord from unnest(array[1.0::float8]) with ordinality as z(a,ord); ! a | ord ! ---+----- ! 1 | 1 ! (1 row) ! ! select * from unnest(array[1.0::float8]) with ordinality as z(a,ord); ! a | ord ! ---+----- ! 1 | 1 ! (1 row) ! ! -- ordinality vs. views ! create temporary view vw_ord as select * from (values (1)) v(n) join foot(1) with ordinality as z(a,b,ord) on (n=ord); ! select * from vw_ord; ! n | a | b | ord ! ---+---+----+----- ! 1 | 1 | 11 | 1 ! (1 row) ! ! select definition from pg_views where viewname='vw_ord'; ! definition ! ------------------------------------------------------------------- ! SELECT v.n, + ! z.a, + ! z.b, + ! z.ord + ! FROM (( VALUES (1)) v(n) + ! JOIN foot(1) WITH ORDINALITY z(a, b, ord) ON ((v.n = z.ord))); ! (1 row) ! ! drop view vw_ord; ! -- ordinality vs. rewind and reverse scan ! begin; ! declare foo scroll cursor for select * from generate_series(1,5) with ordinality as g(i,o); ! fetch all from foo; ! i | o ! ---+--- ! 1 | 1 ! 2 | 2 ! 3 | 3 ! 4 | 4 ! 5 | 5 ! (5 rows) ! ! fetch backward all from foo; ! i | o ! ---+--- ! 5 | 5 ! 4 | 4 ! 3 | 3 ! 2 | 2 ! 1 | 1 ! (5 rows) ! ! fetch all from foo; ! i | o ! ---+--- ! 1 | 1 ! 2 | 2 ! 3 | 3 ! 4 | 4 ! 5 | 5 ! (5 rows) ! ! fetch next from foo; ! i | o ! ---+--- ! (0 rows) ! ! fetch next from foo; ! i | o ! ---+--- ! (0 rows) ! ! fetch prior from foo; ! i | o ! ---+--- ! 5 | 5 ! (1 row) ! ! fetch absolute 1 from foo; ! i | o ! ---+--- ! 1 | 1 ! (1 row) ! ! commit; -- function with implicit LATERAL select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2; fooid | f2 | fooid | f2 *************** *** 28,33 **** select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2; --- 152,166 ---- 1 | 111 | 1 | 111 (3 rows) + -- function with implicit LATERAL and explicit ORDINALITY + select * from foo2, foot(foo2.fooid) with ordinality as z(fooid,f2,ord) where foo2.f2 = z.f2; + fooid | f2 | fooid | f2 | ord + -------+-----+-------+-----+----- + 1 | 11 | 1 | 11 | 1 + 2 | 22 | 2 | 22 | 1 + 1 | 111 | 1 | 111 | 2 + (3 rows) + -- function in subselect select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = foo2.fooid) ORDER BY 1,2; fooid | f2 *************** *** 73,78 **** SELECT * FROM getfoo(1) AS t1; --- 206,217 ---- 1 (1 row) + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); + v | o + ---+--- + 1 | 1 + (1 row) + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; getfoo *************** *** 80,85 **** SELECT * FROM vw_getfoo; --- 219,232 ---- 1 (1 row) + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY as t1(v,o); + SELECT * FROM vw_getfoo; + v | o + ---+--- + 1 | 1 + (1 row) + -- sql, proretset = t, prorettype = b DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); *************** *** 91,96 **** SELECT * FROM getfoo(1) AS t1; --- 238,250 ---- 1 (2 rows) + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); + v | o + ---+--- + 1 | 1 + 1 | 2 + (2 rows) + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; getfoo *************** *** 99,104 **** SELECT * FROM vw_getfoo; --- 253,267 ---- 1 (2 rows) + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); + SELECT * FROM vw_getfoo; + v | o + ---+--- + 1 | 1 + 1 | 2 + (2 rows) + -- sql, proretset = t, prorettype = b DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); *************** *** 110,115 **** SELECT * FROM getfoo(1) AS t1; --- 273,285 ---- Ed (2 rows) + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); + v | o + -----+--- + Joe | 1 + Ed | 2 + (2 rows) + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; getfoo *************** *** 118,123 **** SELECT * FROM vw_getfoo; --- 288,302 ---- Ed (2 rows) + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); + SELECT * FROM vw_getfoo; + v | o + -----+--- + Joe | 1 + Ed | 2 + (2 rows) + -- sql, proretset = f, prorettype = c DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); *************** *** 128,133 **** SELECT * FROM getfoo(1) AS t1; --- 307,318 ---- 1 | 1 | Joe (1 row) + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); + a | b | c | o + ---+---+-----+--- + 1 | 1 | Joe | 1 + (1 row) + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; fooid | foosubid | fooname *************** *** 135,140 **** SELECT * FROM vw_getfoo; --- 320,333 ---- 1 | 1 | Joe (1 row) + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); + SELECT * FROM vw_getfoo; + a | b | c | o + ---+---+-----+--- + 1 | 1 | Joe | 1 + (1 row) + -- sql, proretset = t, prorettype = c DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); *************** *** 146,151 **** SELECT * FROM getfoo(1) AS t1; --- 339,351 ---- 1 | 2 | Ed (2 rows) + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); + a | b | c | o + ---+---+-----+--- + 1 | 1 | Joe | 1 + 1 | 2 | Ed | 2 + (2 rows) + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; fooid | foosubid | fooname *************** *** 154,159 **** SELECT * FROM vw_getfoo; --- 354,369 ---- 1 | 2 | Ed (2 rows) + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); + SELECT * FROM vw_getfoo; + a | b | c | o + ---+---+-----+--- + 1 | 1 | Joe | 1 + 1 | 2 | Ed | 2 + (2 rows) + + -- ordinality not supported for returns record yet -- sql, proretset = f, prorettype = record DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); *************** *** 202,207 **** SELECT * FROM getfoo(1) AS t1; --- 412,423 ---- 1 (1 row) + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); + v | o + ---+--- + 1 | 1 + (1 row) + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; getfoo *************** *** 209,214 **** SELECT * FROM vw_getfoo; --- 425,438 ---- 1 (1 row) + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); + SELECT * FROM vw_getfoo; + v | o + ---+--- + 1 | 1 + (1 row) + -- plpgsql, proretset = f, prorettype = c DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); *************** *** 219,224 **** SELECT * FROM getfoo(1) AS t1; --- 443,454 ---- 1 | 1 | Joe (1 row) + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); + a | b | c | o + ---+---+-----+--- + 1 | 1 | Joe | 1 + (1 row) + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; fooid | foosubid | fooname *************** *** 227,407 **** SELECT * FROM vw_getfoo; (1 row) DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); DROP FUNCTION foot(int); DROP TABLE foo2; DROP TABLE foo; -- Rescan tests -- ! CREATE TABLE foorescan (fooid int, foosubid int, fooname text, primary key(fooid,foosubid)); ! INSERT INTO foorescan values(5000,1,'abc.5000.1'); ! INSERT INTO foorescan values(5001,1,'abc.5001.1'); ! INSERT INTO foorescan values(5002,1,'abc.5002.1'); ! INSERT INTO foorescan values(5003,1,'abc.5003.1'); ! INSERT INTO foorescan values(5004,1,'abc.5004.1'); ! INSERT INTO foorescan values(5005,1,'abc.5005.1'); ! INSERT INTO foorescan values(5006,1,'abc.5006.1'); ! INSERT INTO foorescan values(5007,1,'abc.5007.1'); ! INSERT INTO foorescan values(5008,1,'abc.5008.1'); ! INSERT INTO foorescan values(5009,1,'abc.5009.1'); ! INSERT INTO foorescan values(5000,2,'abc.5000.2'); ! INSERT INTO foorescan values(5001,2,'abc.5001.2'); ! INSERT INTO foorescan values(5002,2,'abc.5002.2'); ! INSERT INTO foorescan values(5003,2,'abc.5003.2'); ! INSERT INTO foorescan values(5004,2,'abc.5004.2'); ! INSERT INTO foorescan values(5005,2,'abc.5005.2'); ! INSERT INTO foorescan values(5006,2,'abc.5006.2'); ! INSERT INTO foorescan values(5007,2,'abc.5007.2'); ! INSERT INTO foorescan values(5008,2,'abc.5008.2'); ! INSERT INTO foorescan values(5009,2,'abc.5009.2'); ! INSERT INTO foorescan values(5000,3,'abc.5000.3'); ! INSERT INTO foorescan values(5001,3,'abc.5001.3'); ! INSERT INTO foorescan values(5002,3,'abc.5002.3'); ! INSERT INTO foorescan values(5003,3,'abc.5003.3'); ! INSERT INTO foorescan values(5004,3,'abc.5004.3'); ! INSERT INTO foorescan values(5005,3,'abc.5005.3'); ! INSERT INTO foorescan values(5006,3,'abc.5006.3'); ! INSERT INTO foorescan values(5007,3,'abc.5007.3'); ! INSERT INTO foorescan values(5008,3,'abc.5008.3'); ! INSERT INTO foorescan values(5009,3,'abc.5009.3'); ! INSERT INTO foorescan values(5000,4,'abc.5000.4'); ! INSERT INTO foorescan values(5001,4,'abc.5001.4'); ! INSERT INTO foorescan values(5002,4,'abc.5002.4'); ! INSERT INTO foorescan values(5003,4,'abc.5003.4'); ! INSERT INTO foorescan values(5004,4,'abc.5004.4'); ! INSERT INTO foorescan values(5005,4,'abc.5005.4'); ! INSERT INTO foorescan values(5006,4,'abc.5006.4'); ! INSERT INTO foorescan values(5007,4,'abc.5007.4'); ! INSERT INTO foorescan values(5008,4,'abc.5008.4'); ! INSERT INTO foorescan values(5009,4,'abc.5009.4'); ! INSERT INTO foorescan values(5000,5,'abc.5000.5'); ! INSERT INTO foorescan values(5001,5,'abc.5001.5'); ! INSERT INTO foorescan values(5002,5,'abc.5002.5'); ! INSERT INTO foorescan values(5003,5,'abc.5003.5'); ! INSERT INTO foorescan values(5004,5,'abc.5004.5'); ! INSERT INTO foorescan values(5005,5,'abc.5005.5'); ! INSERT INTO foorescan values(5006,5,'abc.5006.5'); ! INSERT INTO foorescan values(5007,5,'abc.5007.5'); ! INSERT INTO foorescan values(5008,5,'abc.5008.5'); ! INSERT INTO foorescan values(5009,5,'abc.5009.5'); ! CREATE FUNCTION foorescan(int,int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid >= $1 and fooid < $2 ;' LANGUAGE SQL; ! --invokes ExecReScanFunctionScan ! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2; ! fooid | foosubid | fooname ! -------+----------+------------ ! 5002 | 1 | abc.5002.1 ! 5002 | 2 | abc.5002.2 ! 5002 | 3 | abc.5002.3 ! 5002 | 4 | abc.5002.4 ! 5002 | 5 | abc.5002.5 ! 5003 | 1 | abc.5003.1 ! 5003 | 2 | abc.5003.2 ! 5003 | 3 | abc.5003.3 ! 5003 | 4 | abc.5003.4 ! 5003 | 5 | abc.5003.5 (10 rows) ! CREATE VIEW vw_foorescan AS SELECT * FROM foorescan(5002,5004); ! --invokes ExecReScanFunctionScan ! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2; ! fooid | foosubid | fooname ! -------+----------+------------ ! 5002 | 1 | abc.5002.1 ! 5002 | 2 | abc.5002.2 ! 5002 | 3 | abc.5002.3 ! 5002 | 4 | abc.5002.4 ! 5002 | 5 | abc.5002.5 ! 5003 | 1 | abc.5003.1 ! 5003 | 2 | abc.5003.2 ! 5003 | 3 | abc.5003.3 ! 5003 | 4 | abc.5003.4 ! 5003 | 5 | abc.5003.5 (10 rows) ! CREATE TABLE barrescan (fooid int primary key); ! INSERT INTO barrescan values(5003); ! INSERT INTO barrescan values(5004); ! INSERT INTO barrescan values(5005); ! INSERT INTO barrescan values(5006); ! INSERT INTO barrescan values(5007); ! INSERT INTO barrescan values(5008); ! CREATE FUNCTION foorescan(int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid = $1;' LANGUAGE SQL; ! --invokes ExecReScanFunctionScan with chgParam != NULL ! SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2; ! fooid | foosubid | fooname ! -------+----------+------------ ! 5003 | 1 | abc.5003.1 ! 5003 | 2 | abc.5003.2 ! 5003 | 3 | abc.5003.3 ! 5003 | 4 | abc.5003.4 ! 5003 | 5 | abc.5003.5 ! 5004 | 1 | abc.5004.1 ! 5004 | 2 | abc.5004.2 ! 5004 | 3 | abc.5004.3 ! 5004 | 4 | abc.5004.4 ! 5004 | 5 | abc.5004.5 ! 5005 | 1 | abc.5005.1 ! 5005 | 2 | abc.5005.2 ! 5005 | 3 | abc.5005.3 ! 5005 | 4 | abc.5005.4 ! 5005 | 5 | abc.5005.5 ! 5006 | 1 | abc.5006.1 ! 5006 | 2 | abc.5006.2 ! 5006 | 3 | abc.5006.3 ! 5006 | 4 | abc.5006.4 ! 5006 | 5 | abc.5006.5 ! 5007 | 1 | abc.5007.1 ! 5007 | 2 | abc.5007.2 ! 5007 | 3 | abc.5007.3 ! 5007 | 4 | abc.5007.4 ! 5007 | 5 | abc.5007.5 ! 5008 | 1 | abc.5008.1 ! 5008 | 2 | abc.5008.2 ! 5008 | 3 | abc.5008.3 ! 5008 | 4 | abc.5008.4 ! 5008 | 5 | abc.5008.5 ! (30 rows) ! ! SELECT b.fooid, max(f.foosubid) FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2; ! fooid | max ! -------+----- ! 5003 | 5 ! 5004 | 5 ! 5005 | 5 ! 5006 | 5 ! 5007 | 5 ! 5008 | 5 (6 rows) ! CREATE VIEW fooview1 AS SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2; ! SELECT * FROM fooview1 AS fv WHERE fv.fooid = 5004; ! fooid | foosubid | fooname ! -------+----------+------------ ! 5004 | 1 | abc.5004.1 ! 5004 | 2 | abc.5004.2 ! 5004 | 3 | abc.5004.3 ! 5004 | 4 | abc.5004.4 ! 5004 | 5 | abc.5004.5 ! (5 rows) ! ! CREATE VIEW fooview2 AS SELECT b.fooid, max(f.foosubid) AS maxsubid FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2; ! SELECT * FROM fooview2 AS fv WHERE fv.maxsubid = 5; ! fooid | maxsubid ! -------+---------- ! 5003 | 5 ! 5004 | 5 ! 5005 | 5 ! 5006 | 5 ! 5007 | 5 ! 5008 | 5 (6 rows) ! DROP VIEW vw_foorescan; ! DROP VIEW fooview1; ! DROP VIEW fooview2; ! DROP FUNCTION foorescan(int,int); ! DROP FUNCTION foorescan(int); ! DROP TABLE foorescan; ! DROP TABLE barrescan; -- -- Test cases involving OUT parameters -- --- 457,1078 ---- (1 row) DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); + SELECT * FROM vw_getfoo; + a | b | c | o + ---+---+-----+--- + 1 | 1 | Joe | 1 + (1 row) + + DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); DROP FUNCTION foot(int); DROP TABLE foo2; DROP TABLE foo; -- Rescan tests -- ! CREATE TEMPORARY SEQUENCE foo_rescan_seq; ! CREATE TYPE foo_rescan_t AS (i integer, s bigint); ! CREATE FUNCTION foo_sql(int,int) RETURNS setof foo_rescan_t AS 'SELECT i, nextval(''foo_rescan_seq'') FROM generate_series($1,$2) i;' LANGUAGE SQL; ! -- plpgsql functions use materialize mode ! CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''foo_rescan_seq'')); end loop; end;' LANGUAGE plpgsql; ! --invokes ExecReScanFunctionScan - all these cases should materialize the function only once ! -- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function ! -- is on the inner path of a nestloop join ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100; ! r | i | s ! ---+----+--- ! 1 | 11 | 1 ! 1 | 12 | 2 ! 1 | 13 | 3 ! 2 | 11 | 1 ! 2 | 12 | 2 ! 2 | 13 | 3 ! 3 | 11 | 1 ! 3 | 12 | 2 ! 3 | 13 | 3 ! (9 rows) ! ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100; ! r | i | s | o ! ---+----+---+--- ! 1 | 11 | 1 | 1 ! 1 | 12 | 2 | 2 ! 1 | 13 | 3 | 3 ! 2 | 11 | 1 | 1 ! 2 | 12 | 2 | 2 ! 2 | 13 | 3 | 3 ! 3 | 11 | 1 | 1 ! 3 | 12 | 2 | 2 ! 3 | 13 | 3 | 3 ! (9 rows) ! ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100; ! r | i | s ! ---+----+--- ! 1 | 11 | 1 ! 1 | 12 | 2 ! 1 | 13 | 3 ! 2 | 11 | 1 ! 2 | 12 | 2 ! 2 | 13 | 3 ! 3 | 11 | 1 ! 3 | 12 | 2 ! 3 | 13 | 3 ! (9 rows) ! ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100; ! r | i | s | o ! ---+----+---+--- ! 1 | 11 | 1 | 1 ! 1 | 12 | 2 | 2 ! 1 | 13 | 3 | 3 ! 2 | 11 | 1 | 1 ! 2 | 12 | 2 | 2 ! 2 | 13 | 3 | 3 ! 3 | 11 | 1 | 1 ! 3 | 12 | 2 | 2 ! 3 | 13 | 3 | 3 ! (9 rows) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100; ! r | i ! ---+---- ! 1 | 11 ! 1 | 12 ! 1 | 13 ! 2 | 11 ! 2 | 12 ! 2 | 13 ! 3 | 11 ! 3 | 12 ! 3 | 13 ! (9 rows) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) WITH ORDINALITY AS f(i,o) ON (r+i)<100; ! r | i | o ! ---+----+--- ! 1 | 11 | 1 ! 1 | 12 | 2 ! 1 | 13 | 3 ! 2 | 11 | 1 ! 2 | 12 | 2 ! 2 | 13 | 3 ! 3 | 11 | 1 ! 3 | 12 | 2 ! 3 | 13 | 3 ! (9 rows) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) f(i) ON (r+i)<100; ! r | i ! ---+---- ! 1 | 10 ! 1 | 20 ! 1 | 30 ! 2 | 10 ! 2 | 20 ! 2 | 30 ! 3 | 10 ! 3 | 20 ! 3 | 30 ! (9 rows) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH ORDINALITY AS f(i,o) ON (r+i)<100; ! r | i | o ! ---+----+--- ! 1 | 10 | 1 ! 1 | 20 | 2 ! 1 | 30 | 3 ! 2 | 10 | 1 ! 2 | 20 | 2 ! 2 | 30 | 3 ! 3 | 10 | 1 ! 3 | 20 | 2 ! 3 | 30 | 3 ! (9 rows) ! ! --invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL) ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13); ! r | i | s ! ---+----+--- ! 1 | 11 | 1 ! 1 | 12 | 2 ! 1 | 13 | 3 ! 2 | 12 | 4 ! 2 | 13 | 5 ! 3 | 13 | 6 ! (6 rows) ! ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i,s,o); ! r | i | s | o ! ---+----+---+--- ! 1 | 11 | 1 | 1 ! 1 | 12 | 2 | 2 ! 1 | 13 | 3 | 3 ! 2 | 12 | 4 | 1 ! 2 | 13 | 5 | 2 ! 3 | 13 | 6 | 1 ! (6 rows) ! ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r); ! r | i | s ! ---+----+--- ! 1 | 11 | 1 ! 2 | 11 | 2 ! 2 | 12 | 3 ! 3 | 11 | 4 ! 3 | 12 | 5 ! 3 | 13 | 6 ! (6 rows) ! ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i,s,o); ! r | i | s | o ! ---+----+---+--- ! 1 | 11 | 1 | 1 ! 2 | 11 | 2 | 1 ! 2 | 12 | 3 | 2 ! 3 | 11 | 4 | 1 ! 3 | 12 | 5 | 2 ! 3 | 13 | 6 | 3 ! (6 rows) ! ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2); ! r1 | r2 | i | s ! ----+----+----+---- ! 11 | 12 | 11 | 1 ! 11 | 12 | 12 | 2 ! 13 | 15 | 13 | 3 ! 13 | 15 | 14 | 4 ! 13 | 15 | 15 | 5 ! 16 | 20 | 16 | 6 ! 16 | 20 | 17 | 7 ! 16 | 20 | 18 | 8 ! 16 | 20 | 19 | 9 ! 16 | 20 | 20 | 10 (10 rows) ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORDINALITY AS f(i,s,o); ! r1 | r2 | i | s | o ! ----+----+----+----+--- ! 11 | 12 | 11 | 1 | 1 ! 11 | 12 | 12 | 2 | 2 ! 13 | 15 | 13 | 3 | 1 ! 13 | 15 | 14 | 4 | 2 ! 13 | 15 | 15 | 5 | 3 ! 16 | 20 | 16 | 6 | 1 ! 16 | 20 | 17 | 7 | 2 ! 16 | 20 | 18 | 8 | 3 ! 16 | 20 | 19 | 9 | 4 ! 16 | 20 | 20 | 10 | 5 (10 rows) ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13); ! r | i | s ! ---+----+--- ! 1 | 11 | 1 ! 1 | 12 | 2 ! 1 | 13 | 3 ! 2 | 12 | 4 ! 2 | 13 | 5 ! 3 | 13 | 6 ! (6 rows) ! ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o); ! r | i | s | o ! ---+----+---+--- ! 1 | 11 | 1 | 1 ! 1 | 12 | 2 | 2 ! 1 | 13 | 3 | 3 ! 2 | 12 | 4 | 1 ! 2 | 13 | 5 | 2 ! 3 | 13 | 6 | 1 (6 rows) ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r); ! r | i | s ! ---+----+--- ! 1 | 11 | 1 ! 2 | 11 | 2 ! 2 | 12 | 3 ! 3 | 11 | 4 ! 3 | 12 | 5 ! 3 | 13 | 6 ! (6 rows) ! ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i,s,o); ! r | i | s | o ! ---+----+---+--- ! 1 | 11 | 1 | 1 ! 2 | 11 | 2 | 1 ! 2 | 12 | 3 | 2 ! 3 | 11 | 4 | 1 ! 3 | 12 | 5 | 2 ! 3 | 13 | 6 | 3 (6 rows) ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2); ! r1 | r2 | i | s ! ----+----+----+---- ! 11 | 12 | 11 | 1 ! 11 | 12 | 12 | 2 ! 13 | 15 | 13 | 3 ! 13 | 15 | 14 | 4 ! 13 | 15 | 15 | 5 ! 16 | 20 | 16 | 6 ! 16 | 20 | 17 | 7 ! 16 | 20 | 18 | 8 ! 16 | 20 | 19 | 9 ! 16 | 20 | 20 | 10 ! (10 rows) ! ! SELECT setval('foo_rescan_seq',1,false); ! setval ! -------- ! 1 ! (1 row) ! ! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORDINALITY AS f(i,s,o); ! r1 | r2 | i | s | o ! ----+----+----+----+--- ! 11 | 12 | 11 | 1 | 1 ! 11 | 12 | 12 | 2 | 2 ! 13 | 15 | 13 | 3 | 1 ! 13 | 15 | 14 | 4 | 2 ! 13 | 15 | 15 | 5 | 3 ! 16 | 20 | 16 | 6 | 1 ! 16 | 20 | 17 | 7 | 2 ! 16 | 20 | 18 | 8 | 3 ! 16 | 20 | 19 | 9 | 4 ! 16 | 20 | 20 | 10 | 5 ! (10 rows) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i); ! r | i ! ---+---- ! 1 | 11 ! 1 | 12 ! 1 | 13 ! 1 | 14 ! 1 | 15 ! 1 | 16 ! 1 | 17 ! 1 | 18 ! 1 | 19 ! 2 | 12 ! 2 | 13 ! 2 | 14 ! 2 | 15 ! 2 | 16 ! 2 | 17 ! 2 | 18 ! 3 | 13 ! 3 | 14 ! 3 | 15 ! 3 | 16 ! 3 | 17 ! (21 rows) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o); ! r | i | o ! ---+----+--- ! 1 | 11 | 1 ! 1 | 12 | 2 ! 1 | 13 | 3 ! 1 | 14 | 4 ! 1 | 15 | 5 ! 1 | 16 | 6 ! 1 | 17 | 7 ! 1 | 18 | 8 ! 1 | 19 | 9 ! 2 | 12 | 1 ! 2 | 13 | 2 ! 2 | 14 | 3 ! 2 | 15 | 4 ! 2 | 16 | 5 ! 2 | 17 | 6 ! 2 | 18 | 7 ! 3 | 13 | 1 ! 3 | 14 | 2 ! 3 | 15 | 3 ! 3 | 16 | 4 ! 3 | 17 | 5 ! (21 rows) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) f(i); ! r | i ! ---+---- ! 1 | 10 ! 1 | 20 ! 1 | 30 ! 2 | 20 ! 2 | 40 ! 2 | 60 ! 3 | 30 ! 3 | 60 ! 3 | 90 ! (9 rows) ! ! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) WITH ORDINALITY AS f(i,o); ! r | i | o ! ---+----+--- ! 1 | 10 | 1 ! 1 | 20 | 2 ! 1 | 30 | 3 ! 2 | 20 | 1 ! 2 | 40 | 2 ! 2 | 60 | 3 ! 3 | 30 | 1 ! 3 | 60 | 2 ! 3 | 90 | 3 ! (9 rows) ! ! -- deep nesting ! SELECT * FROM (VALUES (1),(2),(3)) v1(r1), ! LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) ! LEFT JOIN generate_series(21,23) f(i) ON ((r2+i)<100) OFFSET 0) s1; ! r1 | r1 | r2 | i ! ----+----+----+---- ! 1 | 1 | 10 | 21 ! 1 | 1 | 10 | 22 ! 1 | 1 | 10 | 23 ! 1 | 1 | 20 | 21 ! 1 | 1 | 20 | 22 ! 1 | 1 | 20 | 23 ! 1 | 1 | 30 | 21 ! 1 | 1 | 30 | 22 ! 1 | 1 | 30 | 23 ! 2 | 2 | 10 | 21 ! 2 | 2 | 10 | 22 ! 2 | 2 | 10 | 23 ! 2 | 2 | 20 | 21 ! 2 | 2 | 20 | 22 ! 2 | 2 | 20 | 23 ! 2 | 2 | 30 | 21 ! 2 | 2 | 30 | 22 ! 2 | 2 | 30 | 23 ! 3 | 3 | 10 | 21 ! 3 | 3 | 10 | 22 ! 3 | 3 | 10 | 23 ! 3 | 3 | 20 | 21 ! 3 | 3 | 20 | 22 ! 3 | 3 | 20 | 23 ! 3 | 3 | 30 | 21 ! 3 | 3 | 30 | 22 ! 3 | 3 | 30 | 23 ! (27 rows) ! ! SELECT * FROM (VALUES (1),(2),(3)) v1(r1), ! LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) ! LEFT JOIN generate_series(20+r1,23) f(i) ON ((r2+i)<100) OFFSET 0) s1; ! r1 | r1 | r2 | i ! ----+----+----+---- ! 1 | 1 | 10 | 21 ! 1 | 1 | 10 | 22 ! 1 | 1 | 10 | 23 ! 1 | 1 | 20 | 21 ! 1 | 1 | 20 | 22 ! 1 | 1 | 20 | 23 ! 1 | 1 | 30 | 21 ! 1 | 1 | 30 | 22 ! 1 | 1 | 30 | 23 ! 2 | 2 | 10 | 22 ! 2 | 2 | 10 | 23 ! 2 | 2 | 20 | 22 ! 2 | 2 | 20 | 23 ! 2 | 2 | 30 | 22 ! 2 | 2 | 30 | 23 ! 3 | 3 | 10 | 23 ! 3 | 3 | 20 | 23 ! 3 | 3 | 30 | 23 ! (18 rows) ! ! SELECT * FROM (VALUES (1),(2),(3)) v1(r1), ! LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) ! LEFT JOIN generate_series(r2,r2+3) f(i) ON ((r2+i)<100) OFFSET 0) s1; ! r1 | r1 | r2 | i ! ----+----+----+---- ! 1 | 1 | 10 | 10 ! 1 | 1 | 10 | 11 ! 1 | 1 | 10 | 12 ! 1 | 1 | 10 | 13 ! 1 | 1 | 20 | 20 ! 1 | 1 | 20 | 21 ! 1 | 1 | 20 | 22 ! 1 | 1 | 20 | 23 ! 1 | 1 | 30 | 30 ! 1 | 1 | 30 | 31 ! 1 | 1 | 30 | 32 ! 1 | 1 | 30 | 33 ! 2 | 2 | 10 | 10 ! 2 | 2 | 10 | 11 ! 2 | 2 | 10 | 12 ! 2 | 2 | 10 | 13 ! 2 | 2 | 20 | 20 ! 2 | 2 | 20 | 21 ! 2 | 2 | 20 | 22 ! 2 | 2 | 20 | 23 ! 2 | 2 | 30 | 30 ! 2 | 2 | 30 | 31 ! 2 | 2 | 30 | 32 ! 2 | 2 | 30 | 33 ! 3 | 3 | 10 | 10 ! 3 | 3 | 10 | 11 ! 3 | 3 | 10 | 12 ! 3 | 3 | 10 | 13 ! 3 | 3 | 20 | 20 ! 3 | 3 | 20 | 21 ! 3 | 3 | 20 | 22 ! 3 | 3 | 20 | 23 ! 3 | 3 | 30 | 30 ! 3 | 3 | 30 | 31 ! 3 | 3 | 30 | 32 ! 3 | 3 | 30 | 33 ! (36 rows) ! ! SELECT * FROM (VALUES (1),(2),(3)) v1(r1), ! LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) ! LEFT JOIN generate_series(r1,2+r2/5) f(i) ON ((r2+i)<100) OFFSET 0) s1; ! r1 | r1 | r2 | i ! ----+----+----+--- ! 1 | 1 | 10 | 1 ! 1 | 1 | 10 | 2 ! 1 | 1 | 10 | 3 ! 1 | 1 | 10 | 4 ! 1 | 1 | 20 | 1 ! 1 | 1 | 20 | 2 ! 1 | 1 | 20 | 3 ! 1 | 1 | 20 | 4 ! 1 | 1 | 20 | 5 ! 1 | 1 | 20 | 6 ! 1 | 1 | 30 | 1 ! 1 | 1 | 30 | 2 ! 1 | 1 | 30 | 3 ! 1 | 1 | 30 | 4 ! 1 | 1 | 30 | 5 ! 1 | 1 | 30 | 6 ! 1 | 1 | 30 | 7 ! 1 | 1 | 30 | 8 ! 2 | 2 | 10 | 2 ! 2 | 2 | 10 | 3 ! 2 | 2 | 10 | 4 ! 2 | 2 | 20 | 2 ! 2 | 2 | 20 | 3 ! 2 | 2 | 20 | 4 ! 2 | 2 | 20 | 5 ! 2 | 2 | 20 | 6 ! 2 | 2 | 30 | 2 ! 2 | 2 | 30 | 3 ! 2 | 2 | 30 | 4 ! 2 | 2 | 30 | 5 ! 2 | 2 | 30 | 6 ! 2 | 2 | 30 | 7 ! 2 | 2 | 30 | 8 ! 3 | 3 | 10 | 3 ! 3 | 3 | 10 | 4 ! 3 | 3 | 20 | 3 ! 3 | 3 | 20 | 4 ! 3 | 3 | 20 | 5 ! 3 | 3 | 20 | 6 ! 3 | 3 | 30 | 3 ! 3 | 3 | 30 | 4 ! 3 | 3 | 30 | 5 ! 3 | 3 | 30 | 6 ! 3 | 3 | 30 | 7 ! 3 | 3 | 30 | 8 ! (45 rows) ! ! DROP FUNCTION foo_sql(int,int); ! DROP FUNCTION foo_mat(int,int); ! DROP SEQUENCE foo_rescan_seq; -- -- Test cases involving OUT parameters -- *************** *** 877,882 **** SELECT * FROM get_users(); --- 1548,1560 ---- id2 | email2 | t (2 rows) + SELECT * FROM get_users() WITH ORDINALITY; -- make sure ordinality copes + userid | email | enabled | ?column? + --------+--------+---------+---------- + id | email | t | 1 + id2 | email2 | t | 2 + (2 rows) + drop function get_first_user(); drop function get_users(); drop table users; *** a/src/test/regress/sql/rangefuncs.sql --- b/src/test/regress/sql/rangefuncs.sql *************** *** 5,15 **** INSERT INTO foo2 VALUES(1, 11); INSERT INTO foo2 VALUES(2, 22); INSERT INTO foo2 VALUES(1, 111); ! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1;' LANGUAGE SQL; -- function with implicit LATERAL select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2; -- function in subselect select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = foo2.fooid) ORDER BY 1,2; --- 5,44 ---- INSERT INTO foo2 VALUES(2, 22); INSERT INTO foo2 VALUES(1, 111); ! CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1 ORDER BY f2;' LANGUAGE SQL; ! ! -- function with ORDINALITY ! select * from foot(1) with ordinality as z(a,b,ord); ! select * from foot(1) with ordinality as z(a,b,ord) where b > 100; -- ordinal 2, not 1 ! -- ordinality vs. column names and types ! select a,b,ord from foot(1) with ordinality as z(a,b,ord); ! select a,ord from unnest(array['a','b']) with ordinality as z(a,ord); ! select * from unnest(array['a','b']) with ordinality as z(a,ord); ! select a,ord from unnest(array[1.0::float8]) with ordinality as z(a,ord); ! select * from unnest(array[1.0::float8]) with ordinality as z(a,ord); ! -- ordinality vs. views ! create temporary view vw_ord as select * from (values (1)) v(n) join foot(1) with ordinality as z(a,b,ord) on (n=ord); ! select * from vw_ord; ! select definition from pg_views where viewname='vw_ord'; ! drop view vw_ord; ! -- ordinality vs. rewind and reverse scan ! begin; ! declare foo scroll cursor for select * from generate_series(1,5) with ordinality as g(i,o); ! fetch all from foo; ! fetch backward all from foo; ! fetch all from foo; ! fetch next from foo; ! fetch next from foo; ! fetch prior from foo; ! fetch absolute 1 from foo; ! commit; -- function with implicit LATERAL select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2; + -- function with implicit LATERAL and explicit ORDINALITY + select * from foo2, foot(foo2.fooid) with ordinality as z(fooid,f2,ord) where foo2.f2 = z.f2; + -- function in subselect select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = foo2.fooid) ORDER BY 1,2; *************** *** 30,70 **** INSERT INTO foo VALUES(2,1,'Mary'); --- 59,120 ---- -- sql, proretset = f, prorettype = b CREATE FUNCTION getfoo(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1; + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY as t1(v,o); + SELECT * FROM vw_getfoo; -- sql, proretset = t, prorettype = b DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); CREATE FUNCTION getfoo(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1; + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); + SELECT * FROM vw_getfoo; -- sql, proretset = t, prorettype = b DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); CREATE FUNCTION getfoo(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1; + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); + SELECT * FROM vw_getfoo; -- sql, proretset = f, prorettype = c DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); CREATE FUNCTION getfoo(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1; + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); + SELECT * FROM vw_getfoo; -- sql, proretset = t, prorettype = c DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); CREATE FUNCTION getfoo(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1; + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); + SELECT * FROM vw_getfoo; + -- ordinality not supported for returns record yet -- sql, proretset = f, prorettype = record DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); *************** *** 88,103 **** DROP VIEW vw_getfoo; --- 138,161 ---- DROP FUNCTION getfoo(int); CREATE FUNCTION getfoo(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql; SELECT * FROM getfoo(1) AS t1; + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); + SELECT * FROM vw_getfoo; -- plpgsql, proretset = f, prorettype = c DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); CREATE FUNCTION getfoo(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql; SELECT * FROM getfoo(1) AS t1; + SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); + SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); *************** *** 106,204 **** DROP TABLE foo2; DROP TABLE foo; -- Rescan tests -- ! CREATE TABLE foorescan (fooid int, foosubid int, fooname text, primary key(fooid,foosubid)); ! INSERT INTO foorescan values(5000,1,'abc.5000.1'); ! INSERT INTO foorescan values(5001,1,'abc.5001.1'); ! INSERT INTO foorescan values(5002,1,'abc.5002.1'); ! INSERT INTO foorescan values(5003,1,'abc.5003.1'); ! INSERT INTO foorescan values(5004,1,'abc.5004.1'); ! INSERT INTO foorescan values(5005,1,'abc.5005.1'); ! INSERT INTO foorescan values(5006,1,'abc.5006.1'); ! INSERT INTO foorescan values(5007,1,'abc.5007.1'); ! INSERT INTO foorescan values(5008,1,'abc.5008.1'); ! INSERT INTO foorescan values(5009,1,'abc.5009.1'); ! INSERT INTO foorescan values(5000,2,'abc.5000.2'); ! INSERT INTO foorescan values(5001,2,'abc.5001.2'); ! INSERT INTO foorescan values(5002,2,'abc.5002.2'); ! INSERT INTO foorescan values(5003,2,'abc.5003.2'); ! INSERT INTO foorescan values(5004,2,'abc.5004.2'); ! INSERT INTO foorescan values(5005,2,'abc.5005.2'); ! INSERT INTO foorescan values(5006,2,'abc.5006.2'); ! INSERT INTO foorescan values(5007,2,'abc.5007.2'); ! INSERT INTO foorescan values(5008,2,'abc.5008.2'); ! INSERT INTO foorescan values(5009,2,'abc.5009.2'); ! INSERT INTO foorescan values(5000,3,'abc.5000.3'); ! INSERT INTO foorescan values(5001,3,'abc.5001.3'); ! INSERT INTO foorescan values(5002,3,'abc.5002.3'); ! INSERT INTO foorescan values(5003,3,'abc.5003.3'); ! INSERT INTO foorescan values(5004,3,'abc.5004.3'); ! INSERT INTO foorescan values(5005,3,'abc.5005.3'); ! INSERT INTO foorescan values(5006,3,'abc.5006.3'); ! INSERT INTO foorescan values(5007,3,'abc.5007.3'); ! INSERT INTO foorescan values(5008,3,'abc.5008.3'); ! INSERT INTO foorescan values(5009,3,'abc.5009.3'); ! INSERT INTO foorescan values(5000,4,'abc.5000.4'); ! INSERT INTO foorescan values(5001,4,'abc.5001.4'); ! INSERT INTO foorescan values(5002,4,'abc.5002.4'); ! INSERT INTO foorescan values(5003,4,'abc.5003.4'); ! INSERT INTO foorescan values(5004,4,'abc.5004.4'); ! INSERT INTO foorescan values(5005,4,'abc.5005.4'); ! INSERT INTO foorescan values(5006,4,'abc.5006.4'); ! INSERT INTO foorescan values(5007,4,'abc.5007.4'); ! INSERT INTO foorescan values(5008,4,'abc.5008.4'); ! INSERT INTO foorescan values(5009,4,'abc.5009.4'); ! INSERT INTO foorescan values(5000,5,'abc.5000.5'); ! INSERT INTO foorescan values(5001,5,'abc.5001.5'); ! INSERT INTO foorescan values(5002,5,'abc.5002.5'); ! INSERT INTO foorescan values(5003,5,'abc.5003.5'); ! INSERT INTO foorescan values(5004,5,'abc.5004.5'); ! INSERT INTO foorescan values(5005,5,'abc.5005.5'); ! INSERT INTO foorescan values(5006,5,'abc.5006.5'); ! INSERT INTO foorescan values(5007,5,'abc.5007.5'); ! INSERT INTO foorescan values(5008,5,'abc.5008.5'); ! INSERT INTO foorescan values(5009,5,'abc.5009.5'); ! CREATE FUNCTION foorescan(int,int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid >= $1 and fooid < $2 ;' LANGUAGE SQL; ! --invokes ExecReScanFunctionScan ! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM foorescan(5002,5004)) ORDER BY 1,2; ! CREATE VIEW vw_foorescan AS SELECT * FROM foorescan(5002,5004); ! --invokes ExecReScanFunctionScan ! SELECT * FROM foorescan f WHERE f.fooid IN (SELECT fooid FROM vw_foorescan) ORDER BY 1,2; ! CREATE TABLE barrescan (fooid int primary key); ! INSERT INTO barrescan values(5003); ! INSERT INTO barrescan values(5004); ! INSERT INTO barrescan values(5005); ! INSERT INTO barrescan values(5006); ! INSERT INTO barrescan values(5007); ! INSERT INTO barrescan values(5008); ! CREATE FUNCTION foorescan(int) RETURNS setof foorescan AS 'SELECT * FROM foorescan WHERE fooid = $1;' LANGUAGE SQL; ! --invokes ExecReScanFunctionScan with chgParam != NULL ! SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2; ! SELECT b.fooid, max(f.foosubid) FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2; ! CREATE VIEW fooview1 AS SELECT f.* FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) ORDER BY 1,2; ! SELECT * FROM fooview1 AS fv WHERE fv.fooid = 5004; ! CREATE VIEW fooview2 AS SELECT b.fooid, max(f.foosubid) AS maxsubid FROM barrescan b, foorescan f WHERE f.fooid = b.fooid AND b.fooid IN (SELECT fooid FROM foorescan(b.fooid)) GROUP BY b.fooid ORDER BY 1,2; ! SELECT * FROM fooview2 AS fv WHERE fv.maxsubid = 5; ! DROP VIEW vw_foorescan; ! DROP VIEW fooview1; ! DROP VIEW fooview2; ! DROP FUNCTION foorescan(int,int); ! DROP FUNCTION foorescan(int); ! DROP TABLE foorescan; ! DROP TABLE barrescan; -- -- Test cases involving OUT parameters --- 164,248 ---- DROP TABLE foo; -- Rescan tests -- ! CREATE TEMPORARY SEQUENCE foo_rescan_seq; ! CREATE TYPE foo_rescan_t AS (i integer, s bigint); ! CREATE FUNCTION foo_sql(int,int) RETURNS setof foo_rescan_t AS 'SELECT i, nextval(''foo_rescan_seq'') FROM generate_series($1,$2) i;' LANGUAGE SQL; ! -- plpgsql functions use materialize mode ! CREATE FUNCTION foo_mat(int,int) RETURNS setof foo_rescan_t AS 'begin for i in $1..$2 loop return next (i, nextval(''foo_rescan_seq'')); end loop; end;' LANGUAGE plpgsql; ! --invokes ExecReScanFunctionScan - all these cases should materialize the function only once ! -- LEFT JOIN on a condition that the planner can't prove to be true is used to ensure the function ! -- is on the inner path of a nestloop join ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) ON (r+i)<100; ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_sql(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100; ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) ON (r+i)<100; ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN foo_mat(11,13) WITH ORDINALITY AS f(i,s,o) ON (r+i)<100; ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) f(i) ON (r+i)<100; ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN generate_series(11,13) WITH ORDINALITY AS f(i,o) ON (r+i)<100; ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) f(i) ON (r+i)<100; ! SELECT * FROM (VALUES (1),(2),(3)) v(r) LEFT JOIN unnest(array[10,20,30]) WITH ORDINALITY AS f(i,o) ON (r+i)<100; ! --invokes ExecReScanFunctionScan with chgParam != NULL (using implied LATERAL) ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13); ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(10+r,13) WITH ORDINALITY AS f(i,s,o); ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r); ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_sql(11,10+r) WITH ORDINALITY AS f(i,s,o); ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2); ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_sql(r1,r2) WITH ORDINALITY AS f(i,s,o); ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13); ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(10+r,13) WITH ORDINALITY AS f(i,s,o); ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r); ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), foo_mat(11,10+r) WITH ORDINALITY AS f(i,s,o); ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2); ! SELECT setval('foo_rescan_seq',1,false); ! SELECT * FROM (VALUES (11,12),(13,15),(16,20)) v(r1,r2), foo_mat(r1,r2) WITH ORDINALITY AS f(i,s,o); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) f(i); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), generate_series(10+r,20-r) WITH ORDINALITY AS f(i,o); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) f(i); ! SELECT * FROM (VALUES (1),(2),(3)) v(r), unnest(array[r*10,r*20,r*30]) WITH ORDINALITY AS f(i,o); ! -- deep nesting ! SELECT * FROM (VALUES (1),(2),(3)) v1(r1), ! LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) ! LEFT JOIN generate_series(21,23) f(i) ON ((r2+i)<100) OFFSET 0) s1; ! SELECT * FROM (VALUES (1),(2),(3)) v1(r1), ! LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) ! LEFT JOIN generate_series(20+r1,23) f(i) ON ((r2+i)<100) OFFSET 0) s1; ! SELECT * FROM (VALUES (1),(2),(3)) v1(r1), ! LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) ! LEFT JOIN generate_series(r2,r2+3) f(i) ON ((r2+i)<100) OFFSET 0) s1; ! SELECT * FROM (VALUES (1),(2),(3)) v1(r1), ! LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) ! LEFT JOIN generate_series(r1,2+r2/5) f(i) ON ((r2+i)<100) OFFSET 0) s1; ! DROP FUNCTION foo_sql(int,int); ! DROP FUNCTION foo_mat(int,int); ! DROP SEQUENCE foo_rescan_seq; -- -- Test cases involving OUT parameters *************** *** 414,419 **** language sql stable; --- 458,464 ---- SELECT get_users(); SELECT * FROM get_users(); + SELECT * FROM get_users() WITH ORDINALITY; -- make sure ordinality copes drop function get_first_user(); drop function get_users();