diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 1f18e5d..f3205e4 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -554,7 +554,6 @@ ExecSupportsBackwardScan(Plan *node) case T_SeqScan: case T_TidScan: - case T_FunctionScan: case T_ValuesScan: case T_CteScan: case T_Material: @@ -613,7 +612,6 @@ ExecMaterializesOutput(NodeTag plantype) switch (plantype) { case T_Material: - case T_FunctionScan: case T_TableFuncScan: case T_CteScan: case T_NamedTuplestoreScan: diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c index c8a3efc..86ef5ce 100644 --- a/src/backend/executor/execSRF.c +++ b/src/backend/executor/execSRF.c @@ -21,6 +21,7 @@ #include "access/htup_details.h" #include "catalog/objectaccess.h" #include "executor/execdebug.h" +#include "executor/nodeFunctionscan.h" #include "funcapi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" @@ -45,6 +46,9 @@ static void ExecPrepareTuplestoreResult(SetExprState *sexpr, Tuplestorestate *resultStore, TupleDesc resultDesc); static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc); +static void slot_puttuple_offset (TupleTableSlot *scanslot, TupleDesc expectedDesc, AttrNumber scanslot_off, TupleDesc resultdesc, Datum result); +static void slot_copyslot_offset (TupleTableSlot *scanslot, TupleDesc expectedDesc, AttrNumber scanslot_off, TupleDesc resultdesc, TupleTableSlot *result); +static void slot_putscalar_offset (TupleTableSlot *scanslot, TupleDesc expectedDesc, AttrNumber scanslot_off, Datum result, bool isNull); /* @@ -89,33 +93,102 @@ ExecInitTableFunctionResult(Expr *expr, return state; } +static void +ExecFetchFromTableFunctionTuplestore(SetExprState *setexpr, + TupleDesc expectedDesc, + TupleTableSlot *resultslot, + AttrNumber scanslot_off, + ExprDoneCond *isDone) +{ + MemoryContext oldContext; + bool foundTup; + + /* + * Have to make sure tuple in slot lives long enough, otherwise + * clearing the slot could end up trying to free something already + * freed. + */ + oldContext = MemoryContextSwitchTo(resultslot->tts_mcxt); + foundTup = tuplestore_gettupleslot(setexpr->funcResultStore, true, false, + setexpr->funcResultSlot); + MemoryContextSwitchTo(oldContext); + + if (foundTup) + { + *isDone = ExprMultipleResult; + + if (setexpr->funcReturnsTuple) + { + /* We must expand the whole tuple. */ + /* + * Copy it to the result cols. + */ + slot_getallattrs(setexpr->funcResultSlot); + + slot_copyslot_offset (resultslot, expectedDesc, scanslot_off, setexpr->funcResultSlot->tts_tupleDescriptor, setexpr->funcResultSlot); + } + else + { + bool isNull = false; + + /* Extract the first column and return it as a scalar. */ + Datum result = slot_getattr(setexpr->funcResultSlot, 1, &isNull); + + slot_putscalar_offset (resultslot, expectedDesc, scanslot_off, result, isNull); + } + } + else + { + /* Exhausted the tuplestore, so clean up */ + tuplestore_end(setexpr->funcResultStore); + setexpr->funcResultStore = NULL; + *isDone = ExprEndResult; + } +} + /* * ExecMakeTableFunctionResult * - * Evaluate a table function, producing a materialized result in a Tuplestore - * object. + * Evaluate a table function, storing a single row in scanslot starting at + * attribute scanslot_off. * * This is used by nodeFunctionscan.c. */ -Tuplestorestate * -ExecMakeTableFunctionResult(SetExprState *setexpr, +void +ExecMakeTableFunctionResult(FunctionScanPerFuncState *fs, ExprContext *econtext, MemoryContext argContext, - TupleDesc expectedDesc, - bool randomAccess) + TupleTableSlot *resultslot, + AttrNumber scanslot_off, + ExprDoneCond *isDone) { - Tuplestorestate *tupstore = NULL; - TupleDesc tupdesc = NULL; + SetExprState *setexpr = fs->setexpr; Oid funcrettype; bool returnsTuple; bool returnsSet = false; FunctionCallInfo fcinfo; PgStat_FunctionCallUsage fcusage; - ReturnSetInfo rsinfo; - HeapTupleData tmptup; + ReturnSetInfo *rsinfo; MemoryContext callerContext; MemoryContext oldcontext; - bool first_time = true; + +restart: + + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); + + /* + * If a previous call of the function returned a set result in the form of + * a tuplestore, continue reading rows from the tuplestore until it's + * empty. + */ + if (setexpr->funcResultStore) + { + ExecFetchFromTableFunctionTuplestore(setexpr, fs->tupdesc, resultslot, scanslot_off, isDone); + + /* No matter what, we are done here. */ + return; + } callerContext = CurrentMemoryContext; @@ -130,18 +203,21 @@ ExecMakeTableFunctionResult(SetExprState *setexpr, * resultinfo, but set it up anyway because we use some of the fields as * our own state variables. */ - rsinfo.type = T_ReturnSetInfo; - rsinfo.econtext = econtext; - rsinfo.expectedDesc = expectedDesc; - rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred); - if (randomAccess) - rsinfo.allowedModes |= (int) SFRM_Materialize_Random; - rsinfo.returnMode = SFRM_ValuePerCall; - /* isDone is filled below */ - rsinfo.setResult = NULL; - rsinfo.setDesc = NULL; - - fcinfo = palloc(SizeForFunctionCallInfo(list_length(setexpr->args))); + rsinfo = (ReturnSetInfo *) setexpr->fcinfo->resultinfo; + + if (rsinfo == NULL) + { + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + rsinfo = makeNode (ReturnSetInfo); + rsinfo->econtext = econtext; + rsinfo->expectedDesc = fs->tupdesc; + rsinfo->allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); + rsinfo->returnMode = SFRM_ValuePerCall; + setexpr->fcinfo->resultinfo = (Node *) rsinfo; + MemoryContextSwitchTo(oldcontext); + } + + fcinfo = setexpr->fcinfo; /* * Normally the passed expression tree will be a SetExprState, since the @@ -162,23 +238,32 @@ ExecMakeTableFunctionResult(SetExprState *setexpr, InitFunctionCallInfoData(*fcinfo, &(setexpr->func), list_length(setexpr->args), setexpr->fcinfo->fncollation, - NULL, (Node *) &rsinfo); + NULL, (Node *) rsinfo); /* * Evaluate the function's argument list. * - * We can't do this in the per-tuple context: the argument values - * would disappear when we reset that context in the inner loop. And - * the caller's CurrentMemoryContext is typically a query-lifespan - * context, so we don't want to leak memory there. We require the - * caller to pass a separate memory context that can be used for this, - * and can be reset each time through to avoid bloat. + * arguments is a list of expressions to evaluate before passing to the + * function manager. We skip the evaluation if it was already done in the + * previous call (ie, we are continuing the evaluation of a set-valued + * function). Otherwise, collect the current argument values into fcinfo. + * + * The arguments have to live in a context that lives at least until all + * rows from this SRF have been returned, otherwise ValuePerCall SRFs + * would reference freed memory after the first returned row. */ - MemoryContextReset(argContext); - oldcontext = MemoryContextSwitchTo(argContext); - ExecEvalFuncArgs(fcinfo, setexpr->args, econtext); - MemoryContextSwitchTo(oldcontext); - + if (!setexpr->setArgsValid) + { + oldcontext = MemoryContextSwitchTo(argContext); + ExecEvalFuncArgs(fcinfo, setexpr->args, econtext); + MemoryContextSwitchTo(oldcontext); + } + else + { + /* Reset flag (we may set it again below) */ + setexpr->setArgsValid = false; + } + /* * If function is strict, and there are any NULL arguments, skip * calling the function and act like it returned NULL (or an empty @@ -207,166 +292,161 @@ ExecMakeTableFunctionResult(SetExprState *setexpr, MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* - * Loop to handle the ValuePerCall protocol (which is also the same + * Handle the ValuePerCall protocol (which is also the same * behavior needed in the generic ExecEvalExpr path). */ - for (;;) + Datum result; + + /* Call the function or expression one time */ + if (!setexpr->elidedFuncState) { - Datum result; + pgstat_init_function_usage(fcinfo, &fcusage); - CHECK_FOR_INTERRUPTS(); + fcinfo->isnull = false; + rsinfo->isDone = ExprSingleResult; + result = FunctionCallInvoke(fcinfo); + pgstat_end_function_usage(&fcusage, + rsinfo->isDone != ExprMultipleResult); + } + else + { + result = + ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo->isnull); + rsinfo->isDone = ExprSingleResult; + } + + /* Which protocol does function want to use? */ + if (rsinfo->returnMode == SFRM_ValuePerCall) + { /* - * reset per-tuple memory context before each call of the function or - * expression. This cleans up any local memory the function may leak - * when called. + * Check for end of result set. */ - ResetExprContext(econtext); - - /* Call the function or expression one time */ - if (!setexpr->elidedFuncState) - { - pgstat_init_function_usage(fcinfo, &fcusage); - - fcinfo->isnull = false; - rsinfo.isDone = ExprSingleResult; - result = FunctionCallInvoke(fcinfo); - - pgstat_end_function_usage(&fcusage, - rsinfo.isDone != ExprMultipleResult); - } + if (rsinfo->isDone == ExprEndResult) + goto no_function_result; else - { - result = - ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo->isnull); - rsinfo.isDone = ExprSingleResult; - } - - /* Which protocol does function want to use? */ - if (rsinfo.returnMode == SFRM_ValuePerCall) { /* - * Check for end of result set. - */ - if (rsinfo.isDone == ExprEndResult) - break; - - /* - * If first time through, build tuplestore for result. For a - * scalar function result type, also make a suitable tupdesc. + * Save the current argument values to re-use on the next call. */ - if (first_time) + if (*isDone == ExprMultipleResult) { - oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); - rsinfo.setResult = tupstore; - if (!returnsTuple) + setexpr->setArgsValid = true; + /* Register cleanup callback if we didn't already */ + if (!setexpr->shutdown_reg) { - tupdesc = CreateTemplateTupleDesc(1); - TupleDescInitEntry(tupdesc, - (AttrNumber) 1, - "column", - funcrettype, - -1, - 0); - rsinfo.setDesc = tupdesc; + RegisterExprContextCallback(econtext, + ShutdownSetExpr, + PointerGetDatum(setexpr)); + setexpr->shutdown_reg = true; } + } + } + + HeapTupleHeader td = NULL; + + /* + * Obtain a suitable tupdesc, when we first encounter a non-NULL result. + */ + if (fs->returned_tupdesc == NULL) + { + if (!returnsTuple) + { + /* + * This is the first non-NULL result from the + * function. Use the type info embedded in the + * rowtype Datum to look up the needed tupdesc. Make + * a copy for the query. + */ + // FIXME: is this a too-long lived context? + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + fs->returned_tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitEntry(fs->tupdesc, + (AttrNumber) 1, + "column", + funcrettype, + -1, + 0); + MemoryContextSwitchTo(oldcontext); + } + else if (!fcinfo->isnull) + { + td = DatumGetHeapTupleHeader(result); + + /* + * This is the first non-NULL result from the + * function. Use the type info embedded in the + * rowtype Datum to look up the needed tupdesc. Make + * a copy for the query. + */ + // FIXME: is this a too-long lived context? + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + fs->returned_tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td), + HeapTupleHeaderGetTypMod(td)); MemoryContextSwitchTo(oldcontext); } + } - /* - * Store current resultset item. - */ - if (returnsTuple) + /* + * Store current resultset item. + */ + if (returnsTuple) + { + if (!fcinfo->isnull) { - if (!fcinfo->isnull) - { - HeapTupleHeader td = DatumGetHeapTupleHeader(result); - - if (tupdesc == NULL) - { - /* - * This is the first non-NULL result from the - * function. Use the type info embedded in the - * rowtype Datum to look up the needed tupdesc. Make - * a copy for the query. - */ - oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td), - HeapTupleHeaderGetTypMod(td)); - rsinfo.setDesc = tupdesc; - MemoryContextSwitchTo(oldcontext); - } - else - { - /* - * Verify all later returned rows have same subtype; - * necessary in case the type is RECORD. - */ - if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid || - HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("rows returned by function are not all of the same row type"))); - } - - /* - * tuplestore_puttuple needs a HeapTuple not a bare - * HeapTupleHeader, but it doesn't need all the fields. - */ - tmptup.t_len = HeapTupleHeaderGetDatumLength(td); - tmptup.t_data = td; - - tuplestore_puttuple(tupstore, &tmptup); - } - else - { - /* - * NULL result from a tuple-returning function; expand it - * to a row of all nulls. We rely on the expectedDesc to - * form such rows. (Note: this would be problematic if - * tuplestore_putvalues saved the tdtypeid/tdtypmod from - * the provided descriptor, since that might not match - * what we get from the function itself. But it doesn't.) - */ - int natts = expectedDesc->natts; - bool *nullflags; - - nullflags = (bool *) palloc(natts * sizeof(bool)); - memset(nullflags, true, natts * sizeof(bool)); - tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); - } + if (td == NULL) + td = DatumGetHeapTupleHeader(result); + + /* + * Verify all later returned rows have same subtype; + * necessary in case the type is RECORD. + */ + if (HeapTupleHeaderGetTypeId(td) != fs->returned_tupdesc->tdtypeid || + HeapTupleHeaderGetTypMod(td) != fs->returned_tupdesc->tdtypmod) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("rows returned by function are not all of the same row type"))); + + slot_puttuple_offset (resultslot, fs->tupdesc, scanslot_off, rsinfo->setDesc, result); } else { - /* Scalar-type case: just store the function result */ - tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo->isnull); + /* + * NULL result from a tuple-returning function; expand it + * to a row of all nulls. We rely on the expectedDesc to + * form such rows. (Note: this would be problematic if + * tuplestore_putvalues saved the tdtypeid/tdtypmod from + * the provided descriptor, since that might not match + * what we get from the function itself. But it doesn't.) + */ + slot_puttuple_offset (resultslot, fs->tupdesc, scanslot_off, rsinfo->setDesc, 0); } - - /* - * Are we done? - */ - if (rsinfo.isDone != ExprMultipleResult) - break; } - else if (rsinfo.returnMode == SFRM_Materialize) + else { - /* check we're on the same page as the function author */ - if (!first_time || rsinfo.isDone != ExprSingleResult) - ereport(ERROR, - (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), - errmsg("table-function protocol for materialize mode was not followed"))); - /* Done evaluating the set result */ - break; + /* Scalar-type case: just store the function result */ + slot_putscalar_offset (resultslot, fs->tupdesc, scanslot_off, result, fcinfo->isnull); } - else + } + else if (rsinfo->returnMode == SFRM_Materialize) + { + /* check we're on the same page as the function author */ + if (rsinfo->isDone != ExprSingleResult) ereport(ERROR, (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), - errmsg("unrecognized table-function returnMode: %d", - (int) rsinfo.returnMode))); - - first_time = false; + errmsg("table-function protocol for materialize mode was not followed"))); + /* prepare to return values from the tuplestore */ + ExecPrepareTuplestoreResult(setexpr, econtext, + rsinfo->setResult, + rsinfo->setDesc); + /* Done evaluating the set result */ + goto restart; } + else + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("unrecognized table-function returnMode: %d", + (int) rsinfo->returnMode))); no_function_result: @@ -376,20 +456,11 @@ no_function_result: * non-set-returning function then insert a single all-nulls row. As * above, we depend on the expectedDesc to manufacture the dummy row. */ - if (rsinfo.setResult == NULL) + if (rsinfo->setResult == NULL) { - MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); - rsinfo.setResult = tupstore; if (!returnsSet) { - int natts = expectedDesc->natts; - bool *nullflags; - - MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - nullflags = (bool *) palloc(natts * sizeof(bool)); - memset(nullflags, true, natts * sizeof(bool)); - tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); + slot_puttuple_offset (resultslot, fs->tupdesc, scanslot_off, rsinfo->setDesc, 0); } } @@ -397,23 +468,25 @@ no_function_result: * If function provided a tupdesc, cross-check it. We only really need to * do this for functions returning RECORD, but might as well do it always. */ - if (rsinfo.setDesc) + if (rsinfo->setDesc) { - tupledesc_match(expectedDesc, rsinfo.setDesc); + tupledesc_match(fs->tupdesc, 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 (rsinfo.setDesc->tdrefcount == -1) - FreeTupleDesc(rsinfo.setDesc); + //if (rsinfo->setDesc->tdrefcount == -1) + // FreeTupleDesc(rsinfo->setDesc); + // FIXME: work out when to release this... } + + *isDone = rsinfo->isDone; MemoryContextSwitchTo(callerContext); - /* All done, pass back the tuplestore */ - return rsinfo.setResult; + /* All done, result is in the tuplestore */ } @@ -486,7 +559,7 @@ ExecMakeFunctionResultSet(SetExprState *fcache, Datum result; FunctionCallInfo fcinfo; PgStat_FunctionCallUsage fcusage; - ReturnSetInfo rsinfo; + ReturnSetInfo *rsinfo; bool callit; int i; @@ -569,16 +642,23 @@ restart: */ /* Prepare a resultinfo node for communication. */ - fcinfo->resultinfo = (Node *) &rsinfo; - rsinfo.type = T_ReturnSetInfo; - rsinfo.econtext = econtext; - rsinfo.expectedDesc = fcache->funcResultDesc; - rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); - /* note we do not set SFRM_Materialize_Random or _Preferred */ - rsinfo.returnMode = SFRM_ValuePerCall; - /* isDone is filled below */ - rsinfo.setResult = NULL; - rsinfo.setDesc = NULL; + rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + if (rsinfo == NULL) + { + MemoryContext oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + rsinfo = makeNode (ReturnSetInfo); + rsinfo->type = T_ReturnSetInfo; + rsinfo->econtext = econtext; + rsinfo->expectedDesc = fcache->funcResultDesc; + rsinfo->allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); + rsinfo->returnMode = SFRM_ValuePerCall; + /* isDone is filled below */ + rsinfo->setResult = NULL; + rsinfo->setDesc = NULL; + fcinfo->resultinfo = (Node *) rsinfo; + MemoryContextSwitchTo(oldcontext); + } /* * If function is strict, and there are any NULL arguments, skip calling @@ -602,13 +682,13 @@ restart: pgstat_init_function_usage(fcinfo, &fcusage); fcinfo->isnull = false; - rsinfo.isDone = ExprSingleResult; + rsinfo->isDone = ExprSingleResult; result = FunctionCallInvoke(fcinfo); *isNull = fcinfo->isnull; - *isDone = rsinfo.isDone; + *isDone = rsinfo->isDone; pgstat_end_function_usage(&fcusage, - rsinfo.isDone != ExprMultipleResult); + rsinfo->isDone != ExprMultipleResult); } else { @@ -619,7 +699,7 @@ restart: } /* Which protocol does function want to use? */ - if (rsinfo.returnMode == SFRM_ValuePerCall) + if (rsinfo->returnMode == SFRM_ValuePerCall) { if (*isDone != ExprEndResult) { @@ -640,19 +720,20 @@ restart: } } } - else if (rsinfo.returnMode == SFRM_Materialize) + else if (rsinfo->returnMode == SFRM_Materialize) { /* check we're on the same page as the function author */ - if (rsinfo.isDone != ExprSingleResult) + if (rsinfo->isDone != ExprSingleResult) ereport(ERROR, (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), errmsg("table-function protocol for materialize mode was not followed"))); - if (rsinfo.setResult != NULL) + if (rsinfo->setResult != NULL) { /* prepare to return values from the tuplestore */ ExecPrepareTuplestoreResult(fcache, econtext, - rsinfo.setResult, - rsinfo.setDesc); + rsinfo->setResult, + rsinfo->setDesc); + /* loop back to top to start returning from tuplestore */ goto restart; } @@ -665,7 +746,7 @@ restart: ereport(ERROR, (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), errmsg("unrecognized table-function returnMode: %d", - (int) rsinfo.returnMode))); + (int) rsinfo->returnMode))); return result; } @@ -712,6 +793,7 @@ init_sexpr(Oid foid, Oid input_collation, Expr *node, InitFunctionCallInfoData(*sexpr->fcinfo, &(sexpr->func), numargs, input_collation, NULL, NULL); + sexpr->fcinfo->resultinfo = NULL; /* If function returns set, check if that's allowed by caller */ if (sexpr->func.fn_retset && !allowSRF) @@ -804,6 +886,12 @@ ShutdownSetExpr(Datum arg) /* Clear any active set-argument state */ sexpr->setArgsValid = false; + + if (sexpr->fcinfo->resultinfo != NULL) + { + pfree (sexpr->fcinfo->resultinfo); + sexpr->fcinfo->resultinfo = NULL; + } /* execUtils will deregister the callback... */ sexpr->shutdown_reg = false; @@ -960,3 +1048,67 @@ tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc) i + 1))); } } + +static void +slot_puttuple_offset (TupleTableSlot *scanslot, TupleDesc expectedDesc, AttrNumber scanslot_off, + TupleDesc resultdesc, Datum result) +{ + if (result != 0) + { + HeapTupleHeader td = DatumGetHeapTupleHeader(result); + + /* + * tuplestore_puttuple needs a HeapTuple not a bare + * HeapTupleHeader, but it doesn't need all the fields. + */ + HeapTupleData tmptup; + tmptup.t_len = HeapTupleHeaderGetDatumLength(td); + tmptup.t_data = td; + + heap_deform_tuple (&tmptup, expectedDesc, &(scanslot->tts_values[scanslot_off]), &(scanslot->tts_isnull[scanslot_off])); + } + else + { + /* Ensure any remaining result cols are initialsed to NULL. */ + for (int i = 0; i < expectedDesc->natts; i++) + { + scanslot->tts_values[scanslot_off + i] = (Datum) 0; + scanslot->tts_isnull[scanslot_off + i] = true; + } + } +} + +static void +slot_copyslots_offset (TupleTableSlot *scanslot, TupleDesc expectedDesc, AttrNumber scanslot_off, + int natts, Datum *datums, bool *isnulls) +{ + int i; + for (i = 0; i < natts; i++) + { + if (i >= expectedDesc->natts) + break; + + scanslot->tts_values[scanslot_off + i] = datums[i]; + scanslot->tts_isnull[scanslot_off + i] = isnulls[i]; + } + + /* Ensure any remaining result cols are initialsed to NULL. */ + for (; i < expectedDesc->natts; i++) + { + scanslot->tts_values[scanslot_off + i] = (Datum) 0; + scanslot->tts_isnull[scanslot_off + i] = true; + } +} + +static void +slot_copyslot_offset (TupleTableSlot *scanslot, TupleDesc expectedDesc, AttrNumber scanslot_off, + TupleDesc resultdesc, TupleTableSlot *result) +{ + slot_copyslots_offset (scanslot, expectedDesc, scanslot_off, resultdesc->natts, &(result->tts_values[0]), &(result->tts_isnull[0])); +} + +static void +slot_putscalar_offset (TupleTableSlot *scanslot, TupleDesc expectedDesc, AttrNumber scanslot_off, Datum result, bool isNull) +{ + slot_copyslots_offset (scanslot, expectedDesc, scanslot_off, 1, &result, &isNull); +} diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index 0370f2e..34054b7 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -30,19 +30,6 @@ #include "utils/memutils.h" -/* - * Runtime data for each function being scanned. - */ -typedef struct FunctionScanPerFuncState -{ - SetExprState *setexpr; /* 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); @@ -61,59 +48,42 @@ FunctionNext(FunctionScanState *node) { EState *estate; ScanDirection direction; - TupleTableSlot *scanslot; + TupleTableSlot *resultslot; + MemoryContext oldcontext; + ExprDoneCond doneCond; bool alldone; int64 oldpos; int funcno; int att; + // FIXME: assert not backwards + /* * get information from the estate and scan state */ estate = node->ss.ps.state; direction = estate->es_direction; - scanslot = node->ss.ss_ScanTupleSlot; + resultslot = node->ss.ss_ScanTupleSlot; + ExecClearTuple(resultslot); + + /* Call SRFs, as well as plain expressions, in per-tuple context */ + oldcontext = MemoryContextSwitchTo(node->ss.ps.ps_ExprContext->ecxt_per_tuple_memory); + if (node->simple) { /* - * 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; - - /* - * 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].setexpr, - node->ss.ps.ps_ExprContext, - node->argcontext, - 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); - } - - /* - * Get the next tuple from tuplestore. + * Read tuple from function and put it in the scanslot. */ - (void) tuplestore_gettupleslot(tstore, - ScanDirectionIsForward(direction), - false, - scanslot); - return scanslot; + ExecMakeTableFunctionResult(&node->funcstates[0], + node->ss.ps.ps_ExprContext, + node->argcontext, + resultslot, + 0, &doneCond); + + alldone = (doneCond == ExprEndResult); + + goto return_resultslot; } /* @@ -135,93 +105,31 @@ FunctionNext(FunctionScanState *node) * 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++) { FunctionScanPerFuncState *fs = &node->funcstates[funcno]; - int i; /* - * If first time through, read all tuples from function and put them - * in a tuplestore. Subsequent calls just fetch tuples from - * tuplestore. + * Read a tuples from function and put it in the scanslot. */ - if (fs->tstore == NULL) - { - fs->tstore = - ExecMakeTableFunctionResult(fs->setexpr, - node->ss.ps.ps_ExprContext, - node->argcontext, - 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); - } - - /* - * 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)) - { - /* - * 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 + ExecMakeTableFunctionResult(fs, + node->ss.ps.ps_ExprContext, + node->argcontext, + resultslot, + att, + &doneCond); + + if (doneCond != ExprEndResult) { - /* - * 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; } + att += fs->colcount; } /* @@ -229,18 +137,23 @@ FunctionNext(FunctionScanState *node) */ if (node->ordinality) { - scanslot->tts_values[att] = Int64GetDatumFast(node->ordinal); - scanslot->tts_isnull[att] = false; + resultslot->tts_values[att] = Int64GetDatumFast(node->ordinal); + resultslot->tts_isnull[att] = false; } +return_resultslot: + MemoryContextSwitchTo(oldcontext); + + if (alldone) + return NULL; + /* * If alldone, we just return the previously-cleared scanslot. Otherwise, * finish creating the virtual tuple. */ - if (!alldone) - ExecStoreVirtualTuple(scanslot); + ExecStoreVirtualTuple(resultslot); - return scanslot; + return resultslot; } /* @@ -353,14 +266,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) scanstate->ss.ps.ps_ExprContext, &scanstate->ss.ps); - /* - * 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; - /* * Now determine if the function returns a simple or composite type, * and build an appropriate tupdesc. Note that in the composite case, @@ -416,19 +321,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) 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, fs->tupdesc, - &TTSOpsMinimalTuple); - } - else - fs->func_slot = NULL; + fs->returned_tupdesc = NULL; /* will be initialzied during FunctionNext */ natts += colcount; i++; @@ -521,8 +414,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) void ExecEndFunctionScan(FunctionScanState *node) { - int i; - /* * Free the exprcontext */ @@ -534,23 +425,6 @@ ExecEndFunctionScan(FunctionScanState *node) if (node->ss.ps.ps_ResultTupleSlot) ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); ExecClearTuple(node->ss.ss_ScanTupleSlot); - - /* - * Release slots and tuplestore resources - */ - 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; - } - } } /* ---------------------------------------------------------------- @@ -571,9 +445,12 @@ ExecReScanFunctionScan(FunctionScanState *node) for (i = 0; i < node->nfuncs; i++) { FunctionScanPerFuncState *fs = &node->funcstates[i]; - - if (fs->func_slot) - ExecClearTuple(fs->func_slot); + + if (fs->returned_tupdesc != NULL) + { + FreeTupleDesc (fs->returned_tupdesc); + fs->returned_tupdesc = NULL; + } } ExecScanReScan(&node->ss); @@ -597,12 +474,7 @@ ExecReScanFunctionScan(FunctionScanState *node) 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; + // FIXME: trigger something...! } i++; } @@ -614,7 +486,6 @@ ExecReScanFunctionScan(FunctionScanState *node) /* Make sure we rewind any remaining tuplestores */ for (i = 0; i < node->nfuncs; i++) { - if (node->funcstates[i].tstore != NULL) - tuplestore_rescan(node->funcstates[i].tstore); + // FIXME: trigger rewind } } diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index d056fd6..c40f6e6 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -405,11 +405,12 @@ extern bool ExecCheck(ExprState *state, ExprContext *context); */ extern SetExprState *ExecInitTableFunctionResult(Expr *expr, ExprContext *econtext, PlanState *parent); -extern Tuplestorestate *ExecMakeTableFunctionResult(SetExprState *setexpr, +extern void ExecMakeTableFunctionResult(struct FunctionScanPerFuncState *fs, ExprContext *econtext, MemoryContext argContext, - TupleDesc expectedDesc, - bool randomAccess); + TupleTableSlot *scanslot, + AttrNumber scanslot_off, + ExprDoneCond *isDone); extern SetExprState *ExecInitFunctionResultSet(Expr *expr, ExprContext *econtext, PlanState *parent); extern Datum ExecMakeFunctionResultSet(SetExprState *fcache, diff --git a/src/include/executor/nodeFunctionscan.h b/src/include/executor/nodeFunctionscan.h index 4f7d60d..9362952 100644 --- a/src/include/executor/nodeFunctionscan.h +++ b/src/include/executor/nodeFunctionscan.h @@ -16,6 +16,17 @@ #include "nodes/execnodes.h" +/* + * Runtime data for each function being scanned. + */ +typedef struct FunctionScanPerFuncState +{ + SetExprState *setexpr; /* state of the expression being evaluated */ + TupleDesc tupdesc; /* desc of the function result type */ + int colcount; /* expected number of result columns */ + TupleDesc returned_tupdesc; /* desc of the function's last-returned result type */ +} FunctionScanPerFuncState; + extern FunctionScanState *ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags); extern void ExecEndFunctionScan(FunctionScanState *node); extern void ExecReScanFunctionScan(FunctionScanState *node);