Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit ae3129f

Browse files
committed
Quick-and-dirty fix for recursive plpgsql functions, per bug report from
Frank Miles 7-Sep-01. This is really just sticking a finger in the dike. Frank's case works now, but we still couldn't support a recursive function returning a set. Really need to restructure querytrees and execution state so that the querytree is *read only*. We've run into this over and over and over again ... it has to happen sometime soon.
1 parent ac0c234 commit ae3129f

File tree

5 files changed

+123
-92
lines changed

5 files changed

+123
-92
lines changed

src/backend/executor/execQual.c

Lines changed: 61 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.87 2001/06/19 22:39:11 tgl Exp $
11+
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.88 2001/09/21 00:11:30 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -54,9 +54,8 @@ static Datum ExecEvalOper(Expr *opClause, ExprContext *econtext,
5454
bool *isNull, ExprDoneCond *isDone);
5555
static Datum ExecEvalFunc(Expr *funcClause, ExprContext *econtext,
5656
bool *isNull, ExprDoneCond *isDone);
57-
static ExprDoneCond ExecEvalFuncArgs(FunctionCachePtr fcache,
58-
List *argList,
59-
ExprContext *econtext);
57+
static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo,
58+
List *argList, ExprContext *econtext);
6059
static Datum ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull);
6160
static Datum ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull);
6261
static Datum ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull);
@@ -600,7 +599,7 @@ GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull)
600599
* Evaluate arguments for a function.
601600
*/
602601
static ExprDoneCond
603-
ExecEvalFuncArgs(FunctionCachePtr fcache,
602+
ExecEvalFuncArgs(FunctionCallInfo fcinfo,
604603
List *argList,
605604
ExprContext *econtext)
606605
{
@@ -615,14 +614,13 @@ ExecEvalFuncArgs(FunctionCachePtr fcache,
615614
{
616615
ExprDoneCond thisArgIsDone;
617616

618-
fcache->fcinfo.arg[i] = ExecEvalExpr((Node *) lfirst(arg),
619-
econtext,
620-
&fcache->fcinfo.argnull[i],
621-
&thisArgIsDone);
617+
fcinfo->arg[i] = ExecEvalExpr((Node *) lfirst(arg),
618+
econtext,
619+
&fcinfo->argnull[i],
620+
&thisArgIsDone);
622621

623622
if (thisArgIsDone != ExprSingleResult)
624623
{
625-
626624
/*
627625
* We allow only one argument to have a set value; we'd need
628626
* much more complexity to keep track of multiple set
@@ -631,12 +629,13 @@ ExecEvalFuncArgs(FunctionCachePtr fcache,
631629
*/
632630
if (argIsDone != ExprSingleResult)
633631
elog(ERROR, "Functions and operators can take only one set argument");
634-
fcache->hasSetArg = true;
635632
argIsDone = thisArgIsDone;
636633
}
637634
i++;
638635
}
639636

637+
fcinfo->nargs = i;
638+
640639
return argIsDone;
641640
}
642641

@@ -656,19 +655,25 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
656655
ExprDoneCond *isDone)
657656
{
658657
Datum result;
658+
FunctionCallInfoData fcinfo;
659+
ReturnSetInfo rsinfo; /* for functions returning sets */
659660
ExprDoneCond argDone;
661+
bool hasSetArg;
660662
int i;
661663

662664
/*
663665
* arguments is a list of expressions to evaluate before passing to
664666
* the function manager. We skip the evaluation if it was already
665667
* done in the previous call (ie, we are continuing the evaluation of
666668
* a set-valued function). Otherwise, collect the current argument
667-
* values into fcache->fcinfo.
669+
* values into fcinfo.
668670
*/
669-
if (fcache->fcinfo.nargs > 0 && !fcache->argsValid)
671+
if (!fcache->setArgsValid)
670672
{
671-
argDone = ExecEvalFuncArgs(fcache, arguments, econtext);
673+
/* Need to prep callinfo structure */
674+
MemSet(&fcinfo, 0, sizeof(fcinfo));
675+
fcinfo.flinfo = &(fcache->func);
676+
argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext);
672677
if (argDone == ExprEndResult)
673678
{
674679
/* input is an empty set, so return an empty set. */
@@ -679,15 +684,33 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
679684
elog(ERROR, "Set-valued function called in context that cannot accept a set");
680685
return (Datum) 0;
681686
}
687+
hasSetArg = (argDone != ExprSingleResult);
688+
}
689+
else
690+
{
691+
/* Copy callinfo from previous evaluation */
692+
memcpy(&fcinfo, &fcache->setArgs, sizeof(fcinfo));
693+
hasSetArg = fcache->setHasSetArg;
694+
/* Reset flag (we may set it again below) */
695+
fcache->setArgsValid = false;
696+
}
697+
698+
/*
699+
* If function returns set, prepare a resultinfo node for
700+
* communication
701+
*/
702+
if (fcache->func.fn_retset)
703+
{
704+
fcinfo.resultinfo = (Node *) &rsinfo;
705+
rsinfo.type = T_ReturnSetInfo;
682706
}
683707

684708
/*
685709
* now return the value gotten by calling the function manager,
686710
* passing the function the evaluated parameter values.
687711
*/
688-
if (fcache->func.fn_retset || fcache->hasSetArg)
712+
if (fcache->func.fn_retset || hasSetArg)
689713
{
690-
691714
/*
692715
* We need to return a set result. Complain if caller not ready
693716
* to accept one.
@@ -705,7 +728,6 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
705728
*/
706729
for (;;)
707730
{
708-
709731
/*
710732
* If function is strict, and there are any NULL arguments,
711733
* skip calling the function (at least for this set of args).
@@ -714,9 +736,9 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
714736

715737
if (fcache->func.fn_strict)
716738
{
717-
for (i = 0; i < fcache->fcinfo.nargs; i++)
739+
for (i = 0; i < fcinfo.nargs; i++)
718740
{
719-
if (fcache->fcinfo.argnull[i])
741+
if (fcinfo.argnull[i])
720742
{
721743
callit = false;
722744
break;
@@ -726,11 +748,11 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
726748

727749
if (callit)
728750
{
729-
fcache->fcinfo.isnull = false;
730-
fcache->rsinfo.isDone = ExprSingleResult;
731-
result = FunctionCallInvoke(&fcache->fcinfo);
732-
*isNull = fcache->fcinfo.isnull;
733-
*isDone = fcache->rsinfo.isDone;
751+
fcinfo.isnull = false;
752+
rsinfo.isDone = ExprSingleResult;
753+
result = FunctionCallInvoke(&fcinfo);
754+
*isNull = fcinfo.isnull;
755+
*isDone = rsinfo.isDone;
734756
}
735757
else
736758
{
@@ -741,14 +763,17 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
741763

742764
if (*isDone != ExprEndResult)
743765
{
744-
745766
/*
746767
* Got a result from current argument. If function itself
747-
* returns set, flag that we want to reuse current
748-
* argument values on next call.
768+
* returns set, save the current argument values to re-use
769+
* on the next call.
749770
*/
750771
if (fcache->func.fn_retset)
751-
fcache->argsValid = true;
772+
{
773+
memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo));
774+
fcache->setHasSetArg = hasSetArg;
775+
fcache->setArgsValid = true;
776+
}
752777

753778
/*
754779
* Make sure we say we are returning a set, even if the
@@ -759,22 +784,15 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
759784
}
760785

761786
/* Else, done with this argument */
762-
fcache->argsValid = false;
763-
764-
if (!fcache->hasSetArg)
787+
if (!hasSetArg)
765788
break; /* input not a set, so done */
766789

767790
/* Re-eval args to get the next element of the input set */
768-
argDone = ExecEvalFuncArgs(fcache, arguments, econtext);
791+
argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext);
769792

