From 02f8c9a38297459c2664044ea6d98d91678a4d79 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 1 Dec 2002 20:27:32 +0000 Subject: [PATCH] Fix ExecMakeTableFunctionResult() to work with generic expressions as well as function calls. This is needed for cases where the planner has constant-folded or inlined the original function call. Possibly we should back-patch this change into 7.3 branch as well. --- src/backend/executor/execQual.c | 157 +++++++++++++++--------- src/backend/executor/nodeFunctionscan.c | 4 +- src/include/executor/executor.h | 4 +- 3 files changed, 103 insertions(+), 62 deletions(-) diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 90a38f31ee..cb31c025df 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.111 2002/11/30 21:25:04 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.112 2002/12/01 20:27:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -40,6 +40,7 @@ #include "executor/functions.h" #include "executor/nodeSubplan.h" #include "miscadmin.h" +#include "parser/parse_expr.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/fcache.h" @@ -820,80 +821,109 @@ ExecMakeFunctionResult(FunctionCachePtr fcache, * object. (If function returns an empty set, we just return NULL instead.) */ Tuplestorestate * -ExecMakeTableFunctionResult(Expr *funcexpr, +ExecMakeTableFunctionResult(Node *funcexpr, ExprContext *econtext, TupleDesc expectedDesc, TupleDesc *returnDesc) { Tuplestorestate *tupstore = NULL; TupleDesc tupdesc = NULL; - Func *func; - List *argList; - FunctionCachePtr fcache; + Oid funcrettype; FunctionCallInfoData fcinfo; ReturnSetInfo rsinfo; - ExprDoneCond argDone; MemoryContext callerContext; MemoryContext oldcontext; TupleTableSlot *slot; + bool direct_function_call; bool first_time = true; bool returnsTuple = false; - /* Extract data from function-call expression node */ - if (!funcexpr || !IsA(funcexpr, Expr) ||funcexpr->opType != FUNC_EXPR) - elog(ERROR, "ExecMakeTableFunctionResult: expression is not a function call"); - func = (Func *) funcexpr->oper; - argList = funcexpr->args; - /* - * get the fcache from the Func node. If it is NULL, then initialize - * it + * Normally the passed expression tree will be a FUNC_EXPR, since the + * grammar only allows a function call at the top level of a table + * function reference. However, if the function doesn't return set then + * the planner might have replaced the function call via constant-folding + * or inlining. So if we see any other kind of expression node, execute + * it via the general ExecEvalExpr() code; the only difference is that + * we don't get a chance to pass a special ReturnSetInfo to any functions + * buried in the expression. */ - fcache = func->func_fcache; - if (fcache == NULL) + if (funcexpr && + IsA(funcexpr, Expr) && + ((Expr *) funcexpr)->opType == FUNC_EXPR) { - fcache = init_fcache(func->funcid, length(argList), - econtext->ecxt_per_query_memory); - func->func_fcache = fcache; - } + Func *func; + List *argList; + FunctionCachePtr fcache; + ExprDoneCond argDone; - /* - * Evaluate the function's argument list. - * - * Note: ideally, we'd do this in the per-tuple context, but then the - * argument values would disappear when we reset the context in the - * inner loop. So do it in caller context. Perhaps we should make a - * separate context just to hold the evaluated arguments? - */ - MemSet(&fcinfo, 0, sizeof(fcinfo)); - fcinfo.flinfo = &(fcache->func); - argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext); - /* We don't allow sets in the arguments of the table function */ - if (argDone != ExprSingleResult) - elog(ERROR, "Set-valued function called in context that cannot accept a set"); + /* + * This path is similar to ExecMakeFunctionResult. + */ + direct_function_call = true; - /* - * If function is strict, and there are any NULL arguments, skip - * calling the function and return NULL (actually an empty set). - */ - if (fcache->func.fn_strict) - { - int i; + funcrettype = ((Expr *) funcexpr)->typeOid; + func = (Func *) ((Expr *) funcexpr)->oper; + argList = ((Expr *) funcexpr)->args; - for (i = 0; i < fcinfo.nargs; i++) + /* + * get the fcache from the Func node. If it is NULL, then initialize + * it + */ + fcache = func->func_fcache; + if (fcache == NULL) { - if (fcinfo.argnull[i]) + fcache = init_fcache(func->funcid, length(argList), + econtext->ecxt_per_query_memory); + func->func_fcache = fcache; + } + + /* + * Evaluate the function's argument list. + * + * Note: ideally, we'd do this in the per-tuple context, but then the + * argument values would disappear when we reset the context in the + * inner loop. So do it in caller context. Perhaps we should make a + * separate context just to hold the evaluated arguments? + */ + MemSet(&fcinfo, 0, sizeof(fcinfo)); + fcinfo.flinfo = &(fcache->func); + argDone = ExecEvalFuncArgs(&fcinfo, argList, econtext); + /* We don't allow sets in the arguments of the table function */ + if (argDone != ExprSingleResult) + elog(ERROR, "Set-valued function called in context that cannot accept a set"); + + /* + * If function is strict, and there are any NULL arguments, skip + * calling the function and return NULL (actually an empty set). + */ + if (fcache->func.fn_strict) + { + int i; + + for (i = 0; i < fcinfo.nargs; i++) { - *returnDesc = NULL; - return NULL; + if (fcinfo.argnull[i]) + { + *returnDesc = NULL; + return NULL; + } } } } + else + { + /* Treat funcexpr as a generic expression */ + direct_function_call = false; + funcrettype = exprType(funcexpr); + } /* * Prepare a resultinfo node for communication. We always do this * even if not expecting a set result, so that we can pass - * expectedDesc. + * expectedDesc. In the generic-expression case, the expression + * doesn't actually get to see the resultinfo, but set it up anyway + * because we use some of the fields as our own state variables. */ fcinfo.resultinfo = (Node *) &rsinfo; rsinfo.type = T_ReturnSetInfo; @@ -906,12 +936,13 @@ ExecMakeTableFunctionResult(Expr *funcexpr, rsinfo.setDesc = NULL; /* - * Switch to short-lived context for calling the function. + * Switch to short-lived context for calling the function or expression. */ callerContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); /* - * Loop to handle the ValuePerCall protocol. + * Loop to handle the ValuePerCall protocol (which is also the same + * behavior needed in the generic ExecEvalExpr path). */ for (;;) { @@ -920,15 +951,23 @@ ExecMakeTableFunctionResult(Expr *funcexpr, /* * reset per-tuple memory context before each call of the - * function. This cleans up any local memory the function may leak - * when called. + * function or expression. This cleans up any local memory the + * function may leak when called. */ ResetExprContext(econtext); - /* Call the function one time */ - fcinfo.isnull = false; - rsinfo.isDone = ExprSingleResult; - result = FunctionCallInvoke(&fcinfo); + /* Call the function or expression one time */ + if (direct_function_call) + { + fcinfo.isnull = false; + rsinfo.isDone = ExprSingleResult; + result = FunctionCallInvoke(&fcinfo); + } + else + { + result = ExecEvalExpr(funcexpr, econtext, + &fcinfo.isnull, &rsinfo.isDone); + } /* Which protocol does function want to use? */ if (rsinfo.returnMode == SFRM_ValuePerCall) @@ -949,8 +988,6 @@ ExecMakeTableFunctionResult(Expr *funcexpr, */ if (first_time) { - Oid funcrettype = funcexpr->typeOid; - oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); if (funcrettype == RECORDOID || get_typtype(funcrettype) == 'c') @@ -960,7 +997,9 @@ ExecMakeTableFunctionResult(Expr *funcexpr, * TupleTableSlot; use its descriptor */ slot = (TupleTableSlot *) DatumGetPointer(result); - if (fcinfo.isnull || !slot || !IsA(slot, TupleTableSlot) || + if (fcinfo.isnull || + !slot || + !IsA(slot, TupleTableSlot) || !slot->ttc_tupleDescriptor) elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple"); tupdesc = CreateTupleDescCopy(slot->ttc_tupleDescriptor); @@ -993,7 +1032,9 @@ ExecMakeTableFunctionResult(Expr *funcexpr, if (returnsTuple) { slot = (TupleTableSlot *) DatumGetPointer(result); - if (fcinfo.isnull || !slot || !IsA(slot, TupleTableSlot) || + if (fcinfo.isnull || + !slot || + !IsA(slot, TupleTableSlot) || TupIsNull(slot)) elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple"); tuple = slot->val; diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index cf8d74a06f..97da70b2b7 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.12 2002/09/04 20:31:18 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.13 2002/12/01 20:27:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -78,7 +78,7 @@ FunctionNext(FunctionScan *node) TupleDesc funcTupdesc; scanstate->tuplestorestate = tuplestorestate = - ExecMakeTableFunctionResult((Expr *) scanstate->funcexpr, + ExecMakeTableFunctionResult(scanstate->funcexpr, econtext, scanstate->tupdesc, &funcTupdesc); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 312c56fa85..33b83bce6f 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: executor.h,v 1.79 2002/11/30 05:21:03 tgl Exp $ + * $Id: executor.h,v 1.80 2002/12/01 20:27:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -81,7 +81,7 @@ extern Datum ExecMakeFunctionResult(FunctionCachePtr fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -extern Tuplestorestate *ExecMakeTableFunctionResult(Expr *funcexpr, +extern Tuplestorestate *ExecMakeTableFunctionResult(Node *funcexpr, ExprContext *econtext, TupleDesc expectedDesc, TupleDesc *returnDesc);