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.
This commit is contained in:
parent
9ee7409ef7
commit
02f8c9a382
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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/functions.h"
|
||||||
#include "executor/nodeSubplan.h"
|
#include "executor/nodeSubplan.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
#include "parser/parse_expr.h"
|
||||||
#include "utils/array.h"
|
#include "utils/array.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/fcache.h"
|
#include "utils/fcache.h"
|
||||||
@ -820,80 +821,109 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
* object. (If function returns an empty set, we just return NULL instead.)
|
* object. (If function returns an empty set, we just return NULL instead.)
|
||||||
*/
|
*/
|
||||||
Tuplestorestate *
|
Tuplestorestate *
|
||||||
ExecMakeTableFunctionResult(Expr *funcexpr,
|
ExecMakeTableFunctionResult(Node *funcexpr,
|
||||||
ExprContext *econtext,
|
ExprContext *econtext,
|
||||||
TupleDesc expectedDesc,
|
TupleDesc expectedDesc,
|
||||||
TupleDesc *returnDesc)
|
TupleDesc *returnDesc)
|
||||||
{
|
{
|
||||||
Tuplestorestate *tupstore = NULL;
|
Tuplestorestate *tupstore = NULL;
|
||||||
TupleDesc tupdesc = NULL;
|
TupleDesc tupdesc = NULL;
|
||||||
Func *func;
|
Oid funcrettype;
|
||||||
List *argList;
|
|
||||||
FunctionCachePtr fcache;
|
|
||||||
FunctionCallInfoData fcinfo;
|
FunctionCallInfoData fcinfo;
|
||||||
ReturnSetInfo rsinfo;
|
ReturnSetInfo rsinfo;
|
||||||
ExprDoneCond argDone;
|
|
||||||
MemoryContext callerContext;
|
MemoryContext callerContext;
|
||||||
MemoryContext oldcontext;
|
MemoryContext oldcontext;
|
||||||
TupleTableSlot *slot;
|
TupleTableSlot *slot;
|
||||||
|
bool direct_function_call;
|
||||||
bool first_time = true;
|
bool first_time = true;
|
||||||
bool returnsTuple = false;
|
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
|
* Normally the passed expression tree will be a FUNC_EXPR, since the
|
||||||
* it
|
* 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 (funcexpr &&
|
||||||
if (fcache == NULL)
|
IsA(funcexpr, Expr) &&
|
||||||
|
((Expr *) funcexpr)->opType == FUNC_EXPR)
|
||||||
{
|
{
|
||||||
fcache = init_fcache(func->funcid, length(argList),
|
Func *func;
|
||||||
econtext->ecxt_per_query_memory);
|
List *argList;
|
||||||
func->func_fcache = fcache;
|
FunctionCachePtr fcache;
|
||||||
}
|
ExprDoneCond argDone;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Evaluate the function's argument list.
|
* This path is similar to ExecMakeFunctionResult.
|
||||||
*
|
*/
|
||||||
* Note: ideally, we'd do this in the per-tuple context, but then the
|
direct_function_call = true;
|
||||||
* 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");
|
|
||||||
|
|
||||||
/*
|
funcrettype = ((Expr *) funcexpr)->typeOid;
|
||||||
* If function is strict, and there are any NULL arguments, skip
|
func = (Func *) ((Expr *) funcexpr)->oper;
|
||||||
* calling the function and return NULL (actually an empty set).
|
argList = ((Expr *) funcexpr)->args;
|
||||||
*/
|
|
||||||
if (fcache->func.fn_strict)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
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;
|
if (fcinfo.argnull[i])
|
||||||
return NULL;
|
{
|
||||||
|
*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
|
* Prepare a resultinfo node for communication. We always do this
|
||||||
* even if not expecting a set result, so that we can pass
|
* 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;
|
fcinfo.resultinfo = (Node *) &rsinfo;
|
||||||
rsinfo.type = T_ReturnSetInfo;
|
rsinfo.type = T_ReturnSetInfo;
|
||||||
@ -906,12 +936,13 @@ ExecMakeTableFunctionResult(Expr *funcexpr,
|
|||||||
rsinfo.setDesc = NULL;
|
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);
|
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 (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
@ -920,15 +951,23 @@ ExecMakeTableFunctionResult(Expr *funcexpr,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* reset per-tuple memory context before each call of the
|
* reset per-tuple memory context before each call of the
|
||||||
* function. This cleans up any local memory the function may leak
|
* function or expression. This cleans up any local memory the
|
||||||
* when called.
|
* function may leak when called.
|
||||||
*/
|
*/
|
||||||
ResetExprContext(econtext);
|
ResetExprContext(econtext);
|
||||||
|
|
||||||
/* Call the function one time */
|
/* Call the function or expression one time */
|
||||||
fcinfo.isnull = false;
|
if (direct_function_call)
|
||||||
rsinfo.isDone = ExprSingleResult;
|
{
|
||||||
result = FunctionCallInvoke(&fcinfo);
|
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? */
|
/* Which protocol does function want to use? */
|
||||||
if (rsinfo.returnMode == SFRM_ValuePerCall)
|
if (rsinfo.returnMode == SFRM_ValuePerCall)
|
||||||
@ -949,8 +988,6 @@ ExecMakeTableFunctionResult(Expr *funcexpr,
|
|||||||
*/
|
*/
|
||||||
if (first_time)
|
if (first_time)
|
||||||
{
|
{
|
||||||
Oid funcrettype = funcexpr->typeOid;
|
|
||||||
|
|
||||||
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
|
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
|
||||||
if (funcrettype == RECORDOID ||
|
if (funcrettype == RECORDOID ||
|
||||||
get_typtype(funcrettype) == 'c')
|
get_typtype(funcrettype) == 'c')
|
||||||
@ -960,7 +997,9 @@ ExecMakeTableFunctionResult(Expr *funcexpr,
|
|||||||
* TupleTableSlot; use its descriptor
|
* TupleTableSlot; use its descriptor
|
||||||
*/
|
*/
|
||||||
slot = (TupleTableSlot *) DatumGetPointer(result);
|
slot = (TupleTableSlot *) DatumGetPointer(result);
|
||||||
if (fcinfo.isnull || !slot || !IsA(slot, TupleTableSlot) ||
|
if (fcinfo.isnull ||
|
||||||
|
!slot ||
|
||||||
|
!IsA(slot, TupleTableSlot) ||
|
||||||
!slot->ttc_tupleDescriptor)
|
!slot->ttc_tupleDescriptor)
|
||||||
elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple");
|
elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple");
|
||||||
tupdesc = CreateTupleDescCopy(slot->ttc_tupleDescriptor);
|
tupdesc = CreateTupleDescCopy(slot->ttc_tupleDescriptor);
|
||||||
@ -993,7 +1032,9 @@ ExecMakeTableFunctionResult(Expr *funcexpr,
|
|||||||
if (returnsTuple)
|
if (returnsTuple)
|
||||||
{
|
{
|
||||||
slot = (TupleTableSlot *) DatumGetPointer(result);
|
slot = (TupleTableSlot *) DatumGetPointer(result);
|
||||||
if (fcinfo.isnull || !slot || !IsA(slot, TupleTableSlot) ||
|
if (fcinfo.isnull ||
|
||||||
|
!slot ||
|
||||||
|
!IsA(slot, TupleTableSlot) ||
|
||||||
TupIsNull(slot))
|
TupIsNull(slot))
|
||||||
elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple");
|
elog(ERROR, "ExecMakeTableFunctionResult: Invalid result from function returning tuple");
|
||||||
tuple = slot->val;
|
tuple = slot->val;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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;
|
TupleDesc funcTupdesc;
|
||||||
|
|
||||||
scanstate->tuplestorestate = tuplestorestate =
|
scanstate->tuplestorestate = tuplestorestate =
|
||||||
ExecMakeTableFunctionResult((Expr *) scanstate->funcexpr,
|
ExecMakeTableFunctionResult(scanstate->funcexpr,
|
||||||
econtext,
|
econtext,
|
||||||
scanstate->tupdesc,
|
scanstate->tupdesc,
|
||||||
&funcTupdesc);
|
&funcTupdesc);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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,
|
ExprContext *econtext,
|
||||||
bool *isNull,
|
bool *isNull,
|
||||||
ExprDoneCond *isDone);
|
ExprDoneCond *isDone);
|
||||||
extern Tuplestorestate *ExecMakeTableFunctionResult(Expr *funcexpr,
|
extern Tuplestorestate *ExecMakeTableFunctionResult(Node *funcexpr,
|
||||||
ExprContext *econtext,
|
ExprContext *econtext,
|
||||||
TupleDesc expectedDesc,
|
TupleDesc expectedDesc,
|
||||||
TupleDesc *returnDesc);
|
TupleDesc *returnDesc);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user