770793
if (argDone != ExprMultipleResult)
771794
{
772-
773-
/*
774-
* End of arguments, so reset the hasSetArg flag and say
775-
* "Done"
776-
*/
777-
fcache->hasSetArg = false;
795+
/* End of argument set, so we're done. */
778796
*isNull = true;
779797
*isDone = ExprEndResult;
780798
result = (Datum) 0;
@@ -789,7 +807,6 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
789807
}
790808
else
791809
{
792-
793810
/*
794811
* Non-set case: much easier.
795812
*
@@ -798,18 +815,18 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
798815
*/
799816
if (fcache->func.fn_strict)
800817
{
801-
for (i = 0; i < fcache->fcinfo.nargs; i++)
818+
for (i = 0; i < fcinfo.nargs; i++)
802819
{
803-
if (fcache->fcinfo.argnull[i])
820+
if (fcinfo.argnull[i])
804821
{
805822
*isNull = true;
806823
return (Datum) 0;
807824
}
808825
}
809826
}
810-
fcache->fcinfo.isnull = false;
811-
result = FunctionCallInvoke(&fcache->fcinfo);
812-
*isNull = fcache->fcinfo.isnull;
827+
fcinfo.isnull = false;
828+
result = FunctionCallInvoke(&fcinfo);
829+
*isNull = fcinfo.isnull;
813830
}
814831

815832
return result;

src/backend/utils/cache/fcache.c

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* Portions Copyright (c) 1994, Regents of the University of California
99
*
1010
* IDENTIFICATION
11-
* $Header: /cvsroot/pgsql/src/backend/utils/cache/Attic/fcache.c,v 1.39 2001/03/22 03:59:57 momjian Exp $
11+
* $Header: /cvsroot/pgsql/src/backend/utils/cache/Attic/fcache.c,v 1.40 2001/09/21 00:11:31 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -17,18 +17,19 @@
1717
#include "utils/fcache.h"
1818

1919

