diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2013-11-21 19:37:02 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2013-11-21 19:37:20 -0500 |
commit | 784e762e886e6f72f548da86a27cd2ead87dbd1c (patch) | |
tree | 9c21fc1545c96a655ec4591e1ba3c8d99cdfccf8 /src/backend/executor/nodeFunctionscan.c | |
parent | 38f432898131270e5b64245786cb67f322538bae (diff) | |
download | postgresql-784e762e886e6f72f548da86a27cd2ead87dbd1c.tar.gz postgresql-784e762e886e6f72f548da86a27cd2ead87dbd1c.zip |
Support multi-argument UNNEST(), and TABLE() syntax for multiple functions.
This patch adds the ability to write TABLE( function1(), function2(), ...)
as a single FROM-clause entry. The result is the concatenation of the
first row from each function, followed by the second row from each
function, etc; with NULLs inserted if any function produces fewer rows than
others. This is believed to be a much more useful behavior than what
Postgres currently does with multiple SRFs in a SELECT list.
This syntax also provides a reasonable way to combine use of column
definition lists with WITH ORDINALITY: put the column definition list
inside TABLE(), where it's clear that it doesn't control the ordinality
column as well.
Also implement SQL-compliant multiple-argument UNNEST(), by turning
UNNEST(a,b,c) into TABLE(unnest(a), unnest(b), unnest(c)).
The SQL standard specifies TABLE() with only a single function, not
multiple functions, and it seems to require an implicit UNNEST() which is
not what this patch does. There may be something wrong with that reading
of the spec, though, because if it's right then the spec's TABLE() is just
a pointless alternative spelling of UNNEST(). After further review of
that, we might choose to adopt a different syntax for what this patch does,
but in any case this functionality seems clearly worthwhile.
Andrew Gierth, reviewed by Zoltán Böszörményi and Heikki Linnakangas, and
significantly revised by me
Diffstat (limited to 'src/backend/executor/nodeFunctionscan.c')
-rw-r--r-- | src/backend/executor/nodeFunctionscan.c | 595 |
1 files changed, 397 insertions, 198 deletions
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index 423e02f3541..3e386fd3811 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -22,13 +22,30 @@ */ #include "postgres.h" +#include "catalog/pg_type.h" #include "executor/nodeFunctionscan.h" #include "funcapi.h" #include "nodes/nodeFuncs.h" -#include "catalog/pg_type.h" +#include "parser/parsetree.h" +#include "utils/builtins.h" + + +/* + * Runtime data for each function being scanned. + */ +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 */ + int64 rowcount; /* # of rows in result set, -1 if not known */ + TupleTableSlot *func_slot; /* function result slot (or NULL) */ +} FunctionScanPerFuncState; static TupleTableSlot *FunctionNext(FunctionScanState *node); + /* ---------------------------------------------------------------- * Scan Support * ---------------------------------------------------------------- @@ -44,107 +61,182 @@ 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; - } + bool alldone; + int64 oldpos; + int funcno; + int att; /* * get information from the estate and scan state */ estate = node->ss.ps.state; direction = estate->es_direction; + scanslot = node->ss.ss_ScanTupleSlot; - 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) + if (node->simple) { - 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; + /* + * Fast path for the 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. No need to update ordinality or + * rowcounts either. + */ + Tuplestorestate *tstore = node->funcstates[0].tstore; - /* - * 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. - */ + /* + * 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->funcstates[0].tstore = tstore = + ExecMakeTableFunctionResult(node->funcstates[0].funcexpr, + node->ss.ps.ps_ExprContext, + node->funcstates[0].tupdesc, + 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); + } - ExecClearTuple(scanslot); + /* + * Get the next tuple from tuplestore. + */ + (void) tuplestore_gettupleslot(tstore, + ScanDirectionIsForward(direction), + false, + scanslot); + return scanslot; + } /* - * increment or decrement before checking for end-of-data, so that we can - * move off either end of the result by 1 (and no more than 1) without - * losing correct count. See PortalRunSelect for why we assume that we - * won't be called repeatedly in the end-of-data state. + * Increment or decrement ordinal counter before checking for end-of-data, + * so that we can move off either end of the result by 1 (and no more than + * 1) without losing correct count. See PortalRunSelect for why we can + * assume that we won't be called repeatedly in the end-of-data state. */ - + oldpos = node->ordinal; if (ScanDirectionIsForward(direction)) node->ordinal++; else node->ordinal--; - if (!TupIsNull(funcslot)) + /* + * Main loop over functions. + * + * We fetch the function results into func_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. + */ + ExecClearTuple(scanslot); + att = 0; + alldone = true; + for (funcno = 0; funcno < node->nfuncs; funcno++) { - int natts = funcslot->tts_tupleDescriptor->natts; - int i; + FunctionScanPerFuncState *fs = &node->funcstates[funcno]; + int i; - slot_getallattrs(funcslot); + /* + * If first time through, read all tuples from function and put them + * in a tuplestore. Subsequent calls just fetch tuples from + * tuplestore. + */ + if (fs->tstore == NULL) + { + fs->tstore = + ExecMakeTableFunctionResult(fs->funcexpr, + node->ss.ps.ps_ExprContext, + fs->tupdesc, + 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(fs->tstore); + } - for (i = 0; i < natts; ++i) + /* + * 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 (fs->rowcount != -1 && fs->rowcount < oldpos) + ExecClearTuple(fs->func_slot); + else + (void) tuplestore_gettupleslot(fs->tstore, + ScanDirectionIsForward(direction), + false, + fs->func_slot); + + if (TupIsNull(fs->func_slot)) { - scanslot->tts_values[i] = funcslot->tts_values[i]; - scanslot->tts_isnull[i] = funcslot->tts_isnull[i]; + /* + * 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) && fs->rowcount == -1) + fs->rowcount = node->ordinal; + + /* + * populate the result cols with nulls + */ + for (i = 0; i < fs->colcount; i++) + { + scanslot->tts_values[att] = (Datum) 0; + scanslot->tts_isnull[att] = true; + att++; + } } + else + { + /* + * we have a result, so just copy it to the result cols. + */ + slot_getallattrs(fs->func_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]; + att++; + } + + /* + * We're not done until every function result is exhausted; we pad + * the shorter results with nulls until then. + */ + alldone = false; + } + } - scanslot->tts_values[natts] = Int64GetDatumFast(node->ordinal); - scanslot->tts_isnull[natts] = false; + /* + * ordinal col is always last, per spec. + */ + if (node->ordinality) + { + scanslot->tts_values[att] = Int64GetDatumFast(node->ordinal); + scanslot->tts_isnull[att] = false; + } + /* + * If alldone, we just return the previously-cleared scanslot. Otherwise, + * finish creating the virtual tuple. + */ + if (!alldone) ExecStoreVirtualTuple(scanslot); - } return scanslot; } @@ -184,10 +276,13 @@ FunctionScanState * ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) { FunctionScanState *scanstate; - Oid funcrettype; - TypeFuncClass functypclass; - TupleDesc func_tupdesc = NULL; - TupleDesc scan_tupdesc = NULL; + RangeTblEntry *rte = rt_fetch(node->scan.scanrelid, + estate->es_range_table); + int nfuncs = list_length(node->functions); + TupleDesc scan_tupdesc; + int i, + natts; + ListCell *lc; /* check for unsupported flags */ Assert(!(eflags & EXEC_FLAG_MARK)); @@ -207,12 +302,37 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) scanstate->eflags = eflags; /* + * are we adding an ordinality column? + */ + scanstate->ordinality = node->funcordinality; + + scanstate->nfuncs = nfuncs; + if (nfuncs == 1 && !node->funcordinality) + scanstate->simple = true; + else + scanstate->simple = false; + + /* + * 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 */ ExecAssignExprContext(estate, &scanstate->ss.ps); + scanstate->ss.ps.ps_TupFromTlist = false; + /* * tuple table initialization */ @@ -220,16 +340,6 @@ 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 *) @@ -239,113 +349,165 @@ 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); + scanstate->funcstates = palloc(nfuncs * sizeof(FunctionScanPerFuncState)); - if (functypclass == TYPEFUNC_COMPOSITE) + natts = 0; + i = 0; + foreach(lc, node->functions) { - /* Composite data type, e.g. a table's row type */ - Assert(func_tupdesc); + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + Node *funcexpr = rtfunc->funcexpr; + int colcount = rtfunc->funccolcount; + FunctionScanPerFuncState *fs = &scanstate->funcstates[i]; + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; + + fs->funcexpr = ExecInitExpr((Expr *) funcexpr, (PlanState *) scanstate); /* - * 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. + * Don't allocate the tuplestores; the actual calls to the functions + * do that. NULL means that we have not called the function yet (or + * need to call it again after a rescan). */ + fs->tstore = NULL; + fs->rowcount = -1; - /* 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"); - } + /* + * Now determine if the function returns a simple or composite type, + * and build an appropriate tupdesc. Note that in the composite case, + * the function may now return more columns than it did when the plan + * was made; we have to ignore any columns beyond "colcount". + */ + functypclass = get_expr_result_type(funcexpr, + &funcrettype, + &tupdesc); - /* - * 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 (functypclass == TYPEFUNC_COMPOSITE) + { + /* 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); + } + else if (functypclass == TYPEFUNC_SCALAR) + { + /* Base data type, i.e. scalar */ + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + NULL, /* don't care about the name here */ + funcrettype, + -1, + 0); + TupleDescInitEntryCollation(tupdesc, + (AttrNumber) 1, + exprCollation(funcexpr)); + } + else if (functypclass == TYPEFUNC_RECORD) + { + tupdesc = 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); + } + else + { + /* crummy error message, but parser should have caught this */ + elog(ERROR, "function in FROM has unsupported return type"); + } + + fs->tupdesc = tupdesc; + fs->colcount = colcount; + + /* + * We only need separate slots for the function results if we are + * doing ordinality or multiple functions; otherwise, we'll fetch + * function results directly into the scan slot. + */ + if (!scanstate->simple) + { + fs->func_slot = ExecInitExtraTupleSlot(estate); + ExecSetSlotDescriptor(fs->func_slot, fs->tupdesc); + } + else + fs->func_slot = NULL; + + natts += colcount; + i++; + } /* - * 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. + * Create the combined TupleDesc * - * Without ordinality, the scan result tupdesc is the same as the - * function result tupdesc. (No need to make a copy.) + * If there is just one function without ordinality, the scan result + * tupdesc is the same as the function result tupdesc --- except that + * we may stuff new names into it below, so drop any rowtype label. */ - if (node->funcordinality) + if (scanstate->simple) { - int natts = func_tupdesc->natts; + scan_tupdesc = CreateTupleDescCopy(scanstate->funcstates[0].tupdesc); + scan_tupdesc->tdtypeid = RECORDOID; + scan_tupdesc->tdtypmod = -1; + } + else + { + AttrNumber attno = 0; - scan_tupdesc = CreateTupleDescCopyExtend(func_tupdesc, 1); + if (node->funcordinality) + natts++; - TupleDescInitEntry(scan_tupdesc, - natts + 1, - strVal(llast(node->funccolnames)), - INT8OID, - -1, - 0); + scan_tupdesc = CreateTemplateTupleDesc(natts, false); - BlessTupleDesc(scan_tupdesc); - } - else - scan_tupdesc = func_tupdesc; + for (i = 0; i < nfuncs; i++) + { + TupleDesc tupdesc = scanstate->funcstates[i].tupdesc; + int colcount = scanstate->funcstates[i].colcount; + int j; - scanstate->scan_tupdesc = scan_tupdesc; - scanstate->func_tupdesc = func_tupdesc; - ExecAssignScanType(&scanstate->ss, scan_tupdesc); + for (j = 1; j <= colcount; j++) + TupleDescCopyEntry(scan_tupdesc, ++attno, tupdesc, j); + } + + /* If doing ordinality, add a column of type "bigint" at the end */ + if (node->funcordinality) + { + TupleDescInitEntry(scan_tupdesc, + ++attno, + NULL, /* don't care about the name here */ + INT8OID, + -1, + 0); + } - if (scanstate->func_slot) - ExecSetSlotDescriptor(scanstate->func_slot, func_tupdesc); + Assert(attno == natts); + } /* - * Other node-specific setup + * Make sure the scan result tupdesc has the column names the query + * expects. This affects the output of constructs like row_to_json which + * read the column names from the passed-in tupdesc. */ - scanstate->ordinal = 0; - scanstate->tuplestorestate = NULL; + i = 0; + foreach(lc, rte->eref->colnames) + { + char *attname = strVal(lfirst(lc)); - scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr, - (PlanState *) scanstate); + if (i >= scan_tupdesc->natts) + break; /* shouldn't happen, but just in case */ + namestrcpy(&(scan_tupdesc->attrs[i]->attname), attname); + i++; + } - scanstate->ss.ps.ps_TupFromTlist = false; + ExecAssignScanType(&scanstate->ss, scan_tupdesc); /* * Initialize result tuple type and projection info. @@ -365,6 +527,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) void ExecEndFunctionScan(FunctionScanState *node) { + int i; + /* * Free the exprcontext */ @@ -375,15 +539,23 @@ 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 + * Release slots and tuplestore resources */ - if (node->tuplestorestate != NULL) - tuplestore_end(node->tuplestorestate); - node->tuplestorestate = NULL; + for (i = 0; i < node->nfuncs; i++) + { + FunctionScanPerFuncState *fs = &node->funcstates[i]; + + if (fs->func_slot) + ExecClearTuple(fs->func_slot); + + if (fs->tstore != NULL) + { + tuplestore_end(node->funcstates[i].tstore); + fs->tstore = NULL; + } + } } /* ---------------------------------------------------------------- @@ -395,31 +567,58 @@ ExecEndFunctionScan(FunctionScanState *node) void ExecReScanFunctionScan(FunctionScanState *node) { + FunctionScan *scan = (FunctionScan *) node->ss.ps.plan; + int i; + Bitmapset *chgparam = node->ss.ps.chgParam; + ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); - if (node->func_slot) - ExecClearTuple(node->func_slot); + for (i = 0; i < node->nfuncs; i++) + { + FunctionScanPerFuncState *fs = &node->funcstates[i]; - ExecScanReScan(&node->ss); + if (fs->func_slot) + ExecClearTuple(fs->func_slot); + } - node->ordinal = 0; + ExecScanReScan(&node->ss); /* - * If we haven't materialized yet, just return. + * Here we have a choice whether to drop the tuplestores (and recompute + * the function outputs) or just rescan them. We must recompute if an + * expression contains changed parameters, else we rescan. + * + * XXX maybe we should recompute if the function is volatile? But in + * general the executor doesn't conditionalize its actions on that. */ - if (!node->tuplestorestate) - return; + if (chgparam) + { + ListCell *lc; - /* - * 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) + i = 0; + foreach(lc, scan->functions) + { + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + + if (bms_overlap(chgparam, rtfunc->funcparams)) + { + if (node->funcstates[i].tstore != NULL) + { + tuplestore_end(node->funcstates[i].tstore); + node->funcstates[i].tstore = NULL; + } + node->funcstates[i].rowcount = -1; + } + i++; + } + } + + /* Reset ordinality counter */ + node->ordinal = 0; + + /* Make sure we rewind any remaining tuplestores */ + for (i = 0; i < node->nfuncs; i++) { - tuplestore_end(node->tuplestorestate); - node->tuplestorestate = NULL; + if (node->funcstates[i].tstore != NULL) + tuplestore_rescan(node->funcstates[i].tstore); } - else - tuplestore_rescan(node->tuplestorestate); } |