From 39b7ee10f7a5e13826bd77e7f9bb07c8a0d92653 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Fri, 26 Aug 2016 16:19:58 -0700 Subject: [PATCH 4/6] Allow ROWS FROM to return functions as single record column. Using an empty AS list inside ROWS FROM (e.g. SELECT * FROM ROWS FROM(aclexplode('{=r/andres}') AS ())), previously not permitted by the grammar, now returns the results of the function as a single record/composite field. This is primarily interesting to allow for the conversion of SELECT record_returning_srf(); into ROWS FROM. Without a facility like this there'd be no way to do that for functions returning record, and it'd be more complicated for composite returning functions. Todo: - cleanup code a bit, move some of the changes to previous commit --- src/backend/executor/nodeFunctionscan.c | 267 ++++++++++++++++++++++--------- src/backend/nodes/copyfuncs.c | 16 ++ src/backend/nodes/equalfuncs.c | 14 ++ src/backend/nodes/outfuncs.c | 14 ++ src/backend/nodes/readfuncs.c | 1 + src/backend/optimizer/util/clauses.c | 4 + src/backend/parser/gram.y | 43 +++-- src/backend/parser/parse_clause.c | 18 +-- src/backend/parser/parse_relation.c | 66 +++++++- src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 21 ++- src/include/nodes/pg_list.h | 9 ++ src/include/parser/parse_relation.h | 1 + src/test/regress/expected/rangefuncs.out | 85 ++++++++++ src/test/regress/sql/rangefuncs.sql | 18 +++ 15 files changed, 468 insertions(+), 110 deletions(-) diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index 4885f75..1da4fde 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -44,12 +44,15 @@ typedef struct FunctionScanPerFuncState { ExprState *funcexpr; /* state of the expression being evaluated */ - TupleDesc tupdesc; /* desc of the function result type */ int colcount; /* expected number of result columns */ Tuplestorestate *tstore; /* holds the function result set */ TupleTableSlot *func_slot; /* function result slot (or NULL) */ + TupleDesc func_desc; /* desc of the function result type */ + TupleTableSlot *scan_slot; /* function result slot (or NULL) */ + TupleDesc scan_desc; /* tupdesc for returning a tuple as one col */ bool started; bool returnsTuple; + bool toRecord; FunctionCallInfoData fcinfo; ReturnSetInfo rsinfo; } FunctionScanPerFuncState; @@ -109,7 +112,7 @@ FunctionNext(FunctionScanState *node) else ExecNextFunctionResult(node, fs); - scanslot = fs->func_slot; + scanslot = fs->scan_slot; return scanslot; } @@ -125,7 +128,7 @@ FunctionNext(FunctionScanState *node) /* * Main loop over functions. * - * We fetch the function results into func_slots (which match the function + * We fetch the function results into scan_slots (which match the function * return types), and then copy the values to scanslot (which matches the * scan result type), setting the ordinal column (if any) as well. */ @@ -147,7 +150,7 @@ FunctionNext(FunctionScanState *node) else ExecNextFunctionResult(node, fs); - if (TupIsNull(fs->func_slot)) + if (TupIsNull(fs->scan_slot)) { /* * populate the result cols with nulls @@ -164,12 +167,12 @@ FunctionNext(FunctionScanState *node) /* * we have a result, so just copy it to the result cols. */ - slot_getallattrs(fs->func_slot); + slot_getallattrs(fs->scan_slot); for (i = 0; i < fs->colcount; i++) { - scanslot->tts_values[att] = fs->func_slot->tts_values[i]; - scanslot->tts_isnull[att] = fs->func_slot->tts_isnull[i]; + scanslot->tts_values[att] = fs->scan_slot->tts_values[i]; + scanslot->tts_isnull[att] = fs->scan_slot->tts_isnull[i]; att++; } @@ -309,7 +312,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) FunctionScanPerFuncState *fs = &scanstate->funcstates[i]; TypeFuncClass functypclass; Oid funcrettype; - TupleDesc tupdesc; fs->funcexpr = ExecInitExpr((Expr *) funcexpr, (PlanState *) scanstate); @@ -321,6 +323,11 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) fs->tstore = NULL; fs->started = false; fs->rsinfo.setDesc = NULL; + fs->toRecord = false; + fs->scan_desc = NULL; + fs->func_desc = NULL; + fs->scan_slot = NULL; + fs->func_slot = NULL; /* * Now determine if the function returns a simple or composite type, @@ -330,45 +337,77 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) */ functypclass = get_expr_result_type(funcexpr, &funcrettype, - &tupdesc); + &fs->func_desc); - if (functypclass == TYPEFUNC_COMPOSITE) + if (rtfunc->funcasrecord) { - /* Composite data type, e.g. a table's row type */ - Assert(tupdesc); - Assert(tupdesc->natts >= colcount); - /* Must copy it out of typcache for safety */ - tupdesc = CreateTupleDescCopy(tupdesc); - fs->returnsTuple = true; - } - else if (functypclass == TYPEFUNC_SCALAR) - { - /* Base data type, i.e. scalar */ - tupdesc = CreateTemplateTupleDesc(1, false); - TupleDescInitEntry(tupdesc, + /* + * Returning a composite / record type as one column, instead of + * split up. Need to compute two tuple descs: One to pass to the + * function (important when return type is determined in catalog), + * and one specifying what is eventually returned to the user. + */ + Assert(functypclass == TYPEFUNC_COMPOSITE || + functypclass == TYPEFUNC_RECORD); + + /* + * fs->func_desc contains what the function returns, if known, + * scan_desc what we make it return + */ + if (fs->func_desc) + fs->func_desc = CreateTupleDescCopy(fs->func_desc); + fs->scan_desc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(fs->scan_desc, (AttrNumber) 1, NULL, /* don't care about the name here */ funcrettype, -1, 0); - TupleDescInitEntryCollation(tupdesc, + TupleDescInitEntryCollation(fs->scan_desc, (AttrNumber) 1, exprCollation(funcexpr)); + fs->toRecord = true; + fs->returnsTuple = true; + } + else if (functypclass == TYPEFUNC_COMPOSITE) + { + /* Composite data type, e.g. a table's row type */ + Assert(fs->func_desc); + Assert(fs->func_desc->natts >= colcount); + /* Must copy it out of typcache for safety */ + fs->func_desc = CreateTupleDescCopy(fs->func_desc); + fs->scan_desc = fs->func_desc; + fs->returnsTuple = true; + } + else if (functypclass == TYPEFUNC_SCALAR) + { + /* Base data type, i.e. scalar */ + fs->func_desc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(fs->func_desc, + (AttrNumber) 1, + NULL, /* don't care about the name here */ + funcrettype, + -1, + 0); + TupleDescInitEntryCollation(fs->func_desc, + (AttrNumber) 1, + exprCollation(funcexpr)); + fs->scan_desc = fs->func_desc; fs->returnsTuple = false; } else if (functypclass == TYPEFUNC_RECORD) { - tupdesc = BuildDescFromLists(rtfunc->funccolnames, - rtfunc->funccoltypes, - rtfunc->funccoltypmods, - rtfunc->funccolcollations); - + fs->func_desc = BuildDescFromLists(rtfunc->funccolnames, + rtfunc->funccoltypes, + rtfunc->funccoltypmods, + rtfunc->funccolcollations); /* * 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(tupdesc); + BlessTupleDesc(fs->func_desc); + fs->scan_desc = fs->func_desc; fs->returnsTuple = true; } else @@ -377,7 +416,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) elog(ERROR, "function in FROM has unsupported return type"); } - fs->tupdesc = tupdesc; fs->colcount = colcount; /* @@ -387,11 +425,11 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) */ if (!scanstate->simple) { - fs->func_slot = ExecInitExtraTupleSlot(estate); - ExecSetSlotDescriptor(fs->func_slot, fs->tupdesc); + fs->scan_slot = ExecInitExtraTupleSlot(estate); + ExecSetSlotDescriptor(fs->scan_slot, fs->scan_desc); } else - fs->func_slot = scanstate->ss.ss_ScanTupleSlot; + fs->scan_slot = scanstate->ss.ss_ScanTupleSlot; natts += colcount; i++; @@ -406,7 +444,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) */ if (scanstate->simple) { - scan_tupdesc = CreateTupleDescCopy(scanstate->funcstates[0].tupdesc); + scan_tupdesc = CreateTupleDescCopy(scanstate->funcstates[0].scan_desc); scan_tupdesc->tdtypeid = RECORDOID; scan_tupdesc->tdtypmod = -1; } @@ -421,7 +459,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) for (i = 0; i < nfuncs; i++) { - TupleDesc tupdesc = scanstate->funcstates[i].tupdesc; + TupleDesc tupdesc = scanstate->funcstates[i].scan_desc; int colcount = scanstate->funcstates[i].colcount; int j; @@ -498,6 +536,8 @@ ExecEndFunctionScan(FunctionScanState *node) { FunctionScanPerFuncState *fs = &node->funcstates[i]; + if (fs->scan_slot) + ExecClearTuple(fs->scan_slot); if (fs->func_slot) ExecClearTuple(fs->func_slot); @@ -526,6 +566,8 @@ ExecReScanFunctionScan(FunctionScanState *node) { FunctionScanPerFuncState *fs = &node->funcstates[i]; + if (fs->scan_slot) + ExecClearTuple(fs->scan_slot); if (fs->func_slot) ExecClearTuple(fs->func_slot); @@ -570,7 +612,7 @@ ExecBeginFunctionResult(FunctionScanState *node, callerContext = CurrentMemoryContext; - Assert(perfunc->tupdesc != NULL); + Assert(perfunc->scan_desc != NULL); /* * Prepare a resultinfo node for communication. We always do this even if @@ -581,7 +623,7 @@ ExecBeginFunctionResult(FunctionScanState *node, */ perfunc->rsinfo.type = T_ReturnSetInfo; perfunc->rsinfo.econtext = econtext; - perfunc->rsinfo.expectedDesc = perfunc->tupdesc; + perfunc->rsinfo.expectedDesc = perfunc->func_desc; perfunc->rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); perfunc->rsinfo.returnMode = SFRM_ValuePerCall; /* isDone is filled below */ @@ -718,7 +760,7 @@ ExecBeginFunctionResult(FunctionScanState *node, /* * Store current resultset item. */ - if (perfunc->returnsTuple) + if (perfunc->returnsTuple && !perfunc->toRecord) { if (!perfunc->fcinfo.isnull) { @@ -744,16 +786,16 @@ ExecBeginFunctionResult(FunctionScanState *node, * for functions returning RECORD, but might as well do it * always. */ - tupledesc_match(perfunc->tupdesc, perfunc->rsinfo.setDesc); + tupledesc_match(perfunc->scan_desc, perfunc->rsinfo.setDesc); } tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; - ExecStoreTuple(&tmptup, perfunc->func_slot, InvalidBuffer, false); + ExecStoreTuple(&tmptup, perfunc->scan_slot, InvalidBuffer, false); /* materializing handles expanded and toasted datums */ /* XXX: would be nice if this could be optimized away */ - ExecMaterializeSlot(perfunc->func_slot); + ExecMaterializeSlot(perfunc->scan_slot); } else { @@ -761,7 +803,7 @@ ExecBeginFunctionResult(FunctionScanState *node, * NULL result from a tuple-returning function; expand it * to a row of all nulls. */ - ExecStoreAllNullTuple(perfunc->func_slot); + ExecStoreAllNullTuple(perfunc->scan_slot); } } else @@ -769,13 +811,13 @@ ExecBeginFunctionResult(FunctionScanState *node, /* * Scalar-type case: just store the function result */ - ExecClearTuple(perfunc->func_slot); - perfunc->func_slot->tts_values[0] = result; - perfunc->func_slot->tts_isnull[0] = perfunc->fcinfo.isnull; - ExecStoreVirtualTuple(perfunc->func_slot); + ExecClearTuple(perfunc->scan_slot); + perfunc->scan_slot->tts_values[0] = result; + perfunc->scan_slot->tts_isnull[0] = perfunc->fcinfo.isnull; + ExecStoreVirtualTuple(perfunc->scan_slot); /* materializing handles expanded and toasted datums */ - ExecMaterializeSlot(perfunc->func_slot); + ExecMaterializeSlot(perfunc->scan_slot); } } else if (perfunc->rsinfo.returnMode == SFRM_Materialize) @@ -809,25 +851,70 @@ ExecBeginFunctionResult(FunctionScanState *node, */ if (perfunc->rsinfo.setDesc) { - tupledesc_match(perfunc->tupdesc, perfunc->rsinfo.setDesc); + if (!perfunc->toRecord) + tupledesc_match(perfunc->scan_desc, perfunc->rsinfo.setDesc); + else if (perfunc->func_desc) + tupledesc_match(perfunc->func_desc, perfunc->rsinfo.setDesc); + } - /* - * If it is a dynamically-allocated TupleDesc, free it: it is - * typically allocated in a per-query context, so we must avoid - * leaking it across multiple usages. - */ - if (perfunc->rsinfo.setDesc->tdrefcount == -1) + if (!perfunc->toRecord) + { + /* and return first row */ + (void) tuplestore_gettupleslot(perfunc->rsinfo.setResult, + ScanDirectionIsForward(direction), + false, + perfunc->scan_slot); + } + else + { + + if (perfunc->func_slot == NULL) { - FreeTupleDesc(perfunc->rsinfo.setDesc); - perfunc->rsinfo.setDesc = NULL; + MemoryContext oldcontext; + TupleDesc slotDesc; + + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + + /* don't assume slotDesc is long-lived */ + if (perfunc->rsinfo.setDesc) + slotDesc = CreateTupleDescCopy(perfunc->rsinfo.setDesc); + else if (perfunc->func_desc) + slotDesc = perfunc->func_desc; + else + Assert(false); + + perfunc->func_slot = MakeSingleTupleTableSlot(slotDesc); + MemoryContextSwitchTo(oldcontext); + } + + (void) tuplestore_gettupleslot(perfunc->rsinfo.setResult, + ScanDirectionIsForward(direction), + false, + perfunc->func_slot); + + ExecClearTuple(perfunc->scan_slot); + if (!TupIsNull(perfunc->func_slot)) + { + perfunc->scan_slot->tts_values[0] = + ExecFetchSlotTupleDatum(perfunc->func_slot); + perfunc->scan_slot->tts_isnull[0] = false; + ExecStoreVirtualTuple(perfunc->scan_slot); + /* materializing handles expanded and toasted datums */ + ExecMaterializeSlot(perfunc->scan_slot); } } - /* and return first row */ - (void) tuplestore_gettupleslot(perfunc->rsinfo.setResult, - ScanDirectionIsForward(direction), - false, - perfunc->func_slot); + /* + * If it is a dynamically-allocated TupleDesc, free it: it is + * typically allocated in a per-query context, so we want to avoid + * leaking it across multiple usages. + */ + if (perfunc->rsinfo.setDesc && + perfunc->rsinfo.setDesc->tdrefcount == -1) + { + FreeTupleDesc(perfunc->rsinfo.setDesc); + perfunc->rsinfo.setDesc = NULL; + } } } else @@ -847,9 +934,9 @@ no_function_result: */ perfunc->rsinfo.isDone = ExprEndResult; if (returnsSet) - ExecClearTuple(perfunc->func_slot); + ExecClearTuple(perfunc->scan_slot); else - ExecStoreAllNullTuple(perfunc->func_slot); + ExecStoreAllNullTuple(perfunc->scan_slot); done: MemoryContextSwitchTo(callerContext); } @@ -870,15 +957,37 @@ ExecNextFunctionResult(FunctionScanState *node, if (perfunc->tstore) { - (void) tuplestore_gettupleslot(perfunc->tstore, - ScanDirectionIsForward(direction), - false, - perfunc->func_slot); + if (!perfunc->toRecord) + { + /* and return first row */ + (void) tuplestore_gettupleslot(perfunc->rsinfo.setResult, + ScanDirectionIsForward(direction), + false, + perfunc->scan_slot); + } + else + { + (void) tuplestore_gettupleslot(perfunc->rsinfo.setResult, + ScanDirectionIsForward(direction), + false, + perfunc->func_slot); + + ExecClearTuple(perfunc->scan_slot); + if (!TupIsNull(perfunc->func_slot)) + { + perfunc->scan_slot->tts_values[0] = + ExecFetchSlotTupleDatum(perfunc->func_slot); + perfunc->scan_slot->tts_isnull[0] = false; + ExecStoreVirtualTuple(perfunc->scan_slot); + /* materializing handles expanded and toasted datums */ + ExecMaterializeSlot(perfunc->scan_slot); + } + } } else if (perfunc->rsinfo.isDone == ExprSingleResult || perfunc->rsinfo.isDone == ExprEndResult) { - ExecClearTuple(perfunc->func_slot); + ExecClearTuple(perfunc->scan_slot); } else { @@ -910,11 +1019,11 @@ ExecNextFunctionResult(FunctionScanState *node, if (perfunc->rsinfo.isDone == ExprEndResult) { - ExecClearTuple(perfunc->func_slot); + ExecClearTuple(perfunc->scan_slot); goto out; } - if (perfunc->returnsTuple) + if (perfunc->returnsTuple && !perfunc->toRecord) { if (!perfunc->fcinfo.isnull) { @@ -943,7 +1052,7 @@ ExecNextFunctionResult(FunctionScanState *node, * for functions returning RECORD, but might as well do it * always. */ - tupledesc_match(perfunc->tupdesc, perfunc->rsinfo.setDesc); + tupledesc_match(perfunc->scan_desc, perfunc->rsinfo.setDesc); } tupdesc = perfunc->rsinfo.setDesc; @@ -961,26 +1070,26 @@ ExecNextFunctionResult(FunctionScanState *node, tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; - ExecStoreTuple(&tmptup, perfunc->func_slot, InvalidBuffer, false); + ExecStoreTuple(&tmptup, perfunc->scan_slot, InvalidBuffer, false); /* materializing handles expanded and toasted datums */ /* XXX: would be nice if this could be optimized away */ - ExecMaterializeSlot(perfunc->func_slot); + ExecMaterializeSlot(perfunc->scan_slot); } else { - ExecStoreAllNullTuple(perfunc->func_slot); + ExecStoreAllNullTuple(perfunc->scan_slot); } } else { /* Scalar-type case: just store the function result */ - ExecClearTuple(perfunc->func_slot); - perfunc->func_slot->tts_values[0] = result; - perfunc->func_slot->tts_isnull[0] = perfunc->fcinfo.isnull; - ExecStoreVirtualTuple(perfunc->func_slot); + ExecClearTuple(perfunc->scan_slot); + perfunc->scan_slot->tts_values[0] = result; + perfunc->scan_slot->tts_isnull[0] = perfunc->fcinfo.isnull; + ExecStoreVirtualTuple(perfunc->scan_slot); /* materializing handles expanded and toasted datums */ - ExecMaterializeSlot(perfunc->func_slot); + ExecMaterializeSlot(perfunc->scan_slot); } } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 1877fb4..292ab6c 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2183,6 +2183,7 @@ _copyRangeTblFunction(const RangeTblFunction *from) COPY_NODE_FIELD(funccoltypmods); COPY_NODE_FIELD(funccolcollations); COPY_BITMAPSET_FIELD(funcparams); + COPY_SCALAR_FIELD(funcasrecord); return newnode; } @@ -2556,6 +2557,18 @@ _copyRangeFunction(const RangeFunction *from) return newnode; } +static RangeFunctionElem * +_copyRangeFunctionElem(const RangeFunctionElem *from) +{ + RangeFunctionElem *newnode = makeNode(RangeFunctionElem); + + COPY_SCALAR_FIELD(asrecord); + COPY_NODE_FIELD(func); + COPY_NODE_FIELD(coldeflist); + + return newnode; +} + static RangeTableSample * _copyRangeTableSample(const RangeTableSample *from) { @@ -5016,6 +5029,9 @@ copyObject(const void *from) case T_RangeFunction: retval = _copyRangeFunction(from); break; + case T_RangeFunctionElem: + retval = _copyRangeFunctionElem(from); + break; case T_RangeTableSample: retval = _copyRangeTableSample(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 448e1a9..f69dc8e 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2339,6 +2339,16 @@ _equalRangeFunction(const RangeFunction *a, const RangeFunction *b) } static bool +_equalRangeFunctionElem(const RangeFunctionElem *a, const RangeFunctionElem *b) +{ + COMPARE_NODE_FIELD(func); + COMPARE_NODE_FIELD(coldeflist); + COMPARE_SCALAR_FIELD(asrecord); + + return true; +} + +static bool _equalRangeTableSample(const RangeTableSample *a, const RangeTableSample *b) { COMPARE_NODE_FIELD(relation); @@ -2484,6 +2494,7 @@ _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b) COMPARE_NODE_FIELD(funccoltypmods); COMPARE_NODE_FIELD(funccolcollations); COMPARE_BITMAPSET_FIELD(funcparams); + COMPARE_SCALAR_FIELD(funcasrecord); return true; } @@ -3314,6 +3325,9 @@ equal(const void *a, const void *b) case T_RangeFunction: retval = _equalRangeFunction(a, b); break; + case T_RangeFunctionElem: + retval = _equalRangeFunctionElem(a, b); + break; case T_RangeTableSample: retval = _equalRangeTableSample(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 29b7712..bea295b 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2879,6 +2879,7 @@ _outRangeTblFunction(StringInfo str, const RangeTblFunction *node) WRITE_NODE_FIELD(funccoltypmods); WRITE_NODE_FIELD(funccolcollations); WRITE_BITMAPSET_FIELD(funcparams); + WRITE_BOOL_FIELD(funcasrecord); } static void @@ -3148,6 +3149,16 @@ _outRangeFunction(StringInfo str, const RangeFunction *node) } static void +_outRangeFunctionElem(StringInfo str, const RangeFunctionElem *node) +{ + WRITE_NODE_TYPE("RANGEFUNCTIONELEM"); + + WRITE_NODE_FIELD(func); + WRITE_BOOL_FIELD(coldeflist); + WRITE_BOOL_FIELD(asrecord); +} + +static void _outRangeTableSample(StringInfo str, const RangeTableSample *node) { WRITE_NODE_TYPE("RANGETABLESAMPLE"); @@ -3840,6 +3851,9 @@ outNode(StringInfo str, const void *obj) case T_RangeFunction: _outRangeFunction(str, obj); break; + case T_RangeFunctionElem: + _outRangeFunctionElem(str, obj); + break; case T_RangeTableSample: _outRangeTableSample(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 6f9a81e..70bb73d 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1357,6 +1357,7 @@ _readRangeTblFunction(void) READ_NODE_FIELD(funccoltypmods); READ_NODE_FIELD(funccolcollations); READ_BITMAPSET_FIELD(funcparams); + READ_BOOL_FIELD(funcasrecord); READ_DONE(); } diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 4496fde..3830bc9 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4828,6 +4828,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) return NULL; rtfunc = (RangeTblFunction *) linitial(rte->functions); + /* Fail if the function returns as record - we don't implement that here. */ + if (rtfunc->funcasrecord) + return NULL; + if (!IsA(rtfunc->funcexpr, FuncExpr)) return NULL; fexpr = (FuncExpr *) rtfunc->funcexpr; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index cb5cfc4..183403f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -441,11 +441,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound columnref in_expr having_clause func_table array_expr ExclusionWhereClause -%type rowsfrom_item rowsfrom_list opt_col_def_list +%type rowsfrom_list %type opt_ordinality %type ExclusionConstraintList ExclusionConstraintElem %type func_arg_list -%type func_arg_expr +%type func_arg_expr rowsfrom_item %type row explicit_row implicit_row type_list array_expr_list %type case_expr case_arg when_clause case_default %type when_clause_list @@ -10980,10 +10980,17 @@ opt_repeatable_clause: func_table: func_expr_windowless opt_ordinality { RangeFunction *n = makeNode(RangeFunction); + RangeFunctionElem *e = makeNode(RangeFunctionElem); + n->lateral = false; n->ordinality = $2; n->is_rowsfrom = false; - n->functions = list_make1(list_make2($1, NIL)); + n->functions = list_make1(e); + + e->func = $1; + e->coldeflist = NIL; + e->asrecord = false; + /* alias and coldeflist are set by table_ref production */ $$ = (Node *) n; } @@ -10999,8 +11006,30 @@ func_table: func_expr_windowless opt_ordinality } ; -rowsfrom_item: func_expr_windowless opt_col_def_list - { $$ = list_make2($1, $2); } +rowsfrom_item: func_expr_windowless AS '(' TableFuncElementList ')' + { + RangeFunctionElem *n = makeNode(RangeFunctionElem); + n->func = $1; + n->coldeflist = $4; + n->asrecord = false; + $$ = (Node *) n; + } + | func_expr_windowless AS '(' ')' + { + RangeFunctionElem *n = makeNode(RangeFunctionElem); + n->func = $1; + n->coldeflist = NIL; + n->asrecord = true; + $$ = (Node *) n; + } + | func_expr_windowless + { + RangeFunctionElem *n = makeNode(RangeFunctionElem); + n->func = $1; + n->coldeflist = NIL; + n->asrecord = false; + $$ = (Node *) n; + } ; rowsfrom_list: @@ -11008,10 +11037,6 @@ rowsfrom_list: | rowsfrom_list ',' rowsfrom_item { $$ = lappend($1, $3); } ; -opt_col_def_list: AS '(' TableFuncElementList ')' { $$ = $3; } - | /*EMPTY*/ { $$ = NIL; } - ; - opt_ordinality: WITH_LA ORDINALITY { $$ = true; } | /*EMPTY*/ { $$ = false; } ; diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 9b7fcc3..e40954c 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -533,6 +533,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r) List *funcexprs = NIL; List *funcnames = NIL; List *coldeflists = NIL; + List *asrecordlist = NIL; bool is_lateral; RangeTblEntry *rte; ListCell *lc; @@ -567,14 +568,9 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r) */ foreach(lc, r->functions) { - List *pair = (List *) lfirst(lc); - Node *fexpr; - List *coldeflist; - - /* Disassemble the function-call/column-def-list pairs */ - Assert(list_length(pair) == 2); - fexpr = (Node *) linitial(pair); - coldeflist = (List *) lsecond(pair); + RangeFunctionElem *elem = (RangeFunctionElem *) lfirst(lc); + Node *fexpr = elem->func; + List *coldeflist = elem->coldeflist; /* * If we find a function call unnest() with more than one argument and @@ -630,6 +626,8 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r) /* coldeflist is empty, so no error is possible */ coldeflists = lappend(coldeflists, coldeflist); + + asrecordlist = lappend_int(asrecordlist, false); } continue; /* done with this function item */ } @@ -651,6 +649,8 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r) exprLocation((Node *) r->coldeflist)))); coldeflists = lappend(coldeflists, coldeflist); + + asrecordlist = lappend_int(asrecordlist, elem->asrecord); } pstate->p_lateral_active = false; @@ -713,7 +713,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r) */ rte = addRangeTableEntryForFunction(pstate, funcnames, funcexprs, coldeflists, - r, is_lateral, true); + asrecordlist, r, is_lateral, true); return rte; } diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 1e3ecbc..0d92a00 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -1382,6 +1382,7 @@ addRangeTableEntryForFunction(ParseState *pstate, List *funcnames, List *funcexprs, List *coldeflists, + List *asrecordlist, RangeFunction *rangefunc, bool lateral, bool inFromCl) @@ -1395,7 +1396,8 @@ addRangeTableEntryForFunction(ParseState *pstate, TupleDesc tupdesc; ListCell *lc1, *lc2, - *lc3; + *lc3, + *lc4; int i; int j; int funcno; @@ -1429,11 +1431,12 @@ addRangeTableEntryForFunction(ParseState *pstate, totalatts = 0; funcno = 0; - forthree(lc1, funcexprs, lc2, funcnames, lc3, coldeflists) + forfour(lc1, funcexprs, lc2, funcnames, lc3, coldeflists, lc4, asrecordlist) { Node *funcexpr = (Node *) lfirst(lc1); char *funcname = (char *) lfirst(lc2); List *coldeflist = (List *) lfirst(lc3); + int asrecord = lfirst_int(lc4); RangeTblFunction *rtfunc = makeNode(RangeTblFunction); TypeFuncClass functypclass; Oid funcrettype; @@ -1445,6 +1448,7 @@ addRangeTableEntryForFunction(ParseState *pstate, rtfunc->funccoltypmods = NIL; rtfunc->funccolcollations = NIL; rtfunc->funcparams = NULL; /* not set until planning */ + rtfunc->funcasrecord = asrecord; /* * Now determine if the function returns a simple or composite type. @@ -1453,6 +1457,15 @@ addRangeTableEntryForFunction(ParseState *pstate, &funcrettype, &tupdesc); + if (asrecord && functypclass != TYPEFUNC_RECORD && functypclass != TYPEFUNC_COMPOSITE) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only composite and \"record\" returning functions can be returned as record"), + parser_errposition(pstate, + exprLocation(funcexpr)))); + } + /* * A coldeflist is required if the function returns RECORD and hasn't * got a predetermined record type, and is prohibited otherwise. @@ -1466,16 +1479,26 @@ addRangeTableEntryForFunction(ParseState *pstate, parser_errposition(pstate, exprLocation((Node *) coldeflist)))); } - else + else if (functypclass == TYPEFUNC_RECORD && !asrecord) { - 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) + if (asrecord) + { + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + chooseScalarFunctionAlias(funcexpr, funcname, + alias, nfuncs), + funcrettype, + -1, + 0); + } + else if (functypclass == TYPEFUNC_COMPOSITE) { /* Composite data type, e.g. a table's row type */ Assert(tupdesc); @@ -2052,7 +2075,29 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, functypclass = get_expr_result_type(rtfunc->funcexpr, &funcrettype, &tupdesc); - if (functypclass == TYPEFUNC_COMPOSITE) + + if (rtfunc->funcasrecord) + { + /* 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(rtfunc->funcexpr), + sublevels_up); + varnode->location = location; + + *colvars = lappend(*colvars, varnode); + } + } + else if (functypclass == TYPEFUNC_COMPOSITE) { /* Composite data type, e.g. a table's row type */ Assert(tupdesc); @@ -2585,7 +2630,14 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, &funcrettype, &tupdesc); - if (functypclass == TYPEFUNC_COMPOSITE) + if (rtfunc->funcasrecord) + { + /* XXX */ + *vartype = funcrettype; + *vartypmod = -1; + *varcollid = exprCollation(rtfunc->funcexpr); + } + else if (functypclass == TYPEFUNC_COMPOSITE) { /* Composite data type, e.g. a table's row type */ Form_pg_attribute att_tup; diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 2f7efa8..7d7bd91 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -427,6 +427,7 @@ typedef enum NodeTag T_WindowDef, T_RangeSubselect, T_RangeFunction, + T_RangeFunctionElem, T_RangeTableSample, T_TypeName, T_ColumnDef, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 1481fff..f26e651 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -522,12 +522,9 @@ typedef struct RangeSubselect /* * RangeFunction - function call appearing in a FROM clause * - * functions is a List because we use this to represent the construct - * ROWS FROM(func1(...), func2(...), ...). Each element of this list is a - * two-element sublist, the first element being the untransformed function - * call tree, and the second element being a possibly-empty list of ColumnDef - * nodes representing any columndef list attached to that function within the - * ROWS FROM() syntax. + * functions is a List because we use this to represent the construct ROWS + * FROM(func1(...), func2(...), ...). Each element of this list a + * RangeFunctionElem pointer. * * alias and coldeflist represent any alias and/or columndef list attached * at the top level. (We disallow coldeflist appearing both here and @@ -546,6 +543,17 @@ typedef struct RangeFunction } RangeFunction; /* + * RangeFunctionElem - individual function call in RangeFunction + */ +typedef struct RangeFunctionElem +{ + NodeTag type; + Node *func; /* untransformed function call */ + List *coldeflist; /* optional coldef list inside ROWS FROM */ + bool asrecord; /* record returned as one column */ +} RangeFunctionElem; + +/* * RangeTableSample - TABLESAMPLE appearing in a raw FROM clause * * This node, appearing only in raw parse trees, represents @@ -902,6 +910,7 @@ typedef struct RangeTblFunction List *funccolcollations; /* OID list of column collation OIDs */ /* This is set during planning for use by the executor: */ Bitmapset *funcparams; /* PARAM_EXEC Param IDs affecting this func */ + bool funcasrecord; /* return results as a single record column */ } RangeTblFunction; /* diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h index 77b50ff..b6b57c1 100644 --- a/src/include/nodes/pg_list.h +++ b/src/include/nodes/pg_list.h @@ -185,6 +185,15 @@ list_length(const List *l) (cell1) != NULL && (cell2) != NULL && (cell3) != NULL; \ (cell1) = lnext(cell1), (cell2) = lnext(cell2), (cell3) = lnext(cell3)) +/* + * fortfour - + * the same for four lists + */ +#define forfour(cell1, list1, cell2, list2, cell3, list3, cell4, list4) \ + for ((cell1) = list_head(list1), (cell2) = list_head(list2), (cell3) = list_head(list3), (cell4) = list_head(list4); \ + (cell1) != NULL && (cell2) != NULL && (cell3) != NULL && (cell4) != NULL; \ + (cell1) = lnext(cell1), (cell2) = lnext(cell2), (cell3) = lnext(cell3), (cell4) = lnext(cell4)) + extern List *lappend(List *list, void *datum); extern List *lappend_int(List *list, int datum); extern List *lappend_oid(List *list, Oid datum); diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index 3ef3d7b..90af146 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -80,6 +80,7 @@ extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate, List *funcnames, List *funcexprs, List *coldeflists, + List *asrecordlist, RangeFunction *rangefunc, bool lateral, bool inFromCl); diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index a6496fc..249dc67 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -369,6 +369,10 @@ SELECT * FROM getfoo1(1) WITH ORDINALITY AS t1(v,o); 1 | 1 (1 row) +SELECT * FROM ROWS FROM( getfoo1(1) AS ()) AS t1; -- error, not a composite / record +ERROR: only composite and "record" returning functions can be returned as record +LINE 1: SELECT * FROM ROWS FROM( getfoo1(1) AS ()) AS t1; + ^ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1); SELECT * FROM vw_getfoo; getfoo1 @@ -401,6 +405,10 @@ SELECT * FROM getfoo2(1) WITH ORDINALITY AS t1(v,o); 1 | 2 (2 rows) +SELECT * FROM ROWS FROM( getfoo2(1) AS ()) AS t1; -- error, not a composite / record +ERROR: only composite and "record" returning functions can be returned as record +LINE 1: SELECT * FROM ROWS FROM( getfoo2(1) AS ()) AS t1; + ^ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1); SELECT * FROM vw_getfoo; getfoo2 @@ -435,6 +443,10 @@ SELECT * FROM getfoo3(1) WITH ORDINALITY AS t1(v,o); Ed | 2 (2 rows) +SELECT * FROM ROWS FROM( getfoo3(1) AS ()) AS t1; -- error, not a composite / record +ERROR: only composite and "record" returning functions can be returned as record +LINE 1: SELECT * FROM ROWS FROM( getfoo3(1) AS ()) AS t1; + ^ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1); SELECT * FROM vw_getfoo; getfoo3 @@ -461,12 +473,24 @@ SELECT * FROM getfoo4(1) AS t1; 1 | 1 | Joe (1 row) +SELECT * FROM ROWS FROM( getfoo4(1) AS ()) AS t1; + t1 +----------- + (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) +SELECT * FROM ROWS FROM( getfoo4(1) AS ()) WITH ORDINALITY AS t1(abc,o); + abc | o +-----------+--- + (1,1,Joe) | 1 +(1 row) + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(1); SELECT * FROM vw_getfoo; fooid | foosubid | fooname @@ -492,6 +516,13 @@ SELECT * FROM getfoo5(1) AS t1; 1 | 2 | Ed (2 rows) +SELECT * FROM ROWS FROM(getfoo5(1) AS ()) AS t1; + t1 +----------- + (1,1,Joe) + (1,2,Ed) +(2 rows) + SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o); a | b | c | o ---+---+-----+--- @@ -499,6 +530,13 @@ SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o); 1 | 2 | Ed | 2 (2 rows) +SELECT * FROM ROWS FROM(getfoo5(1) AS ()) WITH ORDINALITY AS t1(abc,o); + abc | 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 @@ -525,12 +563,24 @@ SELECT * FROM getfoo6(1) AS t1(fooid int, foosubid int, fooname text); 1 | 1 | Joe (1 row) +SELECT * FROM ROWS FROM( getfoo6(1) AS ()) AS t1; + t1 +----------- + (1,1,Joe) +(1 row) + SELECT * FROM ROWS FROM( getfoo6(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY; fooid | foosubid | fooname | ordinality -------+----------+---------+------------ 1 | 1 | Joe | 1 (1 row) +SELECT * FROM ROWS FROM( getfoo6(1) AS ()) WITH ORDINALITY; + getfoo6 | ordinality +-----------+------------ + (1,1,Joe) | 1 +(1 row) + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo6(1) AS (fooid int, foosubid int, fooname text); SELECT * FROM vw_getfoo; @@ -559,6 +609,13 @@ SELECT * FROM getfoo7(1) AS t1(fooid int, foosubid int, fooname text); 1 | 2 | Ed (2 rows) +SELECT * FROM ROWS FROM( getfoo7(1) AS ()) AS t1; + t1 +----------- + (1,1,Joe) + (1,2,Ed) +(2 rows) + SELECT * FROM ROWS FROM( getfoo7(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY; fooid | foosubid | fooname | ordinality -------+----------+---------+------------ @@ -566,6 +623,13 @@ SELECT * FROM ROWS FROM( getfoo7(1) AS (fooid int, foosubid int, fooname text) ) 1 | 2 | Ed | 2 (2 rows) +SELECT * FROM ROWS FROM( getfoo7(1) AS ()) WITH ORDINALITY; + getfoo7 | ordinality +-----------+------------ + (1,1,Joe) | 1 + (1,2,Ed) | 2 +(2 rows) + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo7(1) AS (fooid int, foosubid int, fooname text); SELECT * FROM vw_getfoo; @@ -601,6 +665,10 @@ SELECT * FROM getfoo8(1) WITH ORDINALITY AS t1(v,o); 1 | 1 (1 row) +SELECT * FROM ROWS FROM( getfoo8(1) AS ()) AS t1; -- error, not a composite / record +ERROR: only composite and "record" returning functions can be returned as record +LINE 1: SELECT * FROM ROWS FROM( getfoo8(1) AS ()) AS t1; + ^ CREATE VIEW vw_getfoo AS SELECT * FROM getfoo8(1); SELECT * FROM vw_getfoo; getfoo8 @@ -631,6 +699,12 @@ SELECT * FROM getfoo9(1) WITH ORDINALITY AS t1(a,b,c,o); 1 | 1 | Joe | 1 (1 row) +SELECT * FROM ROWS FROM( getfoo9(1) AS ()) AS t1; + t1 +----------- + (1,1,Joe) +(1 row) + CREATE VIEW vw_getfoo AS SELECT * FROM getfoo9(1); SELECT * FROM vw_getfoo; fooid | foosubid | fooname @@ -670,6 +744,17 @@ select * from rows from(getfoo9(1),getfoo8(1), | | | | 1 | 2 | Ed | | | | 1 | 2 | Ed | | | | Ed | 1 | | 2 (2 rows) +select * from rows from(getfoo9(1),getfoo8(1), + getfoo7(1) AS (fooid int, foosubid int, fooname text), + getfoo6(1) AS (), + getfoo5(1),getfoo4(1),getfoo3(1),getfoo2(1),getfoo1(1)) + with ordinality as t1(a,b,c,d,e,f,g,h,k,l,m,o,p,q,r,s,t,u); + a | b | c | d | e | f | g | h | k | l | m | o | p | q | r | s | t | u +---+---+-----+---+---+---+-----+-----------+---+---+-----+---+---+-----+-----+---+---+--- + 1 | 1 | Joe | 1 | 1 | 1 | Joe | (1,1,Joe) | 1 | 1 | Joe | 1 | 1 | Joe | Joe | 1 | 1 | 1 + | | | | 1 | 2 | Ed | | 1 | 2 | Ed | | | | Ed | 1 | | 2 +(2 rows) + create temporary view vw_foo as select * from rows from(getfoo9(1), getfoo7(1) AS (fooid int, foosubid int, fooname text), diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql index c8edc55..b43473a 100644 --- a/src/test/regress/sql/rangefuncs.sql +++ b/src/test/regress/sql/rangefuncs.sql @@ -93,6 +93,7 @@ INSERT INTO foo VALUES(2,1,'Mary'); 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); +SELECT * FROM ROWS FROM( getfoo1(1) AS ()) AS t1; -- error, not a composite / record CREATE VIEW vw_getfoo AS SELECT * FROM getfoo1(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; @@ -104,6 +105,7 @@ DROP VIEW vw_getfoo; 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); +SELECT * FROM ROWS FROM( getfoo2(1) AS ()) AS t1; -- error, not a composite / record CREATE VIEW vw_getfoo AS SELECT * FROM getfoo2(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; @@ -115,6 +117,7 @@ DROP VIEW vw_getfoo; 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); +SELECT * FROM ROWS FROM( getfoo3(1) AS ()) AS t1; -- error, not a composite / record CREATE VIEW vw_getfoo AS SELECT * FROM getfoo3(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; @@ -125,7 +128,9 @@ 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 ROWS FROM( getfoo4(1) AS ()) AS t1; SELECT * FROM getfoo4(1) WITH ORDINALITY AS t1(a,b,c,o); +SELECT * FROM ROWS FROM( getfoo4(1) AS ()) WITH ORDINALITY AS t1(abc,o); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo4(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; @@ -136,7 +141,9 @@ 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 ROWS FROM(getfoo5(1) AS ()) AS t1; SELECT * FROM getfoo5(1) WITH ORDINALITY AS t1(a,b,c,o); +SELECT * FROM ROWS FROM(getfoo5(1) AS ()) WITH ORDINALITY AS t1(abc,o); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo5(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; @@ -147,7 +154,9 @@ DROP VIEW vw_getfoo; -- sql, proretset = f, prorettype = record CREATE FUNCTION getfoo6(int) RETURNS RECORD AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo6(1) AS t1(fooid int, foosubid int, fooname text); +SELECT * FROM ROWS FROM( getfoo6(1) AS ()) AS t1; SELECT * FROM ROWS FROM( getfoo6(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY; +SELECT * FROM ROWS FROM( getfoo6(1) AS ()) WITH ORDINALITY; CREATE VIEW vw_getfoo AS SELECT * FROM getfoo6(1) AS (fooid int, foosubid int, fooname text); SELECT * FROM vw_getfoo; @@ -161,7 +170,9 @@ DROP VIEW vw_getfoo; -- sql, proretset = t, prorettype = record CREATE FUNCTION getfoo7(int) RETURNS setof record AS 'SELECT * FROM foo WHERE fooid = $1;' LANGUAGE SQL; SELECT * FROM getfoo7(1) AS t1(fooid int, foosubid int, fooname text); +SELECT * FROM ROWS FROM( getfoo7(1) AS ()) AS t1; SELECT * FROM ROWS FROM( getfoo7(1) AS (fooid int, foosubid int, fooname text) ) WITH ORDINALITY; +SELECT * FROM ROWS FROM( getfoo7(1) AS ()) WITH ORDINALITY; CREATE VIEW vw_getfoo AS SELECT * FROM getfoo7(1) AS (fooid int, foosubid int, fooname text); SELECT * FROM vw_getfoo; @@ -176,6 +187,7 @@ DROP VIEW vw_getfoo; CREATE FUNCTION getfoo8(int) RETURNS int AS 'DECLARE fooint int; BEGIN SELECT fooid into fooint FROM foo WHERE fooid = $1; RETURN fooint; END;' LANGUAGE plpgsql; SELECT * FROM getfoo8(1) AS t1; SELECT * FROM getfoo8(1) WITH ORDINALITY AS t1(v,o); +SELECT * FROM ROWS FROM( getfoo8(1) AS ()) AS t1; -- error, not a composite / record CREATE VIEW vw_getfoo AS SELECT * FROM getfoo8(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; @@ -187,6 +199,7 @@ DROP VIEW vw_getfoo; CREATE FUNCTION getfoo9(int) RETURNS foo AS 'DECLARE footup foo%ROWTYPE; BEGIN SELECT * into footup FROM foo WHERE fooid = $1; RETURN footup; END;' LANGUAGE plpgsql; SELECT * FROM getfoo9(1) AS t1; SELECT * FROM getfoo9(1) WITH ORDINALITY AS t1(a,b,c,o); +SELECT * FROM ROWS FROM( getfoo9(1) AS ()) AS t1; CREATE VIEW vw_getfoo AS SELECT * FROM getfoo9(1); SELECT * FROM vw_getfoo; DROP VIEW vw_getfoo; @@ -206,6 +219,11 @@ select * from rows from(getfoo9(1),getfoo8(1), getfoo6(1) AS (fooid int, foosubid int, fooname text), 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,p,q,r,s,t,u); +select * from rows from(getfoo9(1),getfoo8(1), + getfoo7(1) AS (fooid int, foosubid int, fooname text), + getfoo6(1) AS (), + getfoo5(1),getfoo4(1),getfoo3(1),getfoo2(1),getfoo1(1)) + with ordinality as t1(a,b,c,d,e,f,g,h,k,l,m,o,p,q,r,s,t,u); create temporary view vw_foo as select * from rows from(getfoo9(1), -- 2.9.3