*** a/contrib/pg_stat_statements/pg_stat_statements.c --- b/contrib/pg_stat_statements/pg_stat_statements.c *************** *** 1456,1462 **** JumbleRangeTable(pgssJumbleState *jstate, List *rtable) APP_JUMB(rte->jointype); break; case RTE_FUNCTION: ! JumbleExpr(jstate, rte->funcexpr); break; case RTE_VALUES: JumbleExpr(jstate, (Node *) rte->values_lists); --- 1456,1462 ---- APP_JUMB(rte->jointype); break; case RTE_FUNCTION: ! JumbleExpr(jstate, (Node *) rte->funcexprs); break; case RTE_VALUES: JumbleExpr(jstate, (Node *) rte->values_lists); *** a/src/backend/access/common/tupdesc.c --- b/src/backend/access/common/tupdesc.c *************** *** 192,197 **** CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts) --- 192,234 ---- } /* + * CreateTupleDescCopyMany + * This function creates a new TupleDesc by copying from a set of + * existing TupleDescs. The new tupdesc is an anonymous record type + * (and never has oids) + * + * !!! Constraints and defaults are not copied !!! + */ + TupleDesc + CreateTupleDescCopyMany(TupleDesc *tupdescs, int numtupdescs) + { + TupleDesc desc; + int i,j; + int src_natts = 0; + int att = 0; + + for (i = 0; i < numtupdescs; ++i) + src_natts += tupdescs[i]->natts; + + desc = CreateTemplateTupleDesc(src_natts, false); + + for (i = 0; i < numtupdescs; ++i) + { + int natts = tupdescs[i]->natts; + for (j = 0; j < natts; j++) + { + memcpy(desc->attrs[att], tupdescs[i]->attrs[j], ATTRIBUTE_FIXED_PART_SIZE); + desc->attrs[att]->attnum = att + 1; + desc->attrs[att]->attnotnull = false; + desc->attrs[att]->atthasdef = false; + ++att; + } + } + + return desc; + } + + /* * CreateTupleDescCopyConstr * This function creates a new TupleDesc by copying from an existing * TupleDesc (including its constraints and defaults). *** a/src/backend/catalog/dependency.c --- b/src/backend/catalog/dependency.c *************** *** 1601,1609 **** find_expr_references_walker(Node *node, --- 1601,1630 ---- else if (IsA(node, FuncExpr)) { FuncExpr *funcexpr = (FuncExpr *) node; + ListCell *ct; add_object_address(OCLASS_PROC, funcexpr->funcid, 0, context->addrs); + + /* + * FuncExpr in a function RTE may have a column definition list, + * in which case deal with its types and collations + */ + foreach(ct, funcexpr->funccoltypes) + { + add_object_address(OCLASS_TYPE, lfirst_oid(ct), 0, + context->addrs); + } + foreach(ct, funcexpr->funccolcollations) + { + Oid collid = lfirst_oid(ct); + + if (OidIsValid(collid) && + collid != DEFAULT_COLLATION_OID) + add_object_address(OCLASS_COLLATION, collid, 0, + context->addrs); + } + /* fall through to examine arguments */ } else if (IsA(node, OpExpr)) *************** *** 1757,1764 **** find_expr_references_walker(Node *node, /* * Add whole-relation refs for each plain relation mentioned in the ! * subquery's rtable, as well as refs for any datatypes and collations ! * used in a RECORD function's output. * * Note: query_tree_walker takes care of recursing into RTE_FUNCTION * RTEs, subqueries, etc, so no need to do that here. But keep it --- 1778,1786 ---- /* * Add whole-relation refs for each plain relation mentioned in the ! * subquery's rtable. Refs for any datatypes and collations ! * used in RECORD function column definitions lists are now handled ! * under FuncExpr. * * Note: query_tree_walker takes care of recursing into RTE_FUNCTION * RTEs, subqueries, etc, so no need to do that here. But keep it *************** *** 1771,1777 **** find_expr_references_walker(Node *node, foreach(lc, query->rtable) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); - ListCell *ct; switch (rte->rtekind) { --- 1793,1798 ---- *************** *** 1779,1800 **** find_expr_references_walker(Node *node, add_object_address(OCLASS_CLASS, rte->relid, 0, context->addrs); break; - case RTE_FUNCTION: - foreach(ct, rte->funccoltypes) - { - add_object_address(OCLASS_TYPE, lfirst_oid(ct), 0, - context->addrs); - } - foreach(ct, rte->funccolcollations) - { - Oid collid = lfirst_oid(ct); - - if (OidIsValid(collid) && - collid != DEFAULT_COLLATION_OID) - add_object_address(OCLASS_COLLATION, collid, 0, - context->addrs); - } - break; default: break; } --- 1800,1805 ---- *** a/src/backend/commands/explain.c --- b/src/backend/commands/explain.c *************** *** 1259,1265 **** ExplainNode(PlanState *planstate, List *ancestors, break; case T_FunctionScan: if (es->verbose) ! show_expression(((FunctionScan *) plan)->funcexpr, "Function Call", planstate, ancestors, es->verbose, es); show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); --- 1259,1265 ---- break; case T_FunctionScan: if (es->verbose) ! show_expression((Node *) ((FunctionScan *) plan)->funcexprs, "Function Call", planstate, ancestors, es->verbose, es); show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); *************** *** 1984,1990 **** ExplainTargetRel(Plan *plan, Index rti, ExplainState *es) break; case T_FunctionScan: { ! Node *funcexpr; /* Assert it's on a RangeFunction */ Assert(rte->rtekind == RTE_FUNCTION); --- 1984,1990 ---- break; case T_FunctionScan: { ! List *funcexprs; /* Assert it's on a RangeFunction */ Assert(rte->rtekind == RTE_FUNCTION); *************** *** 1995,2004 **** ExplainTargetRel(Plan *plan, Index rti, ExplainState *es) * happen if the optimizer simplified away the function call, * for example). */ ! funcexpr = ((FunctionScan *) plan)->funcexpr; ! if (funcexpr && IsA(funcexpr, FuncExpr)) { ! Oid funcid = ((FuncExpr *) funcexpr)->funcid; objectname = get_func_name(funcid); if (es->verbose) --- 1995,2004 ---- * happen if the optimizer simplified away the function call, * for example). */ ! funcexprs = ((FunctionScan *) plan)->funcexprs; ! if (funcexprs && list_length(funcexprs) == 1 && IsA(linitial(funcexprs), FuncExpr)) { ! Oid funcid = ((FuncExpr *) linitial(funcexprs))->funcid; objectname = get_func_name(funcid); if (es->verbose) *** a/src/backend/executor/functions.c --- b/src/backend/executor/functions.c *************** *** 380,387 **** sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var) param = ParseFuncOrColumn(pstate, list_make1(subfield), list_make1(param), ! NIL, NULL, false, false, false, ! NULL, true, cref->location); } return param; --- 380,387 ---- param = ParseFuncOrColumn(pstate, list_make1(subfield), list_make1(param), ! cref->location, ! NULL); } return param; *** a/src/backend/executor/nodeFunctionscan.c --- b/src/backend/executor/nodeFunctionscan.c *************** *** 44,76 **** 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; } --- 44,87 ---- { EState *estate; ScanDirection direction; TupleTableSlot *scanslot; ! TupleTableSlot **funcslots; ! ListCell *lc; ! int funcno = 0; ! int att = 0; ! bool alldone = true; ! int64 *rowcounts = node->rowcounts; ! int64 oldpos; ! ! if (node->func_slots) { /* ! * ORDINALITY or multiple functions case: ! * ! * We fetch the function results into FUNCSLOTs (which match the ! * function return types), and then copy the values to SCANSLOT (which ! * matches the scan result type), setting the ordinal column in the ! * process. * ! * Clear scanslot here for simplicity. */ ! funcslots = node->func_slots; scanslot = node->ss.ss_ScanTupleSlot; + ExecClearTuple(scanslot); } else { /* ! * trivial case: the function return type and scan result type are the ! * same, so we fetch the function result straight into the scan result ! * slot. ! * ! * We treat ss_ScanTupleSlot as an array of one element so that the ! * code in the loop below works for both cases seamlessly. */ ! funcslots = &node->ss.ss_ScanTupleSlot; scanslot = NULL; } *************** *** 80,120 **** FunctionNext(FunctionScanState *node) estate = node->ss.ps.state; direction = estate->es_direction; - tuplestorestate = node->tuplestorestate; - - /* - * If first time through, read all tuples from function and put them in a - * tuplestore. Subsequent calls just fetch tuples from tuplestore. - */ - if (tuplestorestate == NULL) - { - 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 --- 91,96 ---- *************** *** 123,151 **** FunctionNext(FunctionScanState *node) */ 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; } --- 99,228 ---- */ if (ScanDirectionIsForward(direction)) ! oldpos = node->ordinal++; else ! oldpos = node->ordinal--; ! /* ! * Main loop over functions. ! * ! * func_tupdescs, funcslots, tuplestorestates and rowcounts are all arrays ! * sized by number of functions. However, in the simple case of one ! * function and no ordinality, rowcounts will be NULL and funcslots will ! * point at ss_ScanTupleSlot; we bail out of the function in the simple ! * case before this becomes an issue. ! */ ! ! foreach(lc, node->funcexprs) { ! TupleTableSlot *slot = funcslots[funcno]; ! Tuplestorestate *tstore = node->tuplestorestates[funcno]; ! int i, natts; ! ! /* ! * If first time through, read all tuples from function and put them in a ! * tuplestore. Subsequent calls just fetch tuples from tuplestore. ! */ ! if (tstore == NULL) ! { ! node->tuplestorestates[funcno] ! = tstore ! = ExecMakeTableFunctionResult(lfirst(lc), ! node->ss.ps.ps_ExprContext, ! node->func_tupdescs[funcno], ! node->eflags & EXEC_FLAG_BACKWARD); ! /* ! * paranoia - cope if the function, which may have constructed the ! * tuplestore itself, didn't leave it pointing at the start. This ! * call is fast, so the overhead shouldn't be an issue. ! */ ! tuplestore_rescan(tstore); ! } ! ! /* ! * Get the next tuple from tuplestore. ! * ! * If we have a rowcount for the function, and we know the previous ! * read position was out of bounds, don't try the read. This allows ! * backward scan to work when there are mixed row counts present. ! */ ! if (rowcounts && rowcounts[funcno] != -1 && rowcounts[funcno] < oldpos) ! ExecClearTuple(slot); ! else ! (void) tuplestore_gettupleslot(tstore, ! ScanDirectionIsForward(direction), ! false, ! slot); ! ! /* bail on the simple case now */ ! ! if (!scanslot) ! return slot; ! ! natts = node->func_tupdescs[funcno]->natts; ! Assert(rowcounts); ! if (TupIsNull(slot)) { ! /* ! * If we ran out of data for this function in the forward ! * direction then we now know how many rows it returned. We need ! * to know this in order to handle backwards scans. The row count ! * we store is actually 1+ the actual number, because we have to ! * position the tuplestore 1 off its end sometimes. ! */ ! ! if (ScanDirectionIsForward(direction) && rowcounts[funcno] == -1) ! rowcounts[funcno] = node->ordinal; ! ! /* ! * populate our result cols with null ! */ ! for (i = 0; i < natts; ++i, ++att) ! { ! scanslot->tts_values[att] = (Datum) 0; ! scanslot->tts_isnull[att] = true; ! } } + else + { + /* + * we have a result, so just copy it to the result cols. + */ ! slot_getallattrs(slot); ! for (i = 0; i < natts; ++i, ++att) ! { ! scanslot->tts_values[att] = slot->tts_values[i]; ! scanslot->tts_isnull[att] = slot->tts_isnull[i]; ! } ! ! /* ! * We're not done until every function result is exhausted; we ! * pad the shorter results with nulls until then. ! */ ! ! alldone = false; ! } ! ! ++funcno; } + /* + * ordinal col is always last, per spec. + */ + + if (node->ordinality) + { + scanslot->tts_values[att] = Int64GetDatumFast(node->ordinal); + scanslot->tts_isnull[att] = false; + } + + if (!alldone) + ExecStoreVirtualTuple(scanslot); + return scanslot; } *************** *** 186,193 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) FunctionScanState *scanstate; Oid funcrettype; TypeFuncClass functypclass; ! TupleDesc func_tupdesc = NULL; TupleDesc scan_tupdesc = NULL; /* check for unsupported flags */ Assert(!(eflags & EXEC_FLAG_MARK)); --- 263,275 ---- FunctionScanState *scanstate; Oid funcrettype; TypeFuncClass functypclass; ! TupleDesc *func_tupdescs = NULL; TupleDesc scan_tupdesc = NULL; + int nfuncs = list_length(node->funcexprs); + bool ordinality = node->funcordinality; + int ntupdescs = nfuncs + (ordinality ? 1 : 0); + int i, atts_done; + ListCell *lc; /* check for unsupported flags */ Assert(!(eflags & EXEC_FLAG_MARK)); *************** *** 207,212 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) --- 289,311 ---- scanstate->eflags = eflags; /* + * are we adding an ordinality column? + */ + scanstate->ordinality = ordinality; + + /* + * Ordinal 0 represents the "before the first row" position. + * + * We need to track ordinal position even when not adding an ordinality + * column to the result, in order to handle backwards scanning properly + * with multiple functions with different result sizes. (We can't position + * any individual function's tuplestore any more than 1 place beyond its + * end, so when scanning backwards, we need to know when to start + * including the function in the scan again.) + */ + scanstate->ordinal = 0; + + /* * Miscellaneous initialization * * create expression context for node *************** *** 220,235 **** 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 *) --- 319,324 ---- *************** *** 239,351 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) ExecInitExpr((Expr *) node->scan.plan.qual, (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 ! { ! /* crummy error message, but parser should have caught this */ ! elog(ERROR, "function in FROM has unsupported return type"); ! } ! /* ! * For RECORD results, make sure a typmod has been assigned. (The ! * 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); ! scanstate->ss.ps.ps_TupFromTlist = false; /* * Initialize result tuple type and projection info. --- 328,487 ---- ExecInitExpr((Expr *) node->scan.plan.qual, (PlanState *) scanstate); + scanstate->funcexprs = (List *) ExecInitExpr((Expr *) node->funcexprs, + (PlanState *) scanstate); + /* ! * Set up to initialize a tupdesc for each function, plus one for the ! * ordinality column if any. We need this even for the one-function case. */ ! scanstate->func_tupdescs ! = func_tupdescs ! = palloc(ntupdescs * sizeof(TupleDesc)); ! ! i = 0; ! atts_done = 0; ! foreach(lc, node->funcexprs) { ! TupleDesc tupdesc; /* ! * Determine if this function returns a simple or composite type, and ! * build an appropriate tupdesc. This tupdesc is the one that matches ! * the shape of the function result, no extra columns. */ + functypclass = get_expr_result_type(lfirst(lc), + &funcrettype, + &tupdesc); ! if (functypclass == TYPEFUNC_COMPOSITE) ! { ! /* Composite data type, e.g. a table's row type */ ! Assert(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); ! ! atts_done += tupdesc->natts; ! } ! else if (functypclass == TYPEFUNC_SCALAR) ! { ! /* Base data type, i.e. scalar */ ! char *attname = strVal(list_nth(node->funccolnames, atts_done)); ! ! tupdesc = CreateTemplateTupleDesc(1, false); ! TupleDescInitEntry(tupdesc, ! (AttrNumber) 1, ! attname, ! funcrettype, ! -1, ! 0); ! TupleDescInitEntryCollation(tupdesc, ! (AttrNumber) 1, ! exprCollation(lfirst(lc))); ! ! ++atts_done; ! } ! else ! { ! /* crummy error message, but parser should have caught this */ ! elog(ERROR, "function in FROM has unsupported return type"); ! } ! func_tupdescs[i++] = tupdesc; ! } /* ! * If doing ordinality, we need a new tupdesc with one column, always of ! * type "bigint", to add to the end of the collection of tupdescs. The ! * column name to use has already been recorded by the parser as the last ! * element of funccolnames. * ! * Without ordinality or multiple functions, the scan result tupdesc is ! * the same as the function result tupdesc. (No need to make a copy.) */ ! if (ntupdescs > 1) { ! if (ordinality) ! { ! TupleDesc tupdesc = CreateTemplateTupleDesc(1, false); ! TupleDescInitEntry(tupdesc, ! (AttrNumber) 1, ! strVal(llast(node->funccolnames)), ! INT8OID, ! -1, ! 0); ! func_tupdescs[nfuncs] = tupdesc; ! } ! /* ! * Produce the final combined tupdesc ! */ ! scan_tupdesc = CreateTupleDescCopyMany(func_tupdescs, ntupdescs); } else ! scan_tupdesc = func_tupdescs[0]; ! ! /* ! * We didn't necessarily bless all the individual function tupdescs, but ! * we have to ensure that the scan result tupdesc is, regardless of where ! * it came from. ! */ ! BlessTupleDesc(scan_tupdesc); scanstate->scan_tupdesc = scan_tupdesc; ! ExecAssignScanType(&scanstate->ss, scan_tupdesc); /* ! * We only need separate slots for the function results if we are doing ! * ordinality or multiple functions; otherwise, we fetch function ! * results directly into the scan slot. Same for rowcounts. ! * ! * However, we don't need a slot for the ordinality col, even though we ! * made a tupdesc for it. */ ! if (ntupdescs > 1) ! { ! scanstate->func_slots = palloc(nfuncs * sizeof(TupleTableSlot *)); ! scanstate->rowcounts = palloc(nfuncs * sizeof(int64)); ! for (i = 0; i < nfuncs; ++i) ! { ! scanstate->rowcounts[i] = -1; ! scanstate->func_slots[i] = ExecInitExtraTupleSlot(estate); ! ExecSetSlotDescriptor(scanstate->func_slots[i], func_tupdescs[i]); ! } ! } ! else ! { ! scanstate->func_slots = NULL; ! scanstate->rowcounts = NULL; ! } ! /* ! * Need to track one tuplestore per function, but we don't allocate the ! * tuplestores; the actual call to the function does that. NULL flags ! * that we have not called the function yet (or need to call it again ! * after a rescan). ! */ ! scanstate->tuplestorestates = palloc(nfuncs * sizeof(Tuplestorestate *)); ! for (i = 0; i < nfuncs; ++i) ! scanstate->tuplestorestates[i] = NULL; /* * Initialize result tuple type and projection info. *************** *** 353,358 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) --- 489,496 ---- ExecAssignResultTypeFromTL(&scanstate->ss.ps); ExecAssignScanProjectionInfo(&scanstate->ss); + scanstate->ss.ps.ps_TupFromTlist = false; + return scanstate; } *************** *** 365,370 **** ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) --- 503,511 ---- void ExecEndFunctionScan(FunctionScanState *node) { + int i; + int nfuncs = list_length(node->funcexprs); + /* * Free the exprcontext */ *************** *** 375,389 **** 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 */ ! if (node->tuplestorestate != NULL) ! tuplestore_end(node->tuplestorestate); ! node->tuplestorestate = NULL; } /* ---------------------------------------------------------------- --- 516,535 ---- */ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); ExecClearTuple(node->ss.ss_ScanTupleSlot); ! ! if (node->func_slots) ! for (i = 0; i < nfuncs; ++i) ! ExecClearTuple(node->func_slots[i]); /* * Release tuplestore resources */ ! for (i = 0; i < nfuncs; ++i) ! { ! if (node->tuplestorestates[i] != NULL) ! tuplestore_end(node->tuplestorestates[i]); ! node->tuplestorestates[i] = NULL; ! } } /* ---------------------------------------------------------------- *************** *** 395,425 **** ExecEndFunctionScan(FunctionScanState *node) 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. */ ! if (!node->tuplestorestate) ! return; ! /* ! * Here we have a choice whether to drop the tuplestore (and recompute the ! * function outputs) or just rescan it. We must recompute if the ! * expression contains parameters, else we rescan. XXX maybe we should ! * recompute if the function is volatile? ! */ ! if (node->ss.ps.chgParam != NULL) { ! tuplestore_end(node->tuplestorestate); ! node->tuplestorestate = NULL; } - else - tuplestore_rescan(node->tuplestorestate); } --- 541,593 ---- void ExecReScanFunctionScan(FunctionScanState *node) { + int i; + int nfuncs = list_length(node->funcexprs); + Bitmapset *chgparam = node->ss.ps.chgParam; + ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); ! if (node->func_slots) ! for (i = 0; i < nfuncs; ++i) ! ExecClearTuple(node->func_slots[i]); ExecScanReScan(&node->ss); node->ordinal = 0; /* ! * Here we have a choice whether to drop the tuplestores (and recompute ! * the function outputs) or just rescan them. We must recompute if the ! * expression contains changed parameters, else we rescan. ! * ! * Note that if chgparam is NULL, it's possible that the funcparams list ! * may be empty (if there never were any params and so finalize_plan was ! * never called), so we have to be careful about iterating or referencing ! * it. ! * ! * XXX maybe we should recompute if the function is volatile? */ ! if (chgparam) ! { ! List *funcparams = ((FunctionScan *) node->ss.ps.plan)->funcparams; ! ListCell *lc; ! i = 0; ! foreach(lc, funcparams) ! { ! if (bms_overlap(chgparam, lfirst(lc))) ! { ! if (node->tuplestorestates[i] != NULL) ! tuplestore_end(node->tuplestorestates[i]); ! node->tuplestorestates[i] = NULL; ! if (node->rowcounts) ! node->rowcounts[i] = -1; ! } ! ++i; ! } ! } ! for (i = 0; i < nfuncs; ++i) { ! if (node->tuplestorestates[i] != NULL) ! tuplestore_rescan(node->tuplestorestates[i]); } } *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 495,500 **** static FunctionScan * --- 495,501 ---- _copyFunctionScan(const FunctionScan *from) { FunctionScan *newnode = makeNode(FunctionScan); + ListCell *lc; /* * copy node superclass fields *************** *** 504,516 **** _copyFunctionScan(const FunctionScan *from) /* * copy remainder of node */ ! COPY_NODE_FIELD(funcexpr); COPY_NODE_FIELD(funccolnames); - COPY_NODE_FIELD(funccoltypes); - COPY_NODE_FIELD(funccoltypmods); - COPY_NODE_FIELD(funccolcollations); COPY_SCALAR_FIELD(funcordinality); return newnode; } --- 505,522 ---- /* * copy remainder of node */ ! COPY_NODE_FIELD(funcexprs); COPY_NODE_FIELD(funccolnames); COPY_SCALAR_FIELD(funcordinality); + /* + * copy the param bitmap list by shallow-copying the list + * structure, then replacing the values with copies + */ + newnode->funcparams = list_copy(from->funcparams); + foreach(lc, newnode->funcparams) + lfirst(lc) = bms_copy(lfirst(lc)); + return newnode; } *************** *** 1205,1210 **** _copyFuncExpr(const FuncExpr *from) --- 1211,1220 ---- COPY_SCALAR_FIELD(funccollid); COPY_SCALAR_FIELD(inputcollid); COPY_NODE_FIELD(args); + COPY_NODE_FIELD(funccolnames); + COPY_NODE_FIELD(funccoltypes); + COPY_NODE_FIELD(funccoltypmods); + COPY_NODE_FIELD(funccolcollations); COPY_LOCATION_FIELD(location); return newnode; *************** *** 1980,1989 **** _copyRangeTblEntry(const RangeTblEntry *from) COPY_SCALAR_FIELD(security_barrier); COPY_SCALAR_FIELD(jointype); COPY_NODE_FIELD(joinaliasvars); ! COPY_NODE_FIELD(funcexpr); ! 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); --- 1990,1996 ---- COPY_SCALAR_FIELD(security_barrier); COPY_SCALAR_FIELD(jointype); COPY_NODE_FIELD(joinaliasvars); ! COPY_NODE_FIELD(funcexprs); COPY_SCALAR_FIELD(funcordinality); COPY_NODE_FIELD(values_lists); COPY_NODE_FIELD(values_collations); *************** *** 2174,2179 **** _copyFuncCall(const FuncCall *from) --- 2181,2187 ---- COPY_SCALAR_FIELD(agg_distinct); COPY_SCALAR_FIELD(func_variadic); COPY_NODE_FIELD(over); + COPY_NODE_FIELD(coldeflist); COPY_LOCATION_FIELD(location); return newnode; *************** *** 2300,2308 **** _copyRangeFunction(const RangeFunction *from) COPY_SCALAR_FIELD(ordinality); COPY_SCALAR_FIELD(lateral); ! COPY_NODE_FIELD(funccallnode); COPY_NODE_FIELD(alias); - COPY_NODE_FIELD(coldeflist); return newnode; } --- 2308,2316 ---- COPY_SCALAR_FIELD(ordinality); COPY_SCALAR_FIELD(lateral); ! COPY_SCALAR_FIELD(is_table); ! COPY_NODE_FIELD(funccallnodes); COPY_NODE_FIELD(alias); return newnode; } *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** *** 247,252 **** _equalFuncExpr(const FuncExpr *a, const FuncExpr *b) --- 247,256 ---- COMPARE_SCALAR_FIELD(funccollid); COMPARE_SCALAR_FIELD(inputcollid); COMPARE_NODE_FIELD(args); + COMPARE_NODE_FIELD(funccolnames); + COMPARE_NODE_FIELD(funccoltypes); + COMPARE_NODE_FIELD(funccoltypmods); + COMPARE_NODE_FIELD(funccolcollations); COMPARE_LOCATION_FIELD(location); return true; *************** *** 2006,2011 **** _equalFuncCall(const FuncCall *a, const FuncCall *b) --- 2010,2016 ---- COMPARE_SCALAR_FIELD(agg_distinct); COMPARE_SCALAR_FIELD(func_variadic); COMPARE_NODE_FIELD(over); + COMPARE_NODE_FIELD(coldeflist); COMPARE_LOCATION_FIELD(location); return true; *************** *** 2132,2140 **** _equalRangeFunction(const RangeFunction *a, const RangeFunction *b) { COMPARE_SCALAR_FIELD(ordinality); COMPARE_SCALAR_FIELD(lateral); ! COMPARE_NODE_FIELD(funccallnode); COMPARE_NODE_FIELD(alias); - COMPARE_NODE_FIELD(coldeflist); return true; } --- 2137,2145 ---- { COMPARE_SCALAR_FIELD(ordinality); COMPARE_SCALAR_FIELD(lateral); ! COMPARE_SCALAR_FIELD(is_table); ! COMPARE_NODE_FIELD(funccallnodes); COMPARE_NODE_FIELD(alias); return true; } *************** *** 2235,2244 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b) COMPARE_SCALAR_FIELD(security_barrier); COMPARE_SCALAR_FIELD(jointype); COMPARE_NODE_FIELD(joinaliasvars); ! COMPARE_NODE_FIELD(funcexpr); ! 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); --- 2240,2246 ---- COMPARE_SCALAR_FIELD(security_barrier); COMPARE_SCALAR_FIELD(jointype); COMPARE_NODE_FIELD(joinaliasvars); ! COMPARE_NODE_FIELD(funcexprs); COMPARE_SCALAR_FIELD(funcordinality); COMPARE_NODE_FIELD(values_lists); COMPARE_NODE_FIELD(values_collations); *** a/src/backend/nodes/makefuncs.c --- b/src/backend/nodes/makefuncs.c *************** *** 127,135 **** makeVarFromTargetEntry(Index varno, * 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, --- 127,135 ---- * 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 or ! * multiple function calls. This forces the result to be composite and RECORD ! * type. */ Var * makeWholeRowVar(RangeTblEntry *rte, *************** *** 164,179 **** makeWholeRowVar(RangeTblEntry *rte, * 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, --- 164,182 ---- * If ordinality is set, we return a composite var even if * the function is a scalar. This var is always of RECORD type. * + * If the RTE has more than one function, we return a composite + * var 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(linitial(rte->funcexprs)); ! if (rte->funcordinality || list_length(rte->funcexprs) > 1) { ! /* always produces an anonymous RECORD result */ result = makeVar(varno, InvalidAttrNumber, RECORDOID, *************** *** 198,204 **** makeWholeRowVar(RangeTblEntry *rte, 1, toid, -1, ! exprCollation(rte->funcexpr), varlevelsup); } else --- 201,207 ---- 1, toid, -1, ! exprCollation(linitial(rte->funcexprs)), varlevelsup); } else *************** *** 494,499 **** makeFuncExpr(Oid funcid, Oid rettype, List *args, --- 497,506 ---- funcexpr->funccollid = funccollid; funcexpr->inputcollid = inputcollid; funcexpr->args = args; + funcexpr->funccolnames = NIL; + funcexpr->funccoltypes = NIL; + funcexpr->funccoltypmods = NIL; + funcexpr->funccolcollations = NIL; funcexpr->location = -1; return funcexpr; *************** *** 559,564 **** makeFuncCall(List *name, List *args, int location) --- 566,572 ---- n->agg_distinct = FALSE; n->func_variadic = FALSE; n->over = NULL; + n->coldeflist = NULL; return n; } *** a/src/backend/nodes/nodeFuncs.c --- b/src/backend/nodes/nodeFuncs.c *************** *** 2000,2006 **** range_table_walker(List *rtable, return true; break; case RTE_FUNCTION: ! if (walker(rte->funcexpr, context)) return true; break; case RTE_VALUES: --- 2000,2006 ---- return true; break; case RTE_FUNCTION: ! if (walker(rte->funcexprs, context)) return true; break; case RTE_VALUES: *************** *** 2725,2731 **** range_table_mutator(List *rtable, } break; case RTE_FUNCTION: ! MUTATE(newrte->funcexpr, rte->funcexpr, Node *); break; case RTE_VALUES: MUTATE(newrte->values_lists, rte->values_lists, List *); --- 2725,2731 ---- } break; case RTE_FUNCTION: ! MUTATE(newrte->funcexprs, rte->funcexprs, List *); break; case RTE_VALUES: MUTATE(newrte->values_lists, rte->values_lists, List *); *************** *** 3035,3040 **** raw_expression_tree_walker(Node *node, --- 3035,3044 ---- if (walker(fcall->over, context)) return true; /* function name is deemed uninteresting */ + /* + * RangeFunction doesn't recurse into coldeflist + * so we don't either + */ } break; case T_NamedArgExpr: *************** *** 3113,3119 **** raw_expression_tree_walker(Node *node, { RangeFunction *rf = (RangeFunction *) node; ! if (walker(rf->funccallnode, context)) return true; if (walker(rf->alias, context)) return true; --- 3117,3123 ---- { RangeFunction *rf = (RangeFunction *) node; ! if (walker(rf->funccallnodes, context)) return true; if (walker(rf->alias, context)) return true; *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** *** 512,527 **** _outSubqueryScan(StringInfo str, const SubqueryScan *node) static void _outFunctionScan(StringInfo str, const FunctionScan *node) { WRITE_NODE_TYPE("FUNCTIONSCAN"); _outScanInfo(str, (const Scan *) node); ! WRITE_NODE_FIELD(funcexpr); WRITE_NODE_FIELD(funccolnames); - WRITE_NODE_FIELD(funccoltypes); - WRITE_NODE_FIELD(funccoltypmods); - WRITE_NODE_FIELD(funccolcollations); WRITE_BOOL_FIELD(funcordinality); } static void --- 512,533 ---- static void _outFunctionScan(StringInfo str, const FunctionScan *node) { + ListCell *lc; + WRITE_NODE_TYPE("FUNCTIONSCAN"); _outScanInfo(str, (const Scan *) node); ! WRITE_NODE_FIELD(funcexprs); WRITE_NODE_FIELD(funccolnames); WRITE_BOOL_FIELD(funcordinality); + + appendStringInfoString(str, " :funcparams"); + foreach(lc, node->funcparams) + { + appendStringInfoChar(str, ' '); + _outBitmapset(str, lfirst(lc)); + } } static void *************** *** 1011,1016 **** _outFuncExpr(StringInfo str, const FuncExpr *node) --- 1017,1026 ---- WRITE_OID_FIELD(funccollid); WRITE_OID_FIELD(inputcollid); WRITE_NODE_FIELD(args); + WRITE_NODE_FIELD(funccolnames); + WRITE_NODE_FIELD(funccoltypes); + WRITE_NODE_FIELD(funccoltypmods); + WRITE_NODE_FIELD(funccolcollations); WRITE_LOCATION_FIELD(location); } *************** *** 2090,2095 **** _outFuncCall(StringInfo str, const FuncCall *node) --- 2100,2106 ---- WRITE_BOOL_FIELD(agg_distinct); WRITE_BOOL_FIELD(func_variadic); WRITE_NODE_FIELD(over); + WRITE_NODE_FIELD(coldeflist); WRITE_LOCATION_FIELD(location); } *************** *** 2380,2389 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) WRITE_NODE_FIELD(joinaliasvars); break; case RTE_FUNCTION: ! WRITE_NODE_FIELD(funcexpr); ! WRITE_NODE_FIELD(funccoltypes); ! WRITE_NODE_FIELD(funccoltypmods); ! WRITE_NODE_FIELD(funccolcollations); WRITE_BOOL_FIELD(funcordinality); break; case RTE_VALUES: --- 2391,2397 ---- WRITE_NODE_FIELD(joinaliasvars); break; case RTE_FUNCTION: ! WRITE_NODE_FIELD(funcexprs); WRITE_BOOL_FIELD(funcordinality); break; case RTE_VALUES: *************** *** 2619,2627 **** _outRangeFunction(StringInfo str, const RangeFunction *node) WRITE_BOOL_FIELD(ordinality); WRITE_BOOL_FIELD(lateral); ! WRITE_NODE_FIELD(funccallnode); WRITE_NODE_FIELD(alias); - WRITE_NODE_FIELD(coldeflist); } static void --- 2627,2635 ---- WRITE_BOOL_FIELD(ordinality); WRITE_BOOL_FIELD(lateral); ! WRITE_BOOL_FIELD(is_table); ! WRITE_NODE_FIELD(funccallnodes); WRITE_NODE_FIELD(alias); } static void *** a/src/backend/nodes/readfuncs.c --- b/src/backend/nodes/readfuncs.c *************** *** 561,566 **** _readFuncExpr(void) --- 561,570 ---- READ_OID_FIELD(funccollid); READ_OID_FIELD(inputcollid); READ_NODE_FIELD(args); + READ_NODE_FIELD(funccolnames); + READ_NODE_FIELD(funccoltypes); + READ_NODE_FIELD(funccoltypmods); + READ_NODE_FIELD(funccolcollations); READ_LOCATION_FIELD(location); READ_DONE(); *************** *** 1219,1228 **** _readRangeTblEntry(void) READ_NODE_FIELD(joinaliasvars); break; case RTE_FUNCTION: ! READ_NODE_FIELD(funcexpr); ! READ_NODE_FIELD(funccoltypes); ! READ_NODE_FIELD(funccoltypmods); ! READ_NODE_FIELD(funccolcollations); READ_BOOL_FIELD(funcordinality); break; case RTE_VALUES: --- 1223,1229 ---- READ_NODE_FIELD(joinaliasvars); break; case RTE_FUNCTION: ! READ_NODE_FIELD(funcexprs); READ_BOOL_FIELD(funcordinality); break; case RTE_VALUES: *** a/src/backend/optimizer/path/allpaths.c --- b/src/backend/optimizer/path/allpaths.c *************** *** 37,42 **** --- 37,43 ---- #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" + #include "catalog/pg_opfamily.h" /* These parameters are set by GUC */ *************** *** 1258,1263 **** static void --- 1259,1265 ---- set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) { Relids required_outer; + List *pathkeys = NIL; /* * We don't support pushing join clauses into the quals of a function *************** *** 1266,1273 **** set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) */ required_outer = rel->lateral_relids; /* Generate appropriate path */ ! add_path(rel, create_functionscan_path(root, rel, required_outer)); /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); --- 1268,1325 ---- */ required_outer = rel->lateral_relids; + /* + * The result is treated as unordered unless ORDINALITY was used, in which + * case it is ordered by the ordinal column (last). + */ + if (rte->funcordinality) + { + ListCell *lc; + Var *var = NULL; + AttrNumber ordattno = list_length(rte->eref->colnames); + + /* + * Find corresponding Var in our tlist by searching for matching + * attno. + */ + + foreach(lc, rel->reltargetlist) + { + Var *node = lfirst(lc); + + if (IsA(node,Var) + && node->varno == rel->relid + && node->varattno == ordattno + && node->varlevelsup == 0) + { + var = node; + break; + } + } + + /* + * The Var might not be found in the tlist, but that should only + * happen if the ordinality column is never referenced anywhere + * in the query - in which case nobody can possibly care about the + * ordering of it. So just leave the pathkeys NIL in that case. + * + * Also, build_expression_pathkey will only build the pathkey if + * there's already an equivalence class; if there isn't, it indicates + * that nothing cares about the ordering. + */ + + if (var) + { + Oid operator = get_opfamily_member(INTEGER_BTREE_FAM_OID, + var->vartype, var->vartype, + BTLessStrategyNumber); + + pathkeys = build_expression_pathkey(root, rel, (Expr*) var, operator, false); + } + } + /* Generate appropriate path */ ! add_path(rel, create_functionscan_path(root, rel, pathkeys, required_outer)); /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); *** a/src/backend/optimizer/path/costsize.c --- b/src/backend/optimizer/path/costsize.c *************** *** 1042,1048 **** cost_functionscan(Path *path, PlannerInfo *root, * estimates for functions tend to be, there's not a lot of point in that * refinement right now. */ ! cost_qual_eval_node(&exprcost, rte->funcexpr, root); startup_cost += exprcost.startup + exprcost.per_tuple; --- 1042,1048 ---- * estimates for functions tend to be, there's not a lot of point in that * refinement right now. */ ! cost_qual_eval_node(&exprcost, (Node *) rte->funcexprs, root); startup_cost += exprcost.startup + exprcost.per_tuple; *************** *** 3799,3812 **** void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel) { RangeTblEntry *rte; /* Should only be applied to base relations that are functions */ Assert(rel->relid > 0); rte = planner_rt_fetch(rel->relid, root); Assert(rte->rtekind == RTE_FUNCTION); ! /* Estimate number of rows the function itself will return */ ! rel->tuples = expression_returns_set_rows(rte->funcexpr); /* Now estimate number of output rows, etc */ set_baserel_size_estimates(root, rel); --- 3799,3823 ---- set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel) { RangeTblEntry *rte; + ListCell *lc; /* Should only be applied to base relations that are functions */ Assert(rel->relid > 0); rte = planner_rt_fetch(rel->relid, root); Assert(rte->rtekind == RTE_FUNCTION); ! rel->tuples = 0; ! ! /* ! * Estimate number of rows the functions will return. The rowcount of ! * result of the node is that of the largest function result. ! */ ! foreach(lc, rte->funcexprs) ! { ! double ntup = expression_returns_set_rows(lfirst(lc)); ! if (ntup > rel->tuples) ! rel->tuples = ntup; ! } /* Now estimate number of output rows, etc */ set_baserel_size_estimates(root, rel); *** a/src/backend/optimizer/path/pathkeys.c --- b/src/backend/optimizer/path/pathkeys.c *************** *** 491,496 **** build_index_pathkeys(PlannerInfo *root, --- 491,546 ---- return retval; } + + /* + * build_expression_pathkey + * Build a pathkeys list (empty or 1 element) that describes an ordering + * of a single expression using a given operator + * + * The result is empty if the expression isn't already in some equivalence + * class. + * + * The main use for this is in declaring the ordering of ordinality + * columns in FunctionScan, but it's written this way to avoid making any + * assumptions about type. + */ + + List * + build_expression_pathkey(PlannerInfo *root, + RelOptInfo *rel, + Expr *expr, + Oid operator, + bool nulls_first) + { + List *pathkeys = NIL; + Oid opfamily, + opcintype; + int16 strategy; + PathKey *cpathkey; + + /* Find the operator in pg_amop --- failure shouldn't happen */ + if (!get_ordering_op_properties(operator, + &opfamily, &opcintype, &strategy)) + elog(ERROR, "operator %u is not a valid ordering operator", + operator); + + cpathkey = make_pathkey_from_sortinfo(root, + expr, + opfamily, + opcintype, + exprCollation((Node*) expr), + (strategy == BTGreaterStrategyNumber), + nulls_first, + 0, + rel->relids, + false); + + if (cpathkey) + pathkeys = lappend(pathkeys, cpathkey); + + return pathkeys; + } + /* * convert_subquery_pathkeys * Build a pathkeys list that describes the ordering of a subquery's *** a/src/backend/optimizer/plan/createplan.c --- b/src/backend/optimizer/plan/createplan.c *************** *** 115,123 **** 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, bool ordinality, ! List *funccolnames, List *funccoltypes, List *funccoltypmods, ! List *funccolcollations); static ValuesScan *make_valuesscan(List *qptlist, List *qpqual, Index scanrelid, List *values_lists); static CteScan *make_ctescan(List *qptlist, List *qpqual, --- 115,122 ---- static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid, List *tidquals); static FunctionScan *make_functionscan(List *qptlist, List *qpqual, ! Index scanrelid, List *funcexprs, bool ordinality, ! List *funccolnames); static ValuesScan *make_valuesscan(List *qptlist, List *qpqual, Index scanrelid, List *values_lists); static CteScan *make_ctescan(List *qptlist, List *qpqual, *************** *** 1709,1721 **** create_functionscan_plan(PlannerInfo *root, Path *best_path, FunctionScan *scan_plan; Index scan_relid = best_path->parent->relid; RangeTblEntry *rte; ! Node *funcexpr; /* it should be a function base rel... */ Assert(scan_relid > 0); rte = planner_rt_fetch(scan_relid, root); Assert(rte->rtekind == RTE_FUNCTION); ! funcexpr = rte->funcexpr; /* Sort clauses into best execution order */ scan_clauses = order_qual_clauses(root, scan_clauses); --- 1708,1720 ---- FunctionScan *scan_plan; Index scan_relid = best_path->parent->relid; RangeTblEntry *rte; ! List *funcexprs; /* it should be a function base rel... */ Assert(scan_relid > 0); rte = planner_rt_fetch(scan_relid, root); Assert(rte->rtekind == RTE_FUNCTION); ! funcexprs = rte->funcexprs; /* Sort clauses into best execution order */ scan_clauses = order_qual_clauses(root, scan_clauses); *************** *** 1729,1744 **** create_functionscan_plan(PlannerInfo *root, Path *best_path, scan_clauses = (List *) replace_nestloop_params(root, (Node *) scan_clauses); /* The func expression itself could contain nestloop params, too */ ! funcexpr = replace_nestloop_params(root, funcexpr); } scan_plan = make_functionscan(tlist, scan_clauses, scan_relid, ! funcexpr, rte->funcordinality, ! rte->eref->colnames, ! rte->funccoltypes, ! rte->funccoltypmods, ! rte->funccolcollations); copy_path_costsize(&scan_plan->scan.plan, best_path); --- 1728,1740 ---- scan_clauses = (List *) replace_nestloop_params(root, (Node *) scan_clauses); /* The func expression itself could contain nestloop params, too */ ! funcexprs = (List *) replace_nestloop_params(root, (Node *) funcexprs); } scan_plan = make_functionscan(tlist, scan_clauses, scan_relid, ! funcexprs, rte->funcordinality, ! rte->eref->colnames); copy_path_costsize(&scan_plan->scan.plan, best_path); *************** *** 3388,3399 **** static FunctionScan * make_functionscan(List *qptlist, List *qpqual, Index scanrelid, ! Node *funcexpr, bool ordinality, ! List *funccolnames, ! List *funccoltypes, ! List *funccoltypmods, ! List *funccolcollations) { FunctionScan *node = makeNode(FunctionScan); Plan *plan = &node->scan.plan; --- 3384,3392 ---- make_functionscan(List *qptlist, List *qpqual, Index scanrelid, ! List *funcexprs, bool ordinality, ! List *funccolnames) { FunctionScan *node = makeNode(FunctionScan); Plan *plan = &node->scan.plan; *************** *** 3404,3415 **** make_functionscan(List *qptlist, plan->lefttree = NULL; plan->righttree = NULL; node->scan.scanrelid = scanrelid; ! node->funcexpr = funcexpr; node->funcordinality = ordinality; node->funccolnames = funccolnames; ! node->funccoltypes = funccoltypes; ! node->funccoltypmods = funccoltypmods; ! node->funccolcollations = funccolcollations; return node; } --- 3397,3407 ---- plan->lefttree = NULL; plan->righttree = NULL; node->scan.scanrelid = scanrelid; ! node->funcexprs = funcexprs; node->funcordinality = ordinality; node->funccolnames = funccolnames; ! /* finalize_plan will fill this in if need be */ ! node->funcparams = NIL; return node; } *** a/src/backend/optimizer/plan/initsplan.c --- b/src/backend/optimizer/plan/initsplan.c *************** *** 307,313 **** extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex) if (rte->rtekind == RTE_SUBQUERY) vars = pull_vars_of_level((Node *) rte->subquery, 1); else if (rte->rtekind == RTE_FUNCTION) ! vars = pull_vars_of_level(rte->funcexpr, 0); else if (rte->rtekind == RTE_VALUES) vars = pull_vars_of_level((Node *) rte->values_lists, 0); else --- 307,313 ---- if (rte->rtekind == RTE_SUBQUERY) vars = pull_vars_of_level((Node *) rte->subquery, 1); else if (rte->rtekind == RTE_FUNCTION) ! vars = pull_vars_of_level((Node *) rte->funcexprs, 0); else if (rte->rtekind == RTE_VALUES) vars = pull_vars_of_level((Node *) rte->values_lists, 0); else *** a/src/backend/optimizer/plan/planner.c --- b/src/backend/optimizer/plan/planner.c *************** *** 487,493 **** subquery_planner(PlannerGlobal *glob, Query *parse, { /* Preprocess the function expression fully */ kind = rte->lateral ? EXPRKIND_RTFUNC_LATERAL : EXPRKIND_RTFUNC; ! rte->funcexpr = preprocess_expression(root, rte->funcexpr, kind); } else if (rte->rtekind == RTE_VALUES) { --- 487,493 ---- { /* Preprocess the function expression fully */ kind = rte->lateral ? EXPRKIND_RTFUNC_LATERAL : EXPRKIND_RTFUNC; ! rte->funcexprs = (List *) preprocess_expression(root, (Node *) rte->funcexprs, kind); } else if (rte->rtekind == RTE_VALUES) { *** a/src/backend/optimizer/plan/setrefs.c --- b/src/backend/optimizer/plan/setrefs.c *************** *** 381,390 **** add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte) /* zap unneeded sub-structure */ newrte->subquery = NULL; newrte->joinaliasvars = NIL; ! newrte->funcexpr = NULL; ! newrte->funccoltypes = NIL; ! newrte->funccoltypmods = NIL; ! newrte->funccolcollations = NIL; newrte->values_lists = NIL; newrte->values_collations = NIL; newrte->ctecoltypes = NIL; --- 381,387 ---- /* zap unneeded sub-structure */ newrte->subquery = NULL; newrte->joinaliasvars = NIL; ! newrte->funcexprs = NULL; newrte->values_lists = NIL; newrte->values_collations = NIL; newrte->ctecoltypes = NIL; *************** *** 525,532 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); splan->scan.plan.qual = fix_scan_list(root, splan->scan.plan.qual, rtoffset); ! splan->funcexpr = ! fix_scan_expr(root, splan->funcexpr, rtoffset); } break; case T_ValuesScan: --- 522,529 ---- fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); splan->scan.plan.qual = fix_scan_list(root, splan->scan.plan.qual, rtoffset); ! splan->funcexprs = ! fix_scan_list(root, splan->funcexprs, rtoffset); } break; case T_ValuesScan: *** a/src/backend/optimizer/plan/subselect.c --- b/src/backend/optimizer/plan/subselect.c *************** *** 2136,2144 **** finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, break; case T_FunctionScan: ! finalize_primnode(((FunctionScan *) plan)->funcexpr, ! &context); ! context.paramids = bms_add_members(context.paramids, scan_params); break; case T_ValuesScan: --- 2136,2174 ---- break; case T_FunctionScan: ! { ! finalize_primnode_context funccontext; ! FunctionScan *fscan = (FunctionScan *) plan; ! ListCell *lc; ! ! /* ! * Call finalize_primnode independently on each funcexpr so ! * that we can record which params are referenced in each, in ! * order to decide which need re-evaluating. ! */ ! ! funccontext = context; ! ! Assert(fscan->funcparams == NIL); ! ! foreach(lc, fscan->funcexprs) ! { ! funccontext.paramids = NULL; ! ! finalize_primnode(lfirst(lc), &funccontext); ! ! /* add the function's params to the overall set */ ! context.paramids = bms_add_members(context.paramids, ! funccontext.paramids); ! ! /* hand off the function's params to the node's list */ ! fscan->funcparams = lappend(fscan->funcparams, ! funccontext.paramids); ! } ! ! context.paramids = bms_add_members(context.paramids, ! scan_params); ! } break; case T_ValuesScan: *** a/src/backend/optimizer/prep/prepjointree.c --- b/src/backend/optimizer/prep/prepjointree.c *************** *** 580,589 **** inline_set_returning_functions(PlannerInfo *root) /* Successful expansion, replace the rtable entry */ rte->rtekind = RTE_SUBQUERY; rte->subquery = funcquery; ! rte->funcexpr = NULL; ! rte->funccoltypes = NIL; ! rte->funccoltypmods = NIL; ! rte->funccolcollations = NIL; } } } --- 580,586 ---- /* Successful expansion, replace the rtable entry */ rte->rtekind = RTE_SUBQUERY; rte->subquery = funcquery; ! rte->funcexprs = NULL; } } } *************** *** 1623,1630 **** replace_vars_in_jointree(Node *jtnode, context); break; case RTE_FUNCTION: ! rte->funcexpr = ! pullup_replace_vars(rte->funcexpr, context); break; case RTE_VALUES: --- 1620,1627 ---- context); break; case RTE_FUNCTION: ! rte->funcexprs = (List *) ! pullup_replace_vars((Node *) rte->funcexprs, context); break; case RTE_VALUES: *** a/src/backend/optimizer/util/clauses.c --- b/src/backend/optimizer/util/clauses.c *************** *** 111,116 **** static Expr *simplify_function(Oid funcid, --- 111,117 ---- Oid result_type, int32 result_typmod, Oid result_collid, Oid input_collid, List **args_p, bool funcvariadic, bool process_args, bool allow_non_const, + FuncExpr *orig_funcexpr, eval_const_expressions_context *context); static List *expand_function_arguments(List *args, Oid result_type, HeapTuple func_tuple); *************** *** 2274,2279 **** eval_const_expressions_mutator(Node *node, --- 2275,2281 ---- expr->funcvariadic, true, true, + expr, context); if (simple) /* successfully simplified it */ return (Node *) simple; *************** *** 2293,2298 **** eval_const_expressions_mutator(Node *node, --- 2295,2304 ---- newexpr->funccollid = expr->funccollid; newexpr->inputcollid = expr->inputcollid; newexpr->args = args; + newexpr->funccolnames = expr->funccolnames; + newexpr->funccoltypes = expr->funccoltypes; + newexpr->funccoltypmods = expr->funccoltypmods; + newexpr->funccolcollations = expr->funccolcollations; newexpr->location = expr->location; return (Node *) newexpr; } *************** *** 2321,2326 **** eval_const_expressions_mutator(Node *node, --- 2327,2333 ---- false, true, true, + NULL, context); if (simple) /* successfully simplified it */ return (Node *) simple; *************** *** 2425,2430 **** eval_const_expressions_mutator(Node *node, --- 2432,2438 ---- false, false, false, + NULL, context); if (simple) /* successfully simplified it */ { *************** *** 2629,2634 **** eval_const_expressions_mutator(Node *node, --- 2637,2643 ---- false, true, true, + NULL, context); if (simple) /* successfully simplified output fn */ { *************** *** 2661,2666 **** eval_const_expressions_mutator(Node *node, --- 2670,2676 ---- false, false, true, + NULL, context); if (simple) /* successfully simplified input fn */ return (Node *) simple; *************** *** 3529,3534 **** static Expr * --- 3539,3545 ---- simplify_function(Oid funcid, Oid result_type, int32 result_typmod, Oid result_collid, Oid input_collid, List **args_p, bool funcvariadic, bool process_args, bool allow_non_const, + FuncExpr *orig_funcexpr, eval_const_expressions_context *context) { List *args = *args_p; *************** *** 3594,3599 **** simplify_function(Oid funcid, Oid result_type, int32 result_typmod, --- 3605,3624 ---- fexpr.funccollid = result_collid; fexpr.inputcollid = input_collid; fexpr.args = args; + if (orig_funcexpr) + { + fexpr.funccolnames = orig_funcexpr->funccolnames; + fexpr.funccoltypes = orig_funcexpr->funccoltypes; + fexpr.funccoltypmods = orig_funcexpr->funccoltypmods; + fexpr.funccolcollations = orig_funcexpr->funccolcollations; + } + else + { + fexpr.funccolnames = NIL; + fexpr.funccoltypes = NIL; + fexpr.funccoltypmods = NIL; + fexpr.funccolcollations = NIL; + } fexpr.location = -1; newexpr = (Expr *) *************** *** 4456,4466 **** inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) 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; /* --- 4481,4493 ---- if (rte->funcordinality) return NULL; ! /* Fail if FROM item isn't a simple, single, FuncExpr */ ! if (list_length(rte->funcexprs) != 1 ! || !IsA(linitial(rte->funcexprs), FuncExpr)) return NULL; + fexpr = (FuncExpr *) linitial(rte->funcexprs); + func_oid = fexpr->funcid; /* *************** *** 4641,4655 **** inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) /* * If it returns RECORD, we have to check against the column type list ! * provided in the RTE; check_sql_fn_retval can't do that. (If no match, ! * we just fail to inline, rather than complaining; see notes for ! * tlist_matches_coltypelist.) We don't have to do this for functions ! * with declared OUT parameters, even though their funcresulttype is ! * RECORDOID, so check get_func_result_type too. */ if (fexpr->funcresulttype == RECORDOID && get_func_result_type(func_oid, NULL, NULL) == TYPEFUNC_RECORD && ! !tlist_matches_coltypelist(querytree->targetList, rte->funccoltypes)) goto fail; /* --- 4668,4682 ---- /* * If it returns RECORD, we have to check against the column type list ! * provided in the FuncExpr (used to be in the RTE); check_sql_fn_retval ! * can't do that. (If no match, we just fail to inline, rather than ! * complaining; see notes for tlist_matches_coltypelist.) We don't have to ! * do this for functions with declared OUT parameters, even though their ! * funcresulttype is RECORDOID, so check get_func_result_type too. */ if (fexpr->funcresulttype == RECORDOID && get_func_result_type(func_oid, NULL, NULL) == TYPEFUNC_RECORD && ! !tlist_matches_coltypelist(querytree->targetList, fexpr->funccoltypes)) goto fail; /* *** a/src/backend/optimizer/util/pathnode.c --- b/src/backend/optimizer/util/pathnode.c *************** *** 1623,1628 **** create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel, --- 1623,1629 ---- */ Path * create_functionscan_path(PlannerInfo *root, RelOptInfo *rel, + List *pathkeys, Relids required_outer) { Path *pathnode = makeNode(Path); *************** *** 1631,1637 **** create_functionscan_path(PlannerInfo *root, RelOptInfo *rel, pathnode->parent = rel; pathnode->param_info = get_baserel_parampathinfo(root, rel, required_outer); ! pathnode->pathkeys = NIL; /* for now, assume unordered result */ cost_functionscan(pathnode, root, rel, pathnode->param_info); --- 1632,1638 ---- pathnode->parent = rel; pathnode->param_info = get_baserel_parampathinfo(root, rel, required_outer); ! pathnode->pathkeys = pathkeys; cost_functionscan(pathnode, root, rel, pathnode->param_info); *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 153,158 **** static void doNegateFloat(Value *v); --- 153,159 ---- static Node *makeAArrayExpr(List *elements, int location); static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args, int location); + static void processTableFuncColdef(RangeFunction *n, List *coldeflist, int location, core_yyscan_t yyscanner); static List *mergeTableFuncParameters(List *func_args, List *columns); static TypeName *TableFuncTypeName(List *columns); static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner); *************** *** 402,409 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type def_elem reloption_elem old_aggr_elem %type def_arg columnElem where_clause where_or_current_clause a_expr b_expr c_expr AexprConst indirection_el ! columnref in_expr having_clause func_table array_expr ExclusionWhereClause %type ExclusionConstraintList ExclusionConstraintElem %type func_arg_list %type func_arg_expr --- 403,413 ---- %type def_elem reloption_elem old_aggr_elem %type def_arg columnElem where_clause where_or_current_clause a_expr b_expr c_expr AexprConst indirection_el ! columnref in_expr having_clause array_expr ExclusionWhereClause + %type func_table func_table_list func_table_single + %type func_table_def opt_col_def_list + %type opt_ordinality %type ExclusionConstraintList ExclusionConstraintElem %type func_arg_list %type func_arg_expr *************** *** 9575,9626 **** from_list: | from_list ',' table_ref { $$ = lappend($1, $3); } ; /* * table_ref is where an alias clause can be attached. */ table_ref: relation_expr opt_alias_clause { $1->alias = $2; $$ = (Node *) $1; } ! | func_table func_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 --- 9579,9624 ---- | from_list ',' table_ref { $$ = lappend($1, $3); } ; + + opt_ordinality: WITH_ORDINALITY { $$ = true; } + | /*EMPTY*/ { $$ = false; } + ; + /* * table_ref is where an alias clause can be attached. + * + * func_table is a list whose first element is a list of FuncCall nodes, + * and which has a second element iff the TABLE() syntax was used. */ table_ref: relation_expr opt_alias_clause { $1->alias = $2; $$ = (Node *) $1; } ! | func_table opt_ordinality func_alias_clause { RangeFunction *n = makeNode(RangeFunction); n->lateral = false; ! n->ordinality = $2; ! n->is_table = (list_length($1) > 1); ! n->funccallnodes = linitial($1); n->alias = linitial($3); ! ! processTableFuncColdef(n, lsecond($3), @3, yyscanner); ! $$ = (Node *) n; } ! | LATERAL_P func_table opt_ordinality func_alias_clause { RangeFunction *n = makeNode(RangeFunction); n->lateral = true; ! n->ordinality = $3; ! n->is_table = (list_length($2) > 1); ! n->funccallnodes = linitial($2); n->alias = linitial($4); ! ! processTableFuncColdef(n, lsecond($4), @4, yyscanner); ! $$ = (Node *) n; } | select_with_parens opt_alias_clause *************** *** 9933,9941 **** relation_expr_opt_alias: relation_expr %prec UMINUS } ; ! func_table: func_expr_windowless { $$ = $1; } ! ; where_clause: WHERE a_expr { $$ = $2; } --- 9931,10019 ---- } ; ! /* ! * func_table returns a list whose first element is the list of FuncCalls, ! * and which has a second element only if TABLE() was used. This is to ! * avoid unnecessary duplication of productions above. ! * ! * func_table_single is a List, not a single node, because it might be ! * an expanded unnest() call. ! */ ! ! func_table: func_table_single { $$ = list_make1($1); } ! | TABLE '(' func_table_list ')' { $$ = list_make2($3,NIL); } ! ; ! ! func_table_list: func_table_def { $$ = $1; } ! | func_table_list ',' func_table_def { $$ = list_concat($1,$3); } ! ! func_table_def: func_table_single opt_col_def_list ! { ! if (list_length($1) == 1 && list_length($2) > 0) ! { ! FuncCall *n = (FuncCall *) linitial($1); ! ! if (!IsA(n, FuncCall)) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("a column definition list is not allowed for this expression"), ! parser_errposition(@2))); ! ! n->coldeflist = $2; ! } ! else if (list_length($2) > 0) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("a column definition list is not allowed for unnest with multiple arguments"), ! errhint("consider using separate unnest calls with one argument each"), ! parser_errposition(@2))); + $$ = $1; + } + + opt_col_def_list: AS '(' TableFuncElementList ')' { $$ = $3; } + | /*EMPTY*/ { $$ = NIL; } + ; + + func_table_single: func_expr_windowless + { + FuncCall *n = (FuncCall *) $1; + List *res = NIL; + + /* + * If we're looking at an unqualified UNNEST() call as a table + * function, replace it with a list of calls, one for each + * parameter. This handles the spec's UNNEST syntax while not + * interfering with the use of unnest() as a plain SRF in + * other contexts. Ugly, but effective. + * + * Note, strcmp not pg_strcasecmp, identifiers have already + * been casefolded. + */ + if (list_length(n->funcname) == 1 + && strcmp(strVal(linitial(n->funcname)),"unnest") == 0) + { + ListCell *lc; + + if (n->agg_order != NIL || n->func_variadic || n->agg_star || n->agg_distinct) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid syntax for unnest()"), + parser_errposition(@1))); + + foreach(lc, n->args) + { + res = lappend(res, makeFuncCall(SystemFuncName("unnest"), + list_make1(lfirst(lc)), + n->location)); + } + } + else + res = list_make1(n); + + $$ = res; + } + ; where_clause: WHERE a_expr { $$ = $2; } *************** *** 13355,13360 **** makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args, --- 13433,13489 ---- } /* + * Support the old column definition list syntax, either when TABLE() was + * not used, or when TABLE(func()) was used with only one function. + * + * This handles pushing the coldeflist down into the function call node. + */ + static void + processTableFuncColdef(RangeFunction *n, List *coldeflist, + int location, core_yyscan_t yyscanner) + { + /* + * coldeflist is allowed only for exactly one function; if more than one, + * then the coldeflist must be applied inside TABLE() not outside. + */ + if (coldeflist != NIL) + { + FuncCall *fn = linitial(n->funccallnodes); + + if (list_length(n->funccallnodes) > 1) + { + if (n->is_table) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a column definition list is not allowed for TABLE() with multiple functions"), + errhint("consider using column definition lists for individual functions inside TABLE()"), + parser_errposition(location))); + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a column definition list is not allowed for unnest with multiple arguments"), + errhint("consider using TABLE() with separate unnest calls with one argument each"), + parser_errposition(location))); + } + + if (!IsA(fn, FuncCall)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a column definition list is not allowed for this expression"), + parser_errposition(location))); + + if (fn->coldeflist != NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple column definition lists are not allowed for the same function"), + errhint("remove one of the definition lists"), + parser_errposition(location))); + + fn->coldeflist = coldeflist; + } + } + + /* * Merge the input and output parameters of a table function. */ static List * *** a/src/backend/parser/parse_clause.c --- b/src/backend/parser/parse_clause.c *************** *** 515,532 **** transformRangeSubselect(ParseState *pstate, RangeSubselect *r) static RangeTblEntry * transformRangeFunction(ParseState *pstate, RangeFunction *r) { ! Node *funcexpr; ! char *funcname; bool is_lateral; RangeTblEntry *rte; ! ! /* ! * Get function name for possible use as alias. We use the same ! * transformation rules as for a SELECT output expression. For a FuncCall ! * node, the result will be the function name, but it is possible for the ! * grammar to hand back other node types. ! */ ! funcname = FigureColname(r->funccallnode); /* * We make lateral_only names of this level visible, whether or not the --- 515,525 ---- static RangeTblEntry * transformRangeFunction(ParseState *pstate, RangeFunction *r) { ! List *funcexprs = NIL; ! List *funcnames = NIL; bool is_lateral; RangeTblEntry *rte; ! ListCell *lc; /* * We make lateral_only names of this level visible, whether or not the *************** *** 541,586 **** transformRangeFunction(ParseState *pstate, RangeFunction *r) pstate->p_lateral_active = true; /* ! * Transform the raw expression. */ ! funcexpr = transformExpr(pstate, r->funccallnode, EXPR_KIND_FROM_FUNCTION); pstate->p_lateral_active = false; /* ! * We must assign collations now so that we can fill funccolcollations. */ ! assign_expr_collations(pstate, funcexpr); /* * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if * there are any lateral cross-references in it. */ ! is_lateral = r->lateral || contain_vars_of_level(funcexpr, 0); /* * OK, build an RTE for the function. */ ! rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr, r, is_lateral, true); - /* - * If a coldeflist was supplied, ensure it defines a legal set of names - * (no duplicates) and datatypes (no pseudo-types, for instance). - * addRangeTableEntryForFunction looked up the type names but didn't check - * them further than that. - */ - if (r->coldeflist) - { - TupleDesc tupdesc; - - tupdesc = BuildDescFromLists(rte->eref->colnames, - rte->funccoltypes, - rte->funccoltypmods, - rte->funccolcollations); - CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false); - } - return rte; } --- 534,585 ---- pstate->p_lateral_active = true; /* ! * Transform the raw expressions. ! * ! * While transforming, get function names for possible use as alias and ! * column names. We use the same transformation rules as for a SELECT ! * output expression. For a FuncCall node, the result will be the function ! * name, but it is possible for the grammar to hand back other node types. ! * ! * Have to get this info now, because FigureColname only works on raw ! * parsetree. Actually deciding what to do with the names is left up to ! * addRangeTableEntryForFunction (which does not see the raw parsenodes). */ ! ! foreach(lc, r->funccallnodes) ! { ! Node *node = lfirst(lc); ! ! funcexprs = lappend(funcexprs, ! transformExpr(pstate, node, EXPR_KIND_FROM_FUNCTION)); ! ! funcnames = lappend(funcnames, makeString(FigureColname(node))); ! } pstate->p_lateral_active = false; /* ! * Assign collations now. ! * ! * This comment used to say that this was required to fill in ! * funccolcollations, but that does not appear to have been the case ! * (assignment of funccolcollations was since moved to the Expr handling ! * above) */ ! assign_list_collations(pstate, funcexprs); /* * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if * there are any lateral cross-references in it. */ ! is_lateral = r->lateral || contain_vars_of_level((Node *) funcexprs, 0); /* * OK, build an RTE for the function. */ ! rte = addRangeTableEntryForFunction(pstate, funcnames, funcexprs, r, is_lateral, true); return rte; } *** a/src/backend/parser/parse_expr.c --- b/src/backend/parser/parse_expr.c *************** *** 463,470 **** transformIndirection(ParseState *pstate, Node *basenode, List *indirection) newresult = ParseFuncOrColumn(pstate, list_make1(n), list_make1(result), ! NIL, NULL, false, false, false, ! NULL, true, location); if (newresult == NULL) unknown_attribute(pstate, result, strVal(n), location); result = newresult; --- 463,470 ---- newresult = ParseFuncOrColumn(pstate, list_make1(n), list_make1(result), ! location, ! NULL); if (newresult == NULL) unknown_attribute(pstate, result, strVal(n), location); result = newresult; *************** *** 631,638 **** transformColumnRef(ParseState *pstate, ColumnRef *cref) node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! NIL, NULL, false, false, false, ! NULL, true, cref->location); } break; } --- 631,637 ---- node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! cref->location, NULL); } break; } *************** *** 676,683 **** transformColumnRef(ParseState *pstate, ColumnRef *cref) node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! NIL, NULL, false, false, false, ! NULL, true, cref->location); } break; } --- 675,681 ---- node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! cref->location, NULL); } break; } *************** *** 734,741 **** transformColumnRef(ParseState *pstate, ColumnRef *cref) node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! NIL, NULL, false, false, false, ! NULL, true, cref->location); } break; } --- 732,738 ---- node = ParseFuncOrColumn(pstate, list_make1(makeString(colname)), list_make1(node), ! cref->location, NULL); } break; } *************** *** 1242,1248 **** transformFuncCall(ParseState *pstate, FuncCall *fn) { List *targs; ListCell *args; - Expr *tagg_filter; /* Transform the list of arguments ... */ targs = NIL; --- 1239,1244 ---- *************** *** 1252,1279 **** transformFuncCall(ParseState *pstate, FuncCall *fn) (Node *) lfirst(args))); } - /* - * Transform the aggregate filter using transformWhereClause(), to which - * FILTER is virtually identical... - */ - tagg_filter = NULL; - if (fn->agg_filter != NULL) - tagg_filter = (Expr *) - transformWhereClause(pstate, (Node *) fn->agg_filter, - EXPR_KIND_FILTER, "FILTER"); - /* ... and hand off to ParseFuncOrColumn */ return ParseFuncOrColumn(pstate, fn->funcname, targs, ! fn->agg_order, ! tagg_filter, ! fn->agg_star, ! fn->agg_distinct, ! fn->func_variadic, ! fn->over, ! false, ! fn->location); } static Node * --- 1248,1259 ---- (Node *) lfirst(args))); } /* ... and hand off to ParseFuncOrColumn */ return ParseFuncOrColumn(pstate, fn->funcname, targs, ! fn->location, ! fn); } static Node * *** a/src/backend/parser/parse_func.c --- b/src/backend/parser/parse_func.c *************** *** 15,20 **** --- 15,22 ---- #include "postgres.h" #include "access/htup_details.h" + #include "catalog/heap.h" + #include "catalog/pg_class.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "funcapi.h" *************** *** 22,27 **** --- 24,30 ---- #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "parser/parse_agg.h" + #include "parser/parse_clause.h" #include "parser/parse_coerce.h" #include "parser/parse_func.h" #include "parser/parse_relation.h" *************** *** 61,70 **** static Node *ParseComplexProjection(ParseState *pstate, char *funcname, */ Node * ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, ! List *agg_order, Expr *agg_filter, ! bool agg_star, bool agg_distinct, bool func_variadic, ! WindowDef *over, bool is_column, int location) { Oid rettype; Oid funcid; ListCell *l; --- 64,79 ---- */ Node * ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, ! int location, FuncCall *fn) { + List *agg_order = (fn ? fn->agg_order : NIL); + Expr *agg_filter = NULL; + bool agg_star = (fn ? fn->agg_star : false); + bool agg_distinct = (fn ? fn->agg_distinct : false); + bool func_variadic = (fn ? fn->func_variadic : false); + WindowDef *over = (fn ? fn->over : NULL); + List *coldeflist = (fn ? fn->coldeflist : NIL); + bool is_column = (fn == NULL); Oid rettype; Oid funcid; ListCell *l; *************** *** 98,103 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, --- 107,121 ---- parser_errposition(pstate, location))); /* + * Transform the aggregate filter using transformWhereClause(), to which + * FILTER is virtually identical... + */ + if (fn && fn->agg_filter != NULL) + agg_filter = (Expr *) + transformWhereClause(pstate, (Node *) fn->agg_filter, + EXPR_KIND_FILTER, "FILTER"); + + /* * Extract arg type info in preparation for function lookup. * * If any arguments are Param markers of type VOID, we discard them from *************** *** 414,419 **** ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, --- 432,499 ---- funcexpr->args = fargs; funcexpr->location = location; + /* + * If we're called in the FROM-clause, we might have a column + * definition list if we return RECORD. The grammar should prevent + * supplying a list in other contexts. Missing coldeflists are + * checked for in parse_relation.c + */ + if (coldeflist != NIL) + { + TypeFuncClass functypclass; + ListCell *col; + TupleDesc tupdesc; + + Assert(pstate->p_expr_kind == EXPR_KIND_FROM_FUNCTION); + + functypclass = get_func_result_type(funcid, NULL, NULL); + + if (functypclass != TYPEFUNC_RECORD) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a column definition list is only allowed for functions returning \"record\""), + parser_errposition(pstate, location))); + + /* + * Use the column definition list to form the + * funccolnames/funccoltypes/funccoltypmods/funccolcollations + * lists. + */ + foreach(col, coldeflist) + { + ColumnDef *n = (ColumnDef *) lfirst(col); + char *attrname; + Oid attrtype; + int32 attrtypmod; + Oid attrcollation; + + attrname = pstrdup(n->colname); + if (n->typeName->setof) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("column \"%s\" cannot be declared SETOF", + attrname), + parser_errposition(pstate, n->typeName->location))); + typenameTypeIdAndMod(pstate, n->typeName, &attrtype, &attrtypmod); + attrcollation = GetColumnDefCollation(pstate, n, attrtype); + funcexpr->funccolnames = lappend(funcexpr->funccolnames, makeString(attrname)); + funcexpr->funccoltypes = lappend_oid(funcexpr->funccoltypes, attrtype); + funcexpr->funccoltypmods = lappend_int(funcexpr->funccoltypmods, attrtypmod); + funcexpr->funccolcollations = lappend_oid(funcexpr->funccolcollations, + attrcollation); + } + + /* + * Ensure it defines a legal set of names (no duplicates) and + * datatypes (no pseudo-types, for instance). + */ + tupdesc = BuildDescFromLists(funcexpr->funccolnames, + funcexpr->funccoltypes, + funcexpr->funccoltypmods, + funcexpr->funccolcollations); + CheckAttributeNamesTypes(tupdesc, RELKIND_COMPOSITE_TYPE, false); + } + retval = (Node *) funcexpr; } else if (fdresult == FUNCDETAIL_AGGREGATE && !over) *** a/src/backend/parser/parse_relation.c --- b/src/backend/parser/parse_relation.c *************** *** 27,32 **** --- 27,33 ---- #include "parser/parsetree.h" #include "parser/parse_relation.h" #include "parser/parse_type.h" + #include "parser/parse_target.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/rel.h" *************** *** 43,49 **** static void expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, int location, bool include_dropped, List **colnames, List **colvars); ! static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, int rtindex, int sublevels_up, int location, bool include_dropped, List **colnames, List **colvars); --- 44,50 ---- int rtindex, int sublevels_up, int location, bool include_dropped, List **colnames, List **colvars); ! static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, int atts_done, int rtindex, int sublevels_up, int location, bool include_dropped, List **colnames, List **colvars); *************** *** 891,912 **** buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, bool ordinali * when the function returns a scalar type (not composite or RECORD). * * funcexpr: transformed expression tree for the function call ! * 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); --- 892,921 ---- * when the function returns a scalar type (not composite or RECORD). * * funcexpr: transformed expression tree for the function call ! * funcname: function name ! * prefer_funcname: prefer to use funcname rather than eref->aliasname * 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 column alias is given, no column name ! * is provided by the function, and prefer_funcname is false. (This makes FROM ! * func() AS foo use "foo" as the column name as well as the table alias.) ! * ! * prefer_funcname is true for the TABLE(func()) case, where calling the ! * resulting column "table" would be silly, and using the function name as ! * eref->aliasname would be inconsistent with TABLE(func1(),func2()). This ! * isn't ideal, but seems to be the least surprising behaviour. * * 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, bool prefer_funcname, Alias *alias, Alias *eref, bool ordinality) { Assert(eref->colnames == NIL); *************** *** 938,944 **** buildScalarFunctionAlias(Node *funcexpr, char *funcname, * caller (which is not necessarily the function name!) */ if (!pname) ! pname = eref->aliasname; eref->colnames = list_make1(makeString(pname)); } --- 947,953 ---- * caller (which is not necessarily the function name!) */ if (!pname) ! pname = (prefer_funcname ? funcname : eref->aliasname); eref->colnames = list_make1(makeString(pname)); } *************** *** 1215,1222 **** addRangeTableEntryForSubquery(ParseState *pstate, */ RangeTblEntry * addRangeTableEntryForFunction(ParseState *pstate, ! char *funcname, ! Node *funcexpr, RangeFunction *rangefunc, bool lateral, bool inFromCl) --- 1224,1231 ---- */ RangeTblEntry * addRangeTableEntryForFunction(ParseState *pstate, ! List *funcnames, ! List *funcexprs, RangeFunction *rangefunc, bool lateral, bool inFromCl) *************** *** 1226,1272 **** addRangeTableEntryForFunction(ParseState *pstate, Oid funcrettype; TupleDesc tupdesc; Alias *alias = rangefunc->alias; - List *coldeflist = rangefunc->coldeflist; Alias *eref; rte->rtekind = RTE_FUNCTION; rte->relid = InvalidOid; rte->subquery = NULL; ! rte->funcexpr = funcexpr; ! rte->funccoltypes = NIL; ! rte->funccoltypmods = NIL; ! rte->funccolcollations = NIL; rte->alias = alias; ! eref = makeAlias(alias ? alias->aliasname : funcname, NIL); rte->eref = eref; /* * Now determine if the function returns a simple or composite type. */ ! functypclass = get_expr_result_type(funcexpr, ! &funcrettype, ! &tupdesc); ! ! /* ! * A coldeflist is required if the function returns RECORD and hasn't got ! * a predetermined record type, and is prohibited otherwise. ! */ ! if (coldeflist != NIL) { ! if (functypclass != TYPEFUNC_RECORD) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("a column definition list is only allowed for functions returning \"record\""), ! parser_errposition(pstate, exprLocation(funcexpr)))); } else { ! if (functypclass == TYPEFUNC_RECORD) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("a column definition list is required for functions returning \"record\""), ! parser_errposition(pstate, exprLocation(funcexpr)))); } if (functypclass == TYPEFUNC_COMPOSITE) --- 1235,1380 ---- Oid funcrettype; TupleDesc tupdesc; Alias *alias = rangefunc->alias; Alias *eref; + char *aliasname; + Oid *funcrettypes = NULL; + TupleDesc *functupdescs = NULL; + int nfuncs = list_length(funcexprs); + ListCell *lc, *lc2; + int i; rte->rtekind = RTE_FUNCTION; rte->relid = InvalidOid; rte->subquery = NULL; ! rte->funcexprs = funcexprs; rte->alias = alias; ! /* ! * Choose the RTE alias name. ! * ! * We punt to "table" if the list results from explicit TABLE() syntax ! * regardless of number of functions. Otherwise, use the first function, ! * since either there is only one, or it was an unnest() which got ! * expanded. We don't currently need to record this fact in the ! * transformed node, since deparse always emits an alias for table ! * functions, and ! * ... FROM unnest(a,b) ! * and ! * ... FROM TABLE(unnest(a),unnest(b)) AS "unnest" ! * are equivalent enough for our purposes. ! */ ! ! if (alias) ! aliasname = alias->aliasname; ! else if (rangefunc->is_table) ! aliasname = "table"; ! else ! aliasname = strVal(linitial(funcnames)); ! ! eref = makeAlias(aliasname, NIL); rte->eref = eref; /* * Now determine if the function returns a simple or composite type. + * + * If there's more than one function, the result must be composite, + * and we have to produce a merged tupdesc. */ ! if (nfuncs == 1) { ! functypclass = get_expr_result_type(linitial(funcexprs), ! &funcrettype, ! &tupdesc); ! ! /* ! * TYPEFUNC_RECORD is only possible here if a column definition list ! * was not supplied. ! */ ! if (functypclass == TYPEFUNC_RECORD) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("a column definition list is required for functions returning \"record\""), ! parser_errposition(pstate, exprLocation(linitial(funcexprs))))); } else { ! /* ! * Produce a flattened TupleDesc with all the constituent columns from ! * all functions. We're only going to use this for assigning aliases, ! * so we don't need collations and so on. ! * ! * This would be less painful if there was a reasonable way to insert ! * dropped columns into a tupdesc. ! */ ! ! funcrettypes = palloc(nfuncs * sizeof(Oid)); ! functupdescs = palloc(nfuncs * sizeof(TupleDesc)); ! ! i = 0; ! forboth(lc, funcexprs, lc2, funcnames) ! { ! functypclass = get_expr_result_type(lfirst(lc), ! &funcrettypes[i], ! &functupdescs[i]); ! ! switch (functypclass) ! { ! case TYPEFUNC_RECORD: ! /* ! * Only gets here if no column definition list was supplied for ! * a function returning an unspecified RECORD. ! */ ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("a column definition list is required for functions returning \"record\""), ! parser_errposition(pstate, exprLocation(lfirst(lc))))); ! ! case TYPEFUNC_SCALAR: ! { ! FuncExpr *funcexpr = lfirst(lc); ! char *pname = NULL; ! ! /* ! * Function might have its own idea what the result ! * column name should be. Prefer that (since ! * buildScalarFunctionAlias does too) ! */ ! if (IsA(funcexpr, FuncExpr)) ! pname = get_func_result_name(funcexpr->funcid); ! ! /* ! * If not, use the function name as the column name. ! */ ! if (!pname) ! pname = strVal(lfirst(lc2)); ! ! functupdescs[i] = CreateTemplateTupleDesc(1, false); ! TupleDescInitEntry(functupdescs[i], ! (AttrNumber) 1, ! pname, ! funcrettypes[i], ! -1, ! 0); ! break; ! } ! ! case TYPEFUNC_COMPOSITE: ! break; ! ! default: ! ereport(ERROR, ! (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("function \"%s\" in FROM has unsupported return type %s", ! strVal(lfirst(lc2)), format_type_be(funcrettype)), ! parser_errposition(pstate, exprLocation(lfirst(lc))))); ! } ! ! ++i; ! } ! ! functypclass = TYPEFUNC_COMPOSITE; ! funcrettype = RECORDOID; ! tupdesc = CreateTupleDescCopyMany(functupdescs, nfuncs); } if (functypclass == TYPEFUNC_COMPOSITE) *************** *** 1279,1330 **** addRangeTableEntryForFunction(ParseState *pstate, 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. ! */ ! foreach(col, coldeflist) ! { ! ColumnDef *n = (ColumnDef *) lfirst(col); ! char *attrname; ! Oid attrtype; ! int32 attrtypmod; ! Oid attrcollation; ! ! attrname = pstrdup(n->colname); ! if (n->typeName->setof) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_TABLE_DEFINITION), ! errmsg("column \"%s\" cannot be declared SETOF", ! attrname), ! parser_errposition(pstate, n->typeName->location))); ! typenameTypeIdAndMod(pstate, n->typeName, &attrtype, &attrtypmod); ! attrcollation = GetColumnDefCollation(pstate, n, attrtype); ! eref->colnames = lappend(eref->colnames, makeString(attrname)); ! rte->funccoltypes = lappend_oid(rte->funccoltypes, attrtype); ! rte->funccoltypmods = lappend_int(rte->funccoltypmods, attrtypmod); ! rte->funccolcollations = lappend_oid(rte->funccolcollations, ! attrcollation); ! } } else ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("function \"%s\" in FROM has unsupported return type %s", ! funcname, format_type_be(funcrettype)), ! parser_errposition(pstate, exprLocation(funcexpr)))); /* * Set flags and access permissions. --- 1387,1402 ---- else if (functypclass == TYPEFUNC_SCALAR) { /* Base data type, i.e. scalar */ ! buildScalarFunctionAlias(linitial(funcexprs), ! strVal(linitial(funcnames)), rangefunc->is_table, ! alias, eref, rangefunc->ordinality); } else ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("function \"%s\" in FROM has unsupported return type %s", ! strVal(linitial(funcnames)), format_type_be(funcrettype)), ! parser_errposition(pstate, exprLocation(linitial(funcexprs))))); /* * Set flags and access permissions. *************** *** 1762,1853 **** 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, ! &tupdesc); ! if (functypclass == TYPEFUNC_COMPOSITE) ! { ! /* 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) { ! /* Base data type, i.e. scalar */ ! if (colnames) ! *colnames = lappend(*colnames, ! linitial(rte->eref->colnames)); ! ! if (colvars) { ! Var *varnode; ! ! varnode = makeVar(rtindex, 1, ! funcrettype, -1, ! exprCollation(rte->funcexpr), ! sublevels_up); ! varnode->location = location; ! ! *colvars = lappend(*colvars, varnode); } ! ! ordinality_attno = 2; ! } ! else if (functypclass == TYPEFUNC_RECORD) ! { ! if (colnames) ! *colnames = copyObject(rte->eref->colnames); ! if (colvars) { ! ListCell *l1; ! ListCell *l2; ! ListCell *l3; ! int attnum = 0; ! ! forthree(l1, rte->funccoltypes, ! l2, rte->funccoltypmods, ! l3, rte->funccolcollations) { - Oid attrtype = lfirst_oid(l1); - int32 attrtypmod = lfirst_int(l2); - Oid attrcollation = lfirst_oid(l3); Var *varnode; ! attnum++; ! varnode = makeVar(rtindex, ! attnum, ! attrtype, ! attrtypmod, ! attrcollation, sublevels_up); varnode->location = location; *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)); --- 1834,1902 ---- TypeFuncClass functypclass; Oid funcrettype; TupleDesc tupdesc; ! int atts_done = 0; ! ListCell *lc; ! /* ! * Loop over functions to assemble result. ! * ! * atts_done is the number of attributes (including dropped ! * cols) constructed so far; it's used as an index offset in ! * various places. ! */ ! foreach(lc, rte->funcexprs) { ! functypclass = get_expr_result_type(lfirst(lc), ! &funcrettype, ! &tupdesc); ! if (functypclass == TYPEFUNC_COMPOSITE) { ! /* 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, atts_done, ! rtindex, sublevels_up, location, ! include_dropped, colnames, colvars); ! ! atts_done += tupdesc->natts; } ! else if (functypclass == TYPEFUNC_SCALAR) { ! /* Base data type, i.e. scalar */ ! if (colnames) ! *colnames = lappend(*colnames, ! list_nth(rte->eref->colnames, atts_done)); ! ! if (colvars) { Var *varnode; ! varnode = makeVar(rtindex, atts_done + 1, ! funcrettype, -1, ! exprCollation(lfirst(lc)), sublevels_up); varnode->location = location; + *colvars = lappend(*colvars, varnode); } ! ++atts_done; ! } ! 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(atts_done > 0); if (colnames) *colnames = lappend(*colnames, llast(rte->eref->colnames)); *************** *** 1855,1861 **** expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, if (colvars) { Var *varnode = makeVar(rtindex, ! ordinality_attno, INT8OID, -1, InvalidOid, --- 1904,1910 ---- if (colvars) { Var *varnode = makeVar(rtindex, ! atts_done + 1, INT8OID, -1, InvalidOid, *************** *** 2030,2036 **** expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, /* Get the tupledesc and turn it over to expandTupleDesc */ rel = relation_open(relid, AccessShareLock); ! expandTupleDesc(rel->rd_att, eref, rtindex, sublevels_up, location, include_dropped, colnames, colvars); relation_close(rel, AccessShareLock); --- 2079,2085 ---- /* Get the tupledesc and turn it over to expandTupleDesc */ rel = relation_open(relid, AccessShareLock); ! expandTupleDesc(rel->rd_att, eref, 0, rtindex, sublevels_up, location, include_dropped, colnames, colvars); relation_close(rel, AccessShareLock); *************** *** 2039,2055 **** 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, int rtindex, int sublevels_up, int location, bool include_dropped, List **colnames, List **colvars) { int maxattrs = tupdesc->natts; ! int numaliases = list_length(eref->colnames); int varattno; for (varattno = 0; varattno < maxattrs; varattno++) --- 2088,2107 ---- /* * 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) ! * ! * atts_done offsets the resulting column numbers, for the function case when ! * we merge multiple tupdescs into one list. */ static void ! expandTupleDesc(TupleDesc tupdesc, Alias *eref, int atts_done, int rtindex, int sublevels_up, int location, bool include_dropped, List **colnames, List **colvars) { int maxattrs = tupdesc->natts; ! int numaliases = list_length(eref->colnames) - atts_done; int varattno; for (varattno = 0; varattno < maxattrs; varattno++) *************** *** 2080,2086 **** expandTupleDesc(TupleDesc tupdesc, Alias *eref, char *label; if (varattno < numaliases) ! label = strVal(list_nth(eref->colnames, varattno)); else label = NameStr(attr->attname); *colnames = lappend(*colnames, makeString(pstrdup(label))); --- 2132,2138 ---- char *label; if (varattno < numaliases) ! label = strVal(list_nth(eref->colnames, varattno + atts_done)); else label = NameStr(attr->attname); *colnames = lappend(*colnames, makeString(pstrdup(label))); *************** *** 2090,2096 **** expandTupleDesc(TupleDesc tupdesc, Alias *eref, { Var *varnode; ! varnode = makeVar(rtindex, attr->attnum, attr->atttypid, attr->atttypmod, attr->attcollation, sublevels_up); --- 2142,2148 ---- { Var *varnode; ! varnode = makeVar(rtindex, attr->attnum + atts_done, attr->atttypid, attr->atttypmod, attr->attcollation, sublevels_up); *************** *** 2260,2270 **** get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, TypeFuncClass functypclass; 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)) --- 2312,2323 ---- TypeFuncClass functypclass; Oid funcrettype; TupleDesc tupdesc; + ListCell *lc; + int atts_done = 0; /* ! * 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)) *************** *** 2275,2335 **** get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, break; } ! functypclass = get_expr_result_type(rte->funcexpr, ! &funcrettype, ! &tupdesc); ! ! if (functypclass == TYPEFUNC_COMPOSITE) { ! /* Composite data type, e.g. a table's row type */ ! Form_pg_attribute att_tup; ! Assert(tupdesc); ! /* this is probably a can't-happen case */ ! if (attnum < 1 || attnum > tupdesc->natts) ! ereport(ERROR, ! (errcode(ERRCODE_UNDEFINED_COLUMN), ! errmsg("column %d of relation \"%s\" does not exist", ! attnum, ! rte->eref->aliasname))); ! att_tup = tupdesc->attrs[attnum - 1]; ! /* ! * If dropped column, pretend it ain't there. See notes ! * in scanRTEForColumn. ! */ ! if (att_tup->attisdropped) ! ereport(ERROR, ! (errcode(ERRCODE_UNDEFINED_COLUMN), ! errmsg("column \"%s\" of relation \"%s\" does not exist", ! NameStr(att_tup->attname), ! rte->eref->aliasname))); ! *vartype = att_tup->atttypid; ! *vartypmod = att_tup->atttypmod; ! *varcollid = att_tup->attcollation; ! } ! else if (functypclass == TYPEFUNC_SCALAR) ! { ! Assert(attnum == 1); ! /* Base data type, i.e. scalar */ ! *vartype = funcrettype; ! *vartypmod = -1; ! *varcollid = exprCollation(rte->funcexpr); ! } ! else if (functypclass == TYPEFUNC_RECORD) ! { ! *vartype = list_nth_oid(rte->funccoltypes, attnum - 1); ! *vartypmod = list_nth_int(rte->funccoltypmods, attnum - 1); ! *varcollid = list_nth_oid(rte->funccolcollations, attnum - 1); ! } ! else ! { ! /* addRangeTableEntryForFunction should've caught this */ ! elog(ERROR, "function in FROM has unsupported return type"); } } break; case RTE_VALUES: --- 2328,2399 ---- break; } ! /* ! * Loop over funcs until we find the one that covers ! * the requested column. ! */ ! foreach(lc, rte->funcexprs) { ! functypclass = get_expr_result_type(lfirst(lc), ! &funcrettype, ! &tupdesc); ! if (functypclass == TYPEFUNC_COMPOSITE) ! { ! /* Composite data type, e.g. a table's row type */ ! Form_pg_attribute att_tup; ! Assert(tupdesc); ! if (attnum > atts_done ! && attnum <= atts_done + tupdesc->natts) ! { ! att_tup = tupdesc->attrs[attnum - atts_done - 1]; ! ! /* ! * If dropped column, pretend it ain't there. See notes ! * in scanRTEForColumn. ! */ ! if (att_tup->attisdropped) ! ereport(ERROR, ! (errcode(ERRCODE_UNDEFINED_COLUMN), ! errmsg("column \"%s\" of relation \"%s\" does not exist", ! NameStr(att_tup->attname), ! rte->eref->aliasname))); ! *vartype = att_tup->atttypid; ! *vartypmod = att_tup->atttypmod; ! *varcollid = att_tup->attcollation; ! return; ! } ! atts_done += tupdesc->natts; ! } ! else if (functypclass == TYPEFUNC_SCALAR) ! { ! if (attnum == atts_done + 1) ! { ! /* Base data type, i.e. scalar */ ! *vartype = funcrettype; ! *vartypmod = -1; ! *varcollid = exprCollation(lfirst(lc)); ! return; ! } ! ++atts_done; ! } ! else ! { ! /* addRangeTableEntryForFunction should've caught this */ ! elog(ERROR, "function in FROM has unsupported return type"); ! } } + + /* this is probably a can't-happen case */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column %d of relation \"%s\" does not exist", + attnum, + rte->eref->aliasname))); } break; case RTE_VALUES: *************** *** 2435,2442 **** get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) case RTE_FUNCTION: { /* Function RTE */ ! Oid funcrettype = exprType(rte->funcexpr); ! Oid funcrelid = typeidTypeRelid(funcrettype); /* * if ordinality, then a reference to the last column --- 2499,2511 ---- case RTE_FUNCTION: { /* Function RTE */ ! TypeFuncClass functypclass; ! Oid funcrettype; ! TupleDesc tupdesc; ! ListCell *lc; ! int atts_done = 0; ! ! result = false; /* * if ordinality, then a reference to the last column *************** *** 2445,2479 **** get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) */ 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 ! * ! * Same as ordinary relation RTE ! */ ! HeapTuple tp; ! Form_pg_attribute att_tup; ! ! tp = SearchSysCache2(ATTNUM, ! ObjectIdGetDatum(funcrelid), ! Int16GetDatum(attnum)); ! if (!HeapTupleIsValid(tp)) /* shouldn't happen */ ! elog(ERROR, "cache lookup failed for attribute %d of relation %u", ! attnum, funcrelid); ! att_tup = (Form_pg_attribute) GETSTRUCT(tp); ! result = att_tup->attisdropped; ! ReleaseSysCache(tp); ! } ! else ! { ! /* ! * Must be a base data type, i.e. scalar ! */ ! result = false; } } break; --- 2514,2565 ---- */ if (rte->funcordinality && attnum == list_length(rte->eref->colnames)) + break; + + /* + * Loop over funcs until we find the one that covers + * the requested column. + */ + foreach(lc, rte->funcexprs) { ! functypclass = get_expr_result_type(lfirst(lc), ! &funcrettype, ! &tupdesc); ! ! if (functypclass == TYPEFUNC_COMPOSITE) ! { ! /* Composite data type, e.g. a table's row type */ ! Form_pg_attribute att_tup; ! ! Assert(tupdesc); ! ! if (attnum > atts_done ! && attnum <= atts_done + tupdesc->natts) ! { ! att_tup = tupdesc->attrs[attnum - atts_done - 1]; ! ! result = att_tup->attisdropped; ! break; ! } ! ! atts_done += tupdesc->natts; ! } ! else if (functypclass == TYPEFUNC_SCALAR) ! { ! if (attnum == atts_done + 1) ! { ! /* Base data type, i.e. scalar */ ! result = false; ! break; ! } ! ! ++atts_done; ! } ! else ! { ! /* addRangeTableEntryForFunction should've caught this */ ! elog(ERROR, "function in FROM has unsupported return type"); ! } } } break; *** a/src/backend/rewrite/rewriteHandler.c --- b/src/backend/rewrite/rewriteHandler.c *************** *** 388,394 **** rewriteRuleAction(Query *parsetree, { case RTE_FUNCTION: sub_action->hasSubLinks = ! checkExprHasSubLink(rte->funcexpr); break; case RTE_VALUES: sub_action->hasSubLinks = --- 388,394 ---- { case RTE_FUNCTION: sub_action->hasSubLinks = ! checkExprHasSubLink((Node *) rte->funcexprs); break; case RTE_VALUES: sub_action->hasSubLinks = *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** *** 27,32 **** --- 27,33 ---- #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" #include "catalog/pg_language.h" + #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" *************** *** 386,392 **** static void get_from_clause_item(Node *jtnode, Query *query, deparse_context *context); static void get_column_alias_list(deparse_columns *colinfo, deparse_context *context); ! static void get_from_clause_coldeflist(deparse_columns *colinfo, List *types, List *typmods, List *collations, deparse_context *context); static void get_opclass_name(Oid opclass, Oid actual_datatype, --- 387,393 ---- deparse_context *context); static void get_column_alias_list(deparse_columns *colinfo, deparse_context *context); ! static void get_from_clause_coldeflist(deparse_columns *colinfo, List *names, List *types, List *typmods, List *collations, deparse_context *context); static void get_opclass_name(Oid opclass, Oid actual_datatype, *************** *** 7979,7984 **** get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) --- 7980,7986 ---- char *refname = get_rtable_name(varno, context); deparse_columns *colinfo = deparse_columns_fetch(varno, dpns); bool printalias; + FuncExpr *func_coldef = NULL; if (rte->lateral) appendStringInfoString(buf, "LATERAL "); *************** *** 8003,8009 **** get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) break; case RTE_FUNCTION: /* Function RTE */ ! get_rule_expr(rte->funcexpr, context, true); if (rte->funcordinality) appendStringInfoString(buf, " WITH ORDINALITY"); break; --- 8005,8100 ---- break; case RTE_FUNCTION: /* Function RTE */ ! ! /* ! * The simple case of omitting TABLE() for one function only ! * works if either there's no ordinality, or the function does ! * not need a column definition list. ! */ ! if (list_length(rte->funcexprs) == 1 ! && (!rte->funcordinality ! || !IsA(linitial(rte->funcexprs), FuncExpr) ! || ((FuncExpr *) linitial(rte->funcexprs))->funccoltypes == NIL)) ! { ! get_rule_expr(linitial(rte->funcexprs), context, true); ! func_coldef = linitial(rte->funcexprs); ! } ! else ! { ! ListCell *lc = list_head(rte->funcexprs); ! Oid unnest_oid = InvalidOid; ! ! /* ! * If all the function calls in the list are to ! * pg_catalog.unnest, then collapse the list back down ! * to UNNEST(args). Since there's currently only one ! * unnest, we check by oid after the first one. ! */ ! ! if (IsA(lfirst(lc), FuncExpr)) ! { ! unnest_oid = ((FuncExpr *) lfirst(lc))->funcid; ! ! if (get_func_namespace(unnest_oid) != PG_CATALOG_NAMESPACE ! || strcmp(get_func_name(unnest_oid),"unnest") != 0) ! unnest_oid = InvalidOid; ! } ! ! while (OidIsValid(unnest_oid)) ! { ! FuncExpr *fn; ! ! lc = lnext(lc); ! if (!lc) ! break; ! ! fn = lfirst(lc); ! if (!IsA(fn, FuncExpr) ! || fn->funcid != unnest_oid ! || fn->funccoltypes != NIL) ! unnest_oid = InvalidOid; ! } ! ! if (OidIsValid(unnest_oid)) ! { ! List *allargs = NIL; ! ! foreach(lc, rte->funcexprs) ! { ! List *args = ((FuncExpr *) lfirst(lc))->args; ! allargs = list_concat(allargs, list_copy(args)); ! } ! ! appendStringInfoString(buf, "unnest("); ! get_rule_expr((Node *) allargs, context, true); ! } ! else ! { ! appendStringInfoString(buf, "TABLE("); ! ! foreach(lc, rte->funcexprs) ! { ! FuncExpr *fn = lfirst(lc); ! ! get_rule_expr((Node *) fn, context, true); ! ! if (IsA(fn, FuncExpr) && fn->funccoltypes != NIL) ! { ! /* Function returning RECORD, reconstruct the columndefs */ ! appendStringInfoString(buf, " AS "); ! get_from_clause_coldeflist(NULL, ! fn->funccolnames, ! fn->funccoltypes, ! fn->funccoltypmods, ! fn->funccolcollations, ! context); ! } ! ! appendStringInfoString(buf, ", "); ! } ! } ! appendStringInfoChar(buf, ')'); ! } if (rte->funcordinality) appendStringInfoString(buf, " WITH ORDINALITY"); break; *************** *** 8065,8077 **** get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) appendStringInfo(buf, " %s", quote_identifier(refname)); /* Print the column definitions or aliases, if needed */ ! if (rte->rtekind == RTE_FUNCTION && rte->funccoltypes != NIL) { /* Function returning RECORD, reconstruct the columndefs */ get_from_clause_coldeflist(colinfo, ! rte->funccoltypes, ! rte->funccoltypmods, ! rte->funccolcollations, context); } else --- 8156,8169 ---- appendStringInfo(buf, " %s", quote_identifier(refname)); /* Print the column definitions or aliases, if needed */ ! if (rte->rtekind == RTE_FUNCTION && func_coldef && func_coldef->funccoltypes != NIL) { /* Function returning RECORD, reconstruct the columndefs */ get_from_clause_coldeflist(colinfo, ! NIL, ! func_coldef->funccoltypes, ! func_coldef->funccoltypmods, ! func_coldef->funccolcollations, context); } else *************** *** 8220,8226 **** get_column_alias_list(deparse_columns *colinfo, deparse_context *context) * responsible for ensuring that an alias or AS is present before it. */ static void ! get_from_clause_coldeflist(deparse_columns *colinfo, List *types, List *typmods, List *collations, deparse_context *context) { --- 8312,8318 ---- * responsible for ensuring that an alias or AS is present before it. */ static void ! get_from_clause_coldeflist(deparse_columns *colinfo, List *names, List *types, List *typmods, List *collations, deparse_context *context) { *************** *** 8228,8233 **** get_from_clause_coldeflist(deparse_columns *colinfo, --- 8320,8326 ---- ListCell *l1; ListCell *l2; ListCell *l3; + ListCell *l4 = (names ? list_head(names) : NULL); int i; appendStringInfoChar(buf, '('); *************** *** 8235,8241 **** get_from_clause_coldeflist(deparse_columns *colinfo, i = 0; forthree(l1, types, l2, typmods, l3, collations) { ! char *attname = colinfo->colnames[i]; Oid atttypid = lfirst_oid(l1); int32 atttypmod = lfirst_int(l2); Oid attcollation = lfirst_oid(l3); --- 8328,8334 ---- i = 0; forthree(l1, types, l2, typmods, l3, collations) { ! char *attname = (colinfo ? colinfo->colnames[i] : strVal(lfirst(l4))); Oid atttypid = lfirst_oid(l1); int32 atttypmod = lfirst_int(l2); Oid attcollation = lfirst_oid(l3); *************** *** 8251,8256 **** get_from_clause_coldeflist(deparse_columns *colinfo, --- 8344,8352 ---- attcollation != get_typcollation(atttypid)) appendStringInfo(buf, " COLLATE %s", generate_collation_name(attcollation)); + + if (!colinfo) + l4 = lnext(l4); i++; } *** a/src/backend/utils/fmgr/funcapi.c --- b/src/backend/utils/fmgr/funcapi.c *************** *** 375,381 **** internal_get_result_type(Oid funcid, case TYPEFUNC_SCALAR: break; case TYPEFUNC_RECORD: ! /* We must get the tupledesc from call context */ if (rsinfo && IsA(rsinfo, ReturnSetInfo) && rsinfo->expectedDesc != NULL) { --- 375,385 ---- case TYPEFUNC_SCALAR: break; case TYPEFUNC_RECORD: ! /* ! * We prefer to get the tupledesc from the call context since ! * that is already built. If there isn't one, we try and cons it ! * up from the funccol* fields of FuncExpr. ! */ if (rsinfo && IsA(rsinfo, ReturnSetInfo) && rsinfo->expectedDesc != NULL) { *************** *** 384,389 **** internal_get_result_type(Oid funcid, --- 388,409 ---- *resultTupleDesc = rsinfo->expectedDesc; /* Assume no polymorphic columns here, either */ } + else if (call_expr && IsA(call_expr, FuncExpr)) + { + FuncExpr *func = (FuncExpr *) call_expr; + + if (func->funccoltypes != NIL) + { + tupdesc = BuildDescFromLists(func->funccolnames, + func->funccoltypes, + func->funccoltypmods, + func->funccolcollations); + result = TYPEFUNC_COMPOSITE; + if (resultTupleDesc) + *resultTupleDesc = tupdesc; + /* Assume no polymorphic columns here, either */ + } + } break; default: break; *** a/src/include/access/tupdesc.h --- b/src/include/access/tupdesc.h *************** *** 88,93 **** extern TupleDesc CreateTupleDesc(int natts, bool hasoid, --- 88,94 ---- extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc); extern TupleDesc CreateTupleDescCopyExtend(TupleDesc tupdesc, int moreatts); + extern TupleDesc CreateTupleDescCopyMany(TupleDesc *tupdescs, int numtupdescs); extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc); *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 1396,1418 **** typedef struct SubqueryScanState * * 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 * ---------------- */ typedef struct FunctionScanState { 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; /* ---------------- --- 1396,1421 ---- * * eflags node's capability flags * ordinal column value for WITH ORDINALITY + * rowcounts number of result rows for each func, when known * scan_tupdesc scan tuple descriptor ! * func_tupdescs function tuple descriptors ! * func_slots function result slots, or null ! * tuplestorestates private states of tuplestore.c ! * funcexprs state for function expressions being evaluated * ---------------- */ typedef struct FunctionScanState { ScanState ss; /* its first field is NodeTag */ int eflags; + bool ordinality; int64 ordinal; + int64 *rowcounts; TupleDesc scan_tupdesc; ! TupleDesc *func_tupdescs; ! TupleTableSlot **func_slots; ! Tuplestorestate **tuplestorestates; ! List *funcexprs; } FunctionScanState; /* ---------------- *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 304,309 **** typedef struct FuncCall --- 304,310 ---- bool agg_distinct; /* arguments were labeled DISTINCT */ bool func_variadic; /* last argument was labeled VARIADIC */ struct WindowDef *over; /* OVER clause, if any */ + List *coldeflist; /* column definition list for record funcs */ int location; /* token location, or -1 if unknown */ } FuncCall; *************** *** 466,481 **** typedef struct RangeSubselect /* * RangeFunction - function call appearing in a FROM clause */ typedef struct RangeFunction { NodeTag type; bool lateral; /* does it have LATERAL prefix? */ bool ordinality; /* does it have WITH ORDINALITY suffix? */ ! Node *funccallnode; /* untransformed function call tree */ Alias *alias; /* table alias & optional column aliases */ - List *coldeflist; /* list of ColumnDef nodes to describe result - * of function returning RECORD */ } RangeFunction; /* --- 467,484 ---- /* * RangeFunction - function call appearing in a FROM clause + * + * funccallnodes is a list because we use this to represent the construct + * TABLE(func1(...),func2(...),...) AS ... */ typedef struct RangeFunction { NodeTag type; bool lateral; /* does it have LATERAL prefix? */ bool ordinality; /* does it have WITH ORDINALITY suffix? */ ! bool is_table; /* result of TABLE() syntax */ ! List *funccallnodes; /* untransformed function call trees */ Alias *alias; /* table alias & optional column aliases */ } RangeFunction; /* *************** *** 653,664 **** typedef struct XmlSerialize * 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 --- 656,667 ---- * 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 a 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 *************** *** 757,776 **** typedef struct RangeTblEntry /* * 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? */ /* --- 760,775 ---- /* * Fields valid for a function RTE (else NULL): * ! * If the function returns an otherwise-unspecified RECORD, we used to ! * store type lists here; we now push those down to the individual ! * FuncExpr nodes, so that we can handle multiple RECORD functions and/or ! * RECORD functions with ordinality. * ! * So, in all cases the result column types can be determined from the ! * funcexprs, with the ordinality column, if present, appended to the ! * end. */ ! List *funcexprs; /* expression trees for func calls */ bool funcordinality; /* is this called WITH ORDINALITY? */ /* *** a/src/include/nodes/plannodes.h --- b/src/include/nodes/plannodes.h *************** *** 424,435 **** typedef struct SubqueryScan typedef struct FunctionScan { Scan scan; ! Node *funcexpr; /* expression tree for func call */ bool funcordinality; /* WITH ORDINALITY */ ! List *funccolnames; /* output column names (string Value nodes) */ ! List *funccoltypes; /* OID list of column type OIDs */ ! List *funccoltypmods; /* integer list of column typmods */ ! List *funccolcollations; /* OID list of column collation OIDs */ } FunctionScan; /* ---------------- --- 424,434 ---- typedef struct FunctionScan { Scan scan; ! List *funcexprs; /* expression trees for func calls */ ! List *funccolnames; /* result column names */ bool funcordinality; /* WITH ORDINALITY */ ! /* keep this last due to nonstandard output */ ! List *funcparams; /* Bitmapsets for params used by each func */ } FunctionScan; /* ---------------- *** a/src/include/nodes/primnodes.h --- b/src/include/nodes/primnodes.h *************** *** 353,358 **** typedef struct FuncExpr --- 353,363 ---- Oid funccollid; /* OID of collation of result */ Oid inputcollid; /* OID of collation that function should use */ List *args; /* arguments to the function */ + /* These func* fields are used only for table functions returning RECORD */ + List *funccolnames; /* result colnames for RECORD rangefuncs */ + List *funccoltypes; /* result coltypes for RECORD rangefuncs */ + List *funccoltypmods; /* result coltypmods for RECORD rangefuncs */ + List *funccolcollations; /* result colcollations for RECORD rangefuncs */ int location; /* token location, or -1 if unknown */ } FuncExpr; *** a/src/include/optimizer/pathnode.h --- b/src/include/optimizer/pathnode.h *************** *** 70,76 **** extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel, extern Path *create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel, List *pathkeys, Relids required_outer); extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel, ! Relids required_outer); extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer); extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel, --- 70,76 ---- extern Path *create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel, List *pathkeys, Relids required_outer); extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel, ! List *pathkeys, Relids required_outer); extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer); extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel, *** a/src/include/optimizer/paths.h --- b/src/include/optimizer/paths.h *************** *** 165,170 **** extern Path *get_cheapest_fractional_path_for_pathkeys(List *paths, --- 165,172 ---- double fraction); extern List *build_index_pathkeys(PlannerInfo *root, IndexOptInfo *index, ScanDirection scandir); + extern List *build_expression_pathkey(PlannerInfo *root, RelOptInfo *rel, + Expr *expr, Oid operator, bool nulls_first); extern List *convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, List *subquery_pathkeys); extern List *build_join_pathkeys(PlannerInfo *root, *** a/src/include/parser/parse_func.h --- b/src/include/parser/parse_func.h *************** *** 43,51 **** typedef enum extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, ! List *agg_order, Expr *agg_filter, ! bool agg_star, bool agg_distinct, bool func_variadic, ! WindowDef *over, bool is_column, int location); extern FuncDetailCode func_get_detail(List *funcname, List *fargs, List *fargnames, --- 43,49 ---- extern Node *ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, ! int location, FuncCall *fn); extern FuncDetailCode func_get_detail(List *funcname, List *fargs, List *fargnames, *** a/src/include/parser/parse_relation.h --- b/src/include/parser/parse_relation.h *************** *** 58,65 **** extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate, bool lateral, bool inFromCl); extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate, ! char *funcname, ! Node *funcexpr, RangeFunction *rangefunc, bool lateral, bool inFromCl); --- 58,65 ---- bool lateral, bool inFromCl); extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate, ! List *funcnames, ! List *funcexprs, RangeFunction *rangefunc, bool lateral, bool inFromCl); *** a/src/test/regress/expected/rangefuncs.out --- b/src/test/regress/expected/rangefuncs.out *************** *** 87,145 **** select definition from pg_views where viewname='vw_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; --- 87,190 ---- (1 row) drop view vw_ord; ! -- multiple functions ! select * from table(foot(1),foot(2)) with ordinality as z(a,b,c,d,ord); ! a | b | c | d | ord ! ---+-----+---+----+----- ! 1 | 11 | 2 | 22 | 1 ! 1 | 111 | | | 2 ! (2 rows) ! ! -- add multiple functions vs. views once deparse is stable ! -- ordinality and multiple functions vs. rewind and reverse scan begin; ! declare foo scroll cursor for select * from table(generate_series(1,5),generate_series(1,2)) with ordinality as g(i,j,o); fetch all from foo; ! i | j | o ! ---+---+--- ! 1 | 1 | 1 ! 2 | 2 | 2 ! 3 | | 3 ! 4 | | 4 ! 5 | | 5 (5 rows) fetch backward all from foo; ! i | j | o ! ---+---+--- ! 5 | | 5 ! 4 | | 4 ! 3 | | 3 ! 2 | 2 | 2 ! 1 | 1 | 1 (5 rows) fetch all from foo; ! i | j | o ! ---+---+--- ! 1 | 1 | 1 ! 2 | 2 | 2 ! 3 | | 3 ! 4 | | 4 ! 5 | | 5 (5 rows) fetch next from foo; ! i | j | o ! ---+---+--- (0 rows) fetch next from foo; ! i | j | o ! ---+---+--- (0 rows) fetch prior from foo; ! i | j | o ! ---+---+--- ! 5 | | 5 (1 row) fetch absolute 1 from foo; ! i | j | o ! ---+---+--- ! 1 | 1 | 1 ! (1 row) ! ! fetch next from foo; ! i | j | o ! ---+---+--- ! 2 | 2 | 2 ! (1 row) ! ! fetch next from foo; ! i | j | o ! ---+---+--- ! 3 | | 3 ! (1 row) ! ! fetch next from foo; ! i | j | o ! ---+---+--- ! 4 | | 4 ! (1 row) ! ! fetch prior from foo; ! i | j | o ! ---+---+--- ! 3 | | 3 ! (1 row) ! ! fetch prior from foo; ! i | j | o ! ---+---+--- ! 2 | 2 | 2 ! (1 row) ! ! fetch prior from foo; ! i | j | o ! ---+---+--- ! 1 | 1 | 1 (1 row) commit; *************** *** 199,260 **** INSERT INTO foo VALUES(1,1,'Joe'); INSERT INTO foo VALUES(1,2,'Ed'); INSERT INTO foo VALUES(2,1,'Mary'); -- sql, proretset = f, prorettype = b ! CREATE FUNCTION getfoo(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL; ! SELECT * FROM getfoo(1) AS t1; t1 ---- 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 ! -------- ! 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); ! CREATE FUNCTION getfoo(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo(1) AS t1; t1 ---- 1 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 ! -------- ! 1 ! 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 ---+--- --- 244,304 ---- INSERT INTO foo VALUES(1,2,'Ed'); INSERT INTO foo VALUES(2,1,'Mary'); -- sql, proretset = f, prorettype = b ! CREATE FUNCTION getfoo1(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL; ! SELECT * FROM getfoo1(1) AS t1; t1 ---- 1 (1 row) ! SELECT * FROM getfoo1(1) WITH ORDINALITY AS t1(v,o); v | o ---+--- 1 | 1 (1 row) ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1); SELECT * FROM vw_getfoo; ! getfoo1 ! --------- ! 1 (1 row) DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1) WITH ORDINALITY as t1(v,o); SELECT * FROM vw_getfoo; v | o ---+--- 1 | 1 (1 row) DROP VIEW vw_getfoo; ! -- sql, proretset = t, prorettype = b ! CREATE FUNCTION getfoo2(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo2(1) AS t1; t1 ---- 1 1 (2 rows) ! SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o); v | o ---+--- 1 | 1 1 | 2 (2 rows) ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1); SELECT * FROM vw_getfoo; ! getfoo2 ! --------- ! 1 ! 1 (2 rows) DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o); SELECT * FROM vw_getfoo; v | o ---+--- *************** *** 262,295 **** SELECT * FROM vw_getfoo; 1 | 2 (2 rows) - -- 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; t1 ----- Joe 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 ! -------- Joe 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 -----+--- --- 306,338 ---- 1 | 2 (2 rows) DROP VIEW vw_getfoo; ! -- sql, proretset = t, prorettype = b ! CREATE FUNCTION getfoo3(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo3(1) AS t1; t1 ----- Joe Ed (2 rows) ! SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o); v | o -----+--- Joe | 1 Ed | 2 (2 rows) ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1); SELECT * FROM vw_getfoo; ! getfoo3 ! --------- Joe Ed (2 rows) DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o); SELECT * FROM vw_getfoo; v | o -----+--- *************** *** 297,319 **** SELECT * FROM vw_getfoo; Ed | 2 (2 rows) - -- 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; fooid | foosubid | fooname -------+----------+--------- 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 -------+----------+--------- --- 340,361 ---- Ed | 2 (2 rows) DROP VIEW vw_getfoo; ! -- sql, proretset = f, prorettype = c ! CREATE FUNCTION getfoo4(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo4(1) AS t1; fooid | foosubid | fooname -------+----------+--------- 1 | 1 | Joe (1 row) ! SELECT * FROM getfoo4(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 getfoo4(1); SELECT * FROM vw_getfoo; fooid | foosubid | fooname -------+----------+--------- *************** *** 321,352 **** SELECT * FROM vw_getfoo; (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); ! CREATE FUNCTION getfoo(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo(1) AS t1; fooid | foosubid | fooname -------+----------+--------- 1 | 1 | Joe 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 -------+----------+--------- --- 363,393 ---- (1 row) DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(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; ! -- sql, proretset = t, prorettype = c ! CREATE FUNCTION getfoo5(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo5(1) AS t1; fooid | foosubid | fooname -------+----------+--------- 1 | 1 | Joe 1 | 2 | Ed (2 rows) ! SELECT * FROM getfoo5(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 getfoo5(1); SELECT * FROM vw_getfoo; fooid | foosubid | fooname -------+----------+--------- *************** *** 355,361 **** SELECT * FROM vw_getfoo; (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 ---+---+-----+--- --- 396,402 ---- (2 rows) DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o); SELECT * FROM vw_getfoo; a | b | c | o ---+---+-----+--- *************** *** 363,372 **** SELECT * FROM vw_getfoo; 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); CREATE FUNCTION getfoo(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text); fooid | foosubid | fooname --- 404,412 ---- 1 | 2 | Ed | 2 (2 rows) + DROP VIEW vw_getfoo; -- ordinality not supported for returns record yet -- sql, proretset = f, prorettype = record CREATE FUNCTION getfoo(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text); fooid | foosubid | fooname *************** *** 382,390 **** SELECT * FROM vw_getfoo; 1 | 1 | Joe (1 row) - -- sql, proretset = t, prorettype = record DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); CREATE FUNCTION getfoo(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text); fooid | foosubid | fooname --- 422,430 ---- 1 | 1 | Joe (1 row) DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); + -- sql, proretset = t, prorettype = record CREATE FUNCTION getfoo(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text); fooid | foosubid | fooname *************** *** 402,455 **** SELECT * FROM vw_getfoo; 1 | 2 | Ed (2 rows) - -- plpgsql, proretset = f, prorettype = b DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); ! CREATE FUNCTION getfoo(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql; ! SELECT * FROM getfoo(1) AS t1; t1 ---- 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 ! -------- ! 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); ! 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; fooid | foosubid | fooname -------+----------+--------- 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 -------+----------+--------- --- 442,496 ---- 1 | 2 | Ed (2 rows) DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); ! -- plpgsql, proretset = f, prorettype = b ! CREATE FUNCTION getfoo6(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql; ! SELECT * FROM getfoo6(1) AS t1; t1 ---- 1 (1 row) ! SELECT * FROM getfoo6(1) WITH ORDINALITY AS t1(v,o); v | o ---+--- 1 | 1 (1 row) ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo6(1); SELECT * FROM vw_getfoo; ! getfoo6 ! --------- ! 1 (1 row) DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo6(1) WITH ORDINALITY AS t1(v,o); SELECT * FROM vw_getfoo; v | o ---+--- 1 | 1 (1 row) DROP VIEW vw_getfoo; ! -- plpgsql, proretset = f, prorettype = c ! DROP FUNCTION getfoo7(int); ! ERROR: function getfoo7(integer) does not exist ! CREATE FUNCTION getfoo7(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql; ! SELECT * FROM getfoo7(1) AS t1; fooid | foosubid | fooname -------+----------+--------- 1 | 1 | Joe (1 row) ! SELECT * FROM getfoo7(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 getfoo7(1); SELECT * FROM vw_getfoo; fooid | foosubid | fooname -------+----------+--------- *************** *** 457,463 **** SELECT * FROM vw_getfoo; (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 ---+---+-----+--- --- 498,504 ---- (1 row) DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo7(1) WITH ORDINALITY AS t1(a,b,c,o); SELECT * FROM vw_getfoo; a | b | c | o ---+---+-----+--- *************** *** 465,471 **** SELECT * FROM vw_getfoo; (1 row) DROP VIEW vw_getfoo; ! DROP FUNCTION getfoo(int); DROP FUNCTION foot(int); DROP TABLE foo2; DROP TABLE foo; --- 506,535 ---- (1 row) DROP VIEW vw_getfoo; ! -- mix 'n match kinds, to exercise expandRTE and related logic ! select * from table(getfoo1(1),getfoo2(1),getfoo3(1),getfoo4(1),getfoo5(1),getfoo6(1),getfoo7(1)) ! with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o); ! a | b | c | d | e | f | g | h | i | j | k | l | m | o ! ---+---+-----+---+---+-----+---+---+-----+---+---+---+-----+--- ! 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | Joe | 1 | 1 | 1 | Joe | 1 ! | 1 | Ed | | | | 1 | 2 | Ed | | | | | 2 ! (2 rows) ! ! select * from table(getfoo7(1),getfoo6(1),getfoo5(1),getfoo4(1),getfoo3(1),getfoo2(1),getfoo1(1)) ! with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o); ! a | b | c | d | e | f | g | h | i | j | k | l | m | o ! ---+---+-----+---+---+---+-----+---+---+-----+-----+---+---+--- ! 1 | 1 | Joe | 1 | 1 | 1 | Joe | 1 | 1 | Joe | Joe | 1 | 1 | 1 ! | | | | 1 | 2 | Ed | | | | Ed | 1 | | 2 ! (2 rows) ! ! DROP FUNCTION getfoo1(int); ! DROP FUNCTION getfoo2(int); ! DROP FUNCTION getfoo3(int); ! DROP FUNCTION getfoo4(int); ! DROP FUNCTION getfoo5(int); ! DROP FUNCTION getfoo6(int); ! DROP FUNCTION getfoo7(int); DROP FUNCTION foot(int); DROP TABLE foo2; DROP TABLE foo; *************** *** 1566,1571 **** SELECT * FROM get_users() WITH ORDINALITY; -- make sure ordinality copes --- 1630,1650 ---- id2 | email2 | t | 2 (2 rows) + -- multiple functions vs. dropped columns + SELECT * FROM TABLE(generate_series(10,11), get_users()) WITH ORDINALITY; + generate_series | userid | email | enabled | ordinality + -----------------+--------+--------+---------+------------ + 10 | id | email | t | 1 + 11 | id2 | email2 | t | 2 + (2 rows) + + SELECT * FROM TABLE(get_users(), generate_series(10,11)) WITH ORDINALITY; + userid | email | enabled | generate_series | ordinality + --------+--------+---------+-----------------+------------ + id | email | t | 10 | 1 + id2 | email2 | t | 11 | 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 *************** *** 21,29 **** create temporary view vw_ord as select * from (values (1)) v(n) join foot(1) wit 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; --- 21,35 ---- select * from vw_ord; select definition from pg_views where viewname='vw_ord'; drop view vw_ord; ! ! -- multiple functions ! select * from table(foot(1),foot(2)) with ordinality as z(a,b,c,d,ord); ! ! -- add multiple functions vs. views once deparse is stable ! ! -- ordinality and multiple functions vs. rewind and reverse scan begin; ! declare foo scroll cursor for select * from table(generate_series(1,5),generate_series(1,2)) with ordinality as g(i,j,o); fetch all from foo; fetch backward all from foo; fetch all from foo; *************** *** 31,36 **** fetch next from foo; --- 37,48 ---- fetch next from foo; fetch prior from foo; fetch absolute 1 from foo; + fetch next from foo; + fetch next from foo; + fetch next from foo; + fetch prior from foo; + fetch prior from foo; + fetch prior from foo; commit; -- function with implicit LATERAL *************** *** 57,164 **** INSERT INTO foo VALUES(1,2,'Ed'); INSERT INTO foo VALUES(2,1,'Mary'); -- sql, proretset = f, prorettype = b ! CREATE FUNCTION getfoo(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL; ! SELECT * FROM getfoo(1) AS t1; ! SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY as t1(v,o); SELECT * FROM vw_getfoo; -- sql, proretset = t, prorettype = b ! DROP VIEW vw_getfoo; ! DROP FUNCTION getfoo(int); ! CREATE FUNCTION getfoo(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo(1) AS t1; ! SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); SELECT * FROM vw_getfoo; -- sql, proretset = t, prorettype = b ! DROP VIEW vw_getfoo; ! DROP FUNCTION getfoo(int); ! CREATE FUNCTION getfoo(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo(1) AS t1; ! SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); SELECT * FROM vw_getfoo; -- sql, proretset = f, prorettype = c ! DROP VIEW vw_getfoo; ! DROP FUNCTION getfoo(int); ! CREATE FUNCTION getfoo(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo(1) AS t1; ! SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); SELECT * FROM vw_getfoo; -- sql, proretset = t, prorettype = c ! DROP VIEW vw_getfoo; ! DROP FUNCTION getfoo(int); ! CREATE FUNCTION getfoo(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo(1) AS t1; ! SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); SELECT * FROM vw_getfoo; -- ordinality not supported for returns record yet -- sql, proretset = f, prorettype = record - DROP VIEW vw_getfoo; - DROP FUNCTION getfoo(int); CREATE FUNCTION getfoo(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS (fooid int, foosubid int, fooname text); SELECT * FROM vw_getfoo; - - -- sql, proretset = t, prorettype = record DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); CREATE FUNCTION getfoo(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS (fooid int, foosubid int, fooname text); SELECT * FROM vw_getfoo; - - -- plpgsql, proretset = f, prorettype = b DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); ! CREATE FUNCTION getfoo(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql; ! SELECT * FROM getfoo(1) AS t1; ! SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(v,o); SELECT * FROM vw_getfoo; -- plpgsql, proretset = f, prorettype = c ! DROP VIEW vw_getfoo; ! DROP FUNCTION getfoo(int); ! CREATE FUNCTION getfoo(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql; ! SELECT * FROM getfoo(1) AS t1; ! SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) WITH ORDINALITY AS t1(a,b,c,o); SELECT * FROM vw_getfoo; - DROP VIEW vw_getfoo; ! DROP FUNCTION getfoo(int); DROP FUNCTION foot(int); DROP TABLE foo2; DROP TABLE foo; --- 69,184 ---- INSERT INTO foo VALUES(2,1,'Mary'); -- sql, proretset = f, prorettype = b ! CREATE FUNCTION getfoo1(int) RETURNS int AS 'SELECT $1;' LANGUAGE SQL; ! SELECT * FROM getfoo1(1) AS t1; ! SELECT * FROM getfoo1(1) WITH ORDINALITY AS t1(v,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1) WITH ORDINALITY as t1(v,o); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; -- sql, proretset = t, prorettype = b ! CREATE FUNCTION getfoo2(int) RETURNS setof int AS 'SELECT fooid FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo2(1) AS t1; ! SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; -- sql, proretset = t, prorettype = b ! CREATE FUNCTION getfoo3(int) RETURNS setof text AS 'SELECT fooname FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo3(1) AS t1; ! SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; -- sql, proretset = f, prorettype = c ! CREATE FUNCTION getfoo4(int) RETURNS foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo4(1) AS t1; ! SELECT * FROM getfoo4(1) WITH ORDINALITY AS t1(a,b,c,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(1) WITH ORDINALITY AS t1(a,b,c,o); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; -- sql, proretset = t, prorettype = c ! CREATE FUNCTION getfoo5(int) RETURNS setof foo AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; ! SELECT * FROM getfoo5(1) AS t1; ! SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo5(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; -- ordinality not supported for returns record yet -- sql, proretset = f, prorettype = record CREATE FUNCTION getfoo(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS (fooid int, foosubid int, fooname text); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); + + -- sql, proretset = t, prorettype = record CREATE FUNCTION getfoo(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1(fooid int, foosubid int, fooname text); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1) AS (fooid int, foosubid int, fooname text); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; DROP FUNCTION getfoo(int); ! ! -- plpgsql, proretset = f, prorettype = b ! CREATE FUNCTION getfoo6(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql; ! SELECT * FROM getfoo6(1) AS t1; ! SELECT * FROM getfoo6(1) WITH ORDINALITY AS t1(v,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo6(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo6(1) WITH ORDINALITY AS t1(v,o); SELECT * FROM vw_getfoo; + DROP VIEW vw_getfoo; -- plpgsql, proretset = f, prorettype = c ! DROP FUNCTION getfoo7(int); ! CREATE FUNCTION getfoo7(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql; ! SELECT * FROM getfoo7(1) AS t1; ! SELECT * FROM getfoo7(1) WITH ORDINALITY AS t1(a,b,c,o); ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo7(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! CREATE VIEW vw_getfoo AS SELECT * FROM getfoo7(1) WITH ORDINALITY AS t1(a,b,c,o); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; ! ! -- mix 'n match kinds, to exercise expandRTE and related logic ! ! select * from table(getfoo1(1),getfoo2(1),getfoo3(1),getfoo4(1),getfoo5(1),getfoo6(1),getfoo7(1)) ! with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o); ! select * from table(getfoo7(1),getfoo6(1),getfoo5(1),getfoo4(1),getfoo3(1),getfoo2(1),getfoo1(1)) ! with ordinality as t1(a,b,c,d,e,f,g,h,i,j,k,l,m,o); ! ! DROP FUNCTION getfoo1(int); ! DROP FUNCTION getfoo2(int); ! DROP FUNCTION getfoo3(int); ! DROP FUNCTION getfoo4(int); ! DROP FUNCTION getfoo5(int); ! DROP FUNCTION getfoo6(int); ! DROP FUNCTION getfoo7(int); DROP FUNCTION foot(int); DROP TABLE foo2; DROP TABLE foo; *************** *** 466,471 **** language sql stable; --- 486,494 ---- SELECT get_users(); SELECT * FROM get_users(); SELECT * FROM get_users() WITH ORDINALITY; -- make sure ordinality copes + -- multiple functions vs. dropped columns + SELECT * FROM TABLE(generate_series(10,11), get_users()) WITH ORDINALITY; + SELECT * FROM TABLE(get_users(), generate_series(10,11)) WITH ORDINALITY; drop function get_first_user(); drop function get_users();