20-
/*-----------------------------------------------------------------
21-
*
20+
/*
2221
* Build a 'FunctionCache' struct given the PG_PROC oid.
23-
*
24-
*-----------------------------------------------------------------
2522
*/
2623
FunctionCachePtr
2724
init_fcache(Oid foid, int nargs, MemoryContext fcacheCxt)
2825
{
2926
MemoryContext oldcontext;
3027
FunctionCachePtr retval;
3128

29+
/* Safety check (should never fail, as parser should check sooner) */
30+
if (nargs > FUNC_MAX_ARGS)
31+
elog(ERROR, "init_fcache: too many arguments");
32+
3233
/* Switch to a context long-lived enough for the fcache entry */
3334
oldcontext = MemoryContextSwitchTo(fcacheCxt);
3435

@@ -38,25 +39,8 @@ init_fcache(Oid foid, int nargs, MemoryContext fcacheCxt)
3839
/* Set up the primary fmgr lookup information */
3940
fmgr_info(foid, &(retval->func));
4041

41-
/* Initialize unvarying fields of per-call info block */
42-
retval->fcinfo.flinfo = &(retval->func);
43-
retval->fcinfo.nargs = nargs;
44-
45-
if (nargs > FUNC_MAX_ARGS)
46-
elog(ERROR, "init_fcache: too many arguments");
47-
48-
/*
49-
* If function returns set, prepare a resultinfo node for
50-
* communication
51-
*/
52-
if (retval->func.fn_retset)
53-
{
54-
retval->fcinfo.resultinfo = (Node *) &(retval->rsinfo);
55-
retval->rsinfo.type = T_ReturnSetInfo;
56-
}
57-
58-
retval->argsValid = false;
59-
retval->hasSetArg = false;
42+
/* Initialize additional info */
43+
retval->setArgsValid = false;
6044

6145
MemoryContextSwitchTo(oldcontext);
6246

src/include/utils/fcache.h

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
1212
* Portions Copyright (c) 1994, Regents of the University of California
1313
*
14-
* $Id: fcache.h,v 1.16 2001/03/22 04:01:12 momjian Exp $
14+
* $Id: fcache.h,v 1.17 2001/09/21 00:11:31 tgl Exp $
1515
*
1616
*-------------------------------------------------------------------------
1717
*/
@@ -25,46 +25,42 @@
2525
* A FunctionCache record is built for all functions regardless of language.
2626
*
2727
* We store the fmgr lookup info to avoid recomputing it on each call.
28-
* We also store a prebuilt FunctionCallInfo struct. When evaluating a
29-
* function-returning-set, fcinfo holds the argument values across calls
30-
* so that we need not re-evaluate the arguments for each call. Even for
31-
* non-set functions, fcinfo saves a few cycles per call by allowing us to
32-
* avoid redundant setup of its fields.
28+
*
29+
* We also need to store argument values across calls when evaluating a
30+
* function-returning-set. This is pretty ugly (and not re-entrant);
31+
* current-evaluation info should be somewhere in the econtext, not in
32+
* the querytree. As it stands, a function-returning-set can't safely be
33+
* recursive, at least not if it's in plpgsql which will try to re-use
34+
* the querytree at multiple execution nesting levels. FIXME someday.
3335
*/
3436

3537
typedef struct FunctionCache
3638
{
37-
3839
/*
3940
* Function manager's lookup info for the target function.
4041
*/
4142
FmgrInfo func;
4243

4344
/*
44-
* Per-call info for calling the target function. Unvarying fields
45-
* are set up by init_fcache(). Argument values are filled in as
46-
* needed.
47-
*/
48-
FunctionCallInfoData fcinfo;
49-
50-
/*
51-
* "Resultinfo" node --- used only if target function returns a set.
52-
*/
53-
ReturnSetInfo rsinfo;
54-
55-
/*
56-
* argsValid is true when we are evaluating a set-valued function and
57-
* we are in the middle of a call series; we want to pass the same
45+
* setArgsValid is true when we are evaluating a set-valued function
46+
* and we are in the middle of a call series; we want to pass the same
5847
* argument values to the function again (and again, until it returns
5948
* ExprEndResult).
6049
*/
61-
bool argsValid; /* TRUE if fcinfo contains valid arguments */
50+
bool setArgsValid;
6251

6352
/*
64-
* hasSetArg is true if we found a set-valued argument to the
53+
* Flag to remember whether we found a set-valued argument to the
6554
* function. This causes the function result to be a set as well.
55+
* Valid only when setArgsValid is true.
56+
*/
57+
bool setHasSetArg; /* some argument returns a set */
58+
59+
/*
60+
* Current argument data for a set-valued function; contains valid
61+
* data only if setArgsValid is true.
6662
*/
67-
bool hasSetArg; /* some argument returns a set */
63+
FunctionCallInfoData setArgs;
6864
} FunctionCache;
6965

7066

0 commit comments

Comments
 (0)