mirror of
https://github.com/postgres/postgres.git
synced 2025-04-24 10:47:04 +03:00
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.
This commit is contained in:
parent
ac0c234c16
commit
ae3129fd03
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.87 2001/06/19 22:39:11 tgl Exp $
|
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.88 2001/09/21 00:11:30 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -54,9 +54,8 @@ static Datum ExecEvalOper(Expr *opClause, ExprContext *econtext,
|
|||||||
bool *isNull, ExprDoneCond *isDone);
|
bool *isNull, ExprDoneCond *isDone);
|
||||||
static Datum ExecEvalFunc(Expr *funcClause, ExprContext *econtext,
|
static Datum ExecEvalFunc(Expr *funcClause, ExprContext *econtext,
|
||||||
bool *isNull, ExprDoneCond *isDone);
|
bool *isNull, ExprDoneCond *isDone);
|
||||||
static ExprDoneCond ExecEvalFuncArgs(FunctionCachePtr fcache,
|
static ExprDoneCond ExecEvalFuncArgs(FunctionCallInfo fcinfo,
|
||||||
List *argList,
|
List *argList, ExprContext *econtext);
|
||||||
ExprContext *econtext);
|
|
||||||
static Datum ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull);
|
static Datum ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull);
|
||||||
static Datum ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull);
|
static Datum ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull);
|
||||||
static Datum ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull);
|
static Datum ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull);
|
||||||
@ -600,7 +599,7 @@ GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull)
|
|||||||
* Evaluate arguments for a function.
|
* Evaluate arguments for a function.
|
||||||
*/
|
*/
|
||||||
static ExprDoneCond
|
static ExprDoneCond
|
||||||
ExecEvalFuncArgs(FunctionCachePtr fcache,
|
ExecEvalFuncArgs(FunctionCallInfo fcinfo,
|
||||||
List *argList,
|
List *argList,
|
||||||
ExprContext *econtext)
|
ExprContext *econtext)
|
||||||
{
|
{
|
||||||
@ -615,14 +614,13 @@ ExecEvalFuncArgs(FunctionCachePtr fcache,
|
|||||||
{
|
{
|
||||||
ExprDoneCond thisArgIsDone;
|
ExprDoneCond thisArgIsDone;
|
||||||
|
|
||||||
fcache->fcinfo.arg[i] = ExecEvalExpr((Node *) lfirst(arg),
|
fcinfo->arg[i] = ExecEvalExpr((Node *) lfirst(arg),
|
||||||
econtext,
|
econtext,
|
||||||
&fcache->fcinfo.argnull[i],
|
&fcinfo->argnull[i],
|
||||||
&thisArgIsDone);
|
&thisArgIsDone);
|
||||||
|
|
||||||
if (thisArgIsDone != ExprSingleResult)
|
if (thisArgIsDone != ExprSingleResult)
|
||||||
{
|
{
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We allow only one argument to have a set value; we'd need
|
* We allow only one argument to have a set value; we'd need
|
||||||
* much more complexity to keep track of multiple set
|
* much more complexity to keep track of multiple set
|
||||||
@ -631,12 +629,13 @@ ExecEvalFuncArgs(FunctionCachePtr fcache,
|
|||||||
*/
|
*/
|
||||||
if (argIsDone != ExprSingleResult)
|
if (argIsDone != ExprSingleResult)
|
||||||
elog(ERROR, "Functions and operators can take only one set argument");
|
elog(ERROR, "Functions and operators can take only one set argument");
|
||||||
fcache->hasSetArg = true;
|
|
||||||
argIsDone = thisArgIsDone;
|
argIsDone = thisArgIsDone;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fcinfo->nargs = i;
|
||||||
|
|
||||||
return argIsDone;
|
return argIsDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -656,7 +655,10 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
ExprDoneCond *isDone)
|
ExprDoneCond *isDone)
|
||||||
{
|
{
|
||||||
Datum result;
|
Datum result;
|
||||||
|
FunctionCallInfoData fcinfo;
|
||||||
|
ReturnSetInfo rsinfo; /* for functions returning sets */
|
||||||
ExprDoneCond argDone;
|
ExprDoneCond argDone;
|
||||||
|
bool hasSetArg;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -664,11 +666,14 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
* the function manager. We skip the evaluation if it was already
|
* the function manager. We skip the evaluation if it was already
|
||||||
* done in the previous call (ie, we are continuing the evaluation of
|
* done in the previous call (ie, we are continuing the evaluation of
|
||||||
* a set-valued function). Otherwise, collect the current argument
|
* a set-valued function). Otherwise, collect the current argument
|
||||||
* values into fcache->fcinfo.
|
* values into fcinfo.
|
||||||
*/
|
*/
|
||||||
if (fcache->fcinfo.nargs > 0 && !fcache->argsValid)
|
if (!fcache->setArgsValid)
|
||||||
{
|
{
|
||||||
argDone = ExecEvalFuncArgs(fcache, arguments, econtext);
|
/* Need to prep callinfo structure */
|
||||||
|
MemSet(&fcinfo, 0, sizeof(fcinfo));
|
||||||
|
fcinfo.flinfo = &(fcache->func);
|
||||||
|
argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext);
|
||||||
if (argDone == ExprEndResult)
|
if (argDone == ExprEndResult)
|
||||||
{
|
{
|
||||||
/* input is an empty set, so return an empty set. */
|
/* input is an empty set, so return an empty set. */
|
||||||
@ -679,15 +684,33 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
elog(ERROR, "Set-valued function called in context that cannot accept a set");
|
elog(ERROR, "Set-valued function called in context that cannot accept a set");
|
||||||
return (Datum) 0;
|
return (Datum) 0;
|
||||||
}
|
}
|
||||||
|
hasSetArg = (argDone != ExprSingleResult);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Copy callinfo from previous evaluation */
|
||||||
|
memcpy(&fcinfo, &fcache->setArgs, sizeof(fcinfo));
|
||||||
|
hasSetArg = fcache->setHasSetArg;
|
||||||
|
/* Reset flag (we may set it again below) */
|
||||||
|
fcache->setArgsValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If function returns set, prepare a resultinfo node for
|
||||||
|
* communication
|
||||||
|
*/
|
||||||
|
if (fcache->func.fn_retset)
|
||||||
|
{
|
||||||
|
fcinfo.resultinfo = (Node *) &rsinfo;
|
||||||
|
rsinfo.type = T_ReturnSetInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* now return the value gotten by calling the function manager,
|
* now return the value gotten by calling the function manager,
|
||||||
* passing the function the evaluated parameter values.
|
* passing the function the evaluated parameter values.
|
||||||
*/
|
*/
|
||||||
if (fcache->func.fn_retset || fcache->hasSetArg)
|
if (fcache->func.fn_retset || hasSetArg)
|
||||||
{
|
{
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We need to return a set result. Complain if caller not ready
|
* We need to return a set result. Complain if caller not ready
|
||||||
* to accept one.
|
* to accept one.
|
||||||
@ -705,7 +728,6 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
*/
|
*/
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If function is strict, and there are any NULL arguments,
|
* If function is strict, and there are any NULL arguments,
|
||||||
* skip calling the function (at least for this set of args).
|
* skip calling the function (at least for this set of args).
|
||||||
@ -714,9 +736,9 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
|
|
||||||
if (fcache->func.fn_strict)
|
if (fcache->func.fn_strict)
|
||||||
{
|
{
|
||||||
for (i = 0; i < fcache->fcinfo.nargs; i++)
|
for (i = 0; i < fcinfo.nargs; i++)
|
||||||
{
|
{
|
||||||
if (fcache->fcinfo.argnull[i])
|
if (fcinfo.argnull[i])
|
||||||
{
|
{
|
||||||
callit = false;
|
callit = false;
|
||||||
break;
|
break;
|
||||||
@ -726,11 +748,11 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
|
|
||||||
if (callit)
|
if (callit)
|
||||||
{
|
{
|
||||||
fcache->fcinfo.isnull = false;
|
fcinfo.isnull = false;
|
||||||
fcache->rsinfo.isDone = ExprSingleResult;
|
rsinfo.isDone = ExprSingleResult;
|
||||||
result = FunctionCallInvoke(&fcache->fcinfo);
|
result = FunctionCallInvoke(&fcinfo);
|
||||||
*isNull = fcache->fcinfo.isnull;
|
*isNull = fcinfo.isnull;
|
||||||
*isDone = fcache->rsinfo.isDone;
|
*isDone = rsinfo.isDone;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -741,14 +763,17 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
|
|
||||||
if (*isDone != ExprEndResult)
|
if (*isDone != ExprEndResult)
|
||||||
{
|
{
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Got a result from current argument. If function itself
|
* Got a result from current argument. If function itself
|
||||||
* returns set, flag that we want to reuse current
|
* returns set, save the current argument values to re-use
|
||||||
* argument values on next call.
|
* on the next call.
|
||||||
*/
|
*/
|
||||||
if (fcache->func.fn_retset)
|
if (fcache->func.fn_retset)
|
||||||
fcache->argsValid = true;
|
{
|
||||||
|
memcpy(&fcache->setArgs, &fcinfo, sizeof(fcinfo));
|
||||||
|
fcache->setHasSetArg = hasSetArg;
|
||||||
|
fcache->setArgsValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make sure we say we are returning a set, even if the
|
* Make sure we say we are returning a set, even if the
|
||||||
@ -759,22 +784,15 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Else, done with this argument */
|
/* Else, done with this argument */
|
||||||
fcache->argsValid = false;
|
if (!hasSetArg)
|
||||||
|
|
||||||
if (!fcache->hasSetArg)
|
|
||||||
break; /* input not a set, so done */
|
break; /* input not a set, so done */
|
||||||
|
|
||||||
/* Re-eval args to get the next element of the input set */
|
/* Re-eval args to get the next element of the input set */
|
||||||
argDone = ExecEvalFuncArgs(fcache, arguments, econtext);
|
argDone = ExecEvalFuncArgs(&fcinfo, arguments, econtext);
|
||||||
|
|
||||||
if (argDone != ExprMultipleResult)
|
if (argDone != ExprMultipleResult)
|
||||||
{
|
{
|
||||||
|
/* End of argument set, so we're done. */
|
||||||
/*
|
|
||||||
* End of arguments, so reset the hasSetArg flag and say
|
|
||||||
* "Done"
|
|
||||||
*/
|
|
||||||
fcache->hasSetArg = false;
|
|
||||||
*isNull = true;
|
*isNull = true;
|
||||||
*isDone = ExprEndResult;
|
*isDone = ExprEndResult;
|
||||||
result = (Datum) 0;
|
result = (Datum) 0;
|
||||||
@ -789,7 +807,6 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Non-set case: much easier.
|
* Non-set case: much easier.
|
||||||
*
|
*
|
||||||
@ -798,18 +815,18 @@ ExecMakeFunctionResult(FunctionCachePtr fcache,
|
|||||||
*/
|
*/
|
||||||
if (fcache->func.fn_strict)
|
if (fcache->func.fn_strict)
|
||||||
{
|
{
|
||||||
for (i = 0; i < fcache->fcinfo.nargs; i++)
|
for (i = 0; i < fcinfo.nargs; i++)
|
||||||
{
|
{
|
||||||
if (fcache->fcinfo.argnull[i])
|
if (fcinfo.argnull[i])
|
||||||
{
|
{
|
||||||
*isNull = true;
|
*isNull = true;
|
||||||
return (Datum) 0;
|
return (Datum) 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fcache->fcinfo.isnull = false;
|
fcinfo.isnull = false;
|
||||||
result = FunctionCallInvoke(&fcache->fcinfo);
|
result = FunctionCallInvoke(&fcinfo);
|
||||||
*isNull = fcache->fcinfo.isnull;
|
*isNull = fcinfo.isnull;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
32
src/backend/utils/cache/fcache.c
vendored
32
src/backend/utils/cache/fcache.c
vendored
@ -8,7 +8,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/backend/utils/cache/Attic/fcache.c,v 1.39 2001/03/22 03:59:57 momjian Exp $
|
* $Header: /cvsroot/pgsql/src/backend/utils/cache/Attic/fcache.c,v 1.40 2001/09/21 00:11:31 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -17,11 +17,8 @@
|
|||||||
#include "utils/fcache.h"
|
#include "utils/fcache.h"
|
||||||
|
|
||||||
|
|
||||||
/*-----------------------------------------------------------------
|
/*
|
||||||
*
|
|
||||||
* Build a 'FunctionCache' struct given the PG_PROC oid.
|
* Build a 'FunctionCache' struct given the PG_PROC oid.
|
||||||
*
|
|
||||||
*-----------------------------------------------------------------
|
|
||||||
*/
|
*/
|
||||||
FunctionCachePtr
|
FunctionCachePtr
|
||||||
init_fcache(Oid foid, int nargs, MemoryContext fcacheCxt)
|
init_fcache(Oid foid, int nargs, MemoryContext fcacheCxt)
|
||||||
@ -29,6 +26,10 @@ init_fcache(Oid foid, int nargs, MemoryContext fcacheCxt)
|
|||||||
MemoryContext oldcontext;
|
MemoryContext oldcontext;
|
||||||
FunctionCachePtr retval;
|
FunctionCachePtr retval;
|
||||||
|
|
||||||
|
/* Safety check (should never fail, as parser should check sooner) */
|
||||||
|
if (nargs > FUNC_MAX_ARGS)
|
||||||
|
elog(ERROR, "init_fcache: too many arguments");
|
||||||
|
|
||||||
/* Switch to a context long-lived enough for the fcache entry */
|
/* Switch to a context long-lived enough for the fcache entry */
|
||||||
oldcontext = MemoryContextSwitchTo(fcacheCxt);
|
oldcontext = MemoryContextSwitchTo(fcacheCxt);
|
||||||
|
|
||||||
@ -38,25 +39,8 @@ init_fcache(Oid foid, int nargs, MemoryContext fcacheCxt)
|
|||||||
/* Set up the primary fmgr lookup information */
|
/* Set up the primary fmgr lookup information */
|
||||||
fmgr_info(foid, &(retval->func));
|
fmgr_info(foid, &(retval->func));
|
||||||
|
|
||||||
/* Initialize unvarying fields of per-call info block */
|
/* Initialize additional info */
|
||||||
retval->fcinfo.flinfo = &(retval->func);
|
retval->setArgsValid = false;
|
||||||
retval->fcinfo.nargs = nargs;
|
|
||||||
|
|
||||||
if (nargs > FUNC_MAX_ARGS)
|
|
||||||
elog(ERROR, "init_fcache: too many arguments");
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If function returns set, prepare a resultinfo node for
|
|
||||||
* communication
|
|
||||||
*/
|
|
||||||
if (retval->func.fn_retset)
|
|
||||||
{
|
|
||||||
retval->fcinfo.resultinfo = (Node *) &(retval->rsinfo);
|
|
||||||
retval->rsinfo.type = T_ReturnSetInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
retval->argsValid = false;
|
|
||||||
retval->hasSetArg = false;
|
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldcontext);
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2001, 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: fcache.h,v 1.16 2001/03/22 04:01:12 momjian Exp $
|
* $Id: fcache.h,v 1.17 2001/09/21 00:11:31 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -25,46 +25,42 @@
|
|||||||
* A FunctionCache record is built for all functions regardless of language.
|
* A FunctionCache record is built for all functions regardless of language.
|
||||||
*
|
*
|
||||||
* We store the fmgr lookup info to avoid recomputing it on each call.
|
* We store the fmgr lookup info to avoid recomputing it on each call.
|
||||||
* We also store a prebuilt FunctionCallInfo struct. When evaluating a
|
*
|
||||||
* function-returning-set, fcinfo holds the argument values across calls
|
* We also need to store argument values across calls when evaluating a
|
||||||
* so that we need not re-evaluate the arguments for each call. Even for
|
* function-returning-set. This is pretty ugly (and not re-entrant);
|
||||||
* non-set functions, fcinfo saves a few cycles per call by allowing us to
|
* current-evaluation info should be somewhere in the econtext, not in
|
||||||
* avoid redundant setup of its fields.
|
* the querytree. As it stands, a function-returning-set can't safely be
|
||||||
|
* recursive, at least not if it's in plpgsql which will try to re-use
|
||||||
|
* the querytree at multiple execution nesting levels. FIXME someday.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
typedef struct FunctionCache
|
typedef struct FunctionCache
|
||||||
{
|
{
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Function manager's lookup info for the target function.
|
* Function manager's lookup info for the target function.
|
||||||
*/
|
*/
|
||||||
FmgrInfo func;
|
FmgrInfo func;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Per-call info for calling the target function. Unvarying fields
|
* setArgsValid is true when we are evaluating a set-valued function
|
||||||
* are set up by init_fcache(). Argument values are filled in as
|
* and we are in the middle of a call series; we want to pass the same
|
||||||
* needed.
|
|
||||||
*/
|
|
||||||
FunctionCallInfoData fcinfo;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* "Resultinfo" node --- used only if target function returns a set.
|
|
||||||
*/
|
|
||||||
ReturnSetInfo rsinfo;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* argsValid is true when we are evaluating a set-valued function and
|
|
||||||
* we are in the middle of a call series; we want to pass the same
|
|
||||||
* argument values to the function again (and again, until it returns
|
* argument values to the function again (and again, until it returns
|
||||||
* ExprEndResult).
|
* ExprEndResult).
|
||||||
*/
|
*/
|
||||||
bool argsValid; /* TRUE if fcinfo contains valid arguments */
|
bool setArgsValid;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* hasSetArg is true if we found a set-valued argument to the
|
* Flag to remember whether we found a set-valued argument to the
|
||||||
* function. This causes the function result to be a set as well.
|
* function. This causes the function result to be a set as well.
|
||||||
|
* Valid only when setArgsValid is true.
|
||||||
*/
|
*/
|
||||||
bool hasSetArg; /* some argument returns a set */
|
bool setHasSetArg; /* some argument returns a set */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Current argument data for a set-valued function; contains valid
|
||||||
|
* data only if setArgsValid is true.
|
||||||
|
*/
|
||||||
|
FunctionCallInfoData setArgs;
|
||||||
} FunctionCache;
|
} FunctionCache;
|
||||||
|
|
||||||
|
|
||||||
|
@ -1515,3 +1515,22 @@ insert into IFace values ('IF', 'notthere', 'eth0', '');
|
|||||||
ERROR: system "notthere" does not exist
|
ERROR: system "notthere" does not exist
|
||||||
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
|
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
|
||||||
ERROR: IFace slotname "IF.orion.ethernet_interface_name_too_long" too long (20 char max)
|
ERROR: IFace slotname "IF.orion.ethernet_interface_name_too_long" too long (20 char max)
|
||||||
|
--
|
||||||
|
-- Test recursion, per bug report 7-Sep-01
|
||||||
|
--
|
||||||
|
CREATE FUNCTION recursion_test(int,int) RETURNS text AS '
|
||||||
|
DECLARE rslt text;
|
||||||
|
BEGIN
|
||||||
|
IF $1 <= 0 THEN
|
||||||
|
rslt = CAST($2 AS TEXT);
|
||||||
|
ELSE
|
||||||
|
rslt = CAST($1 AS TEXT) || '','' || recursion_test($1 - 1, $2);
|
||||||
|
END IF;
|
||||||
|
RETURN rslt;
|
||||||
|
END;' LANGUAGE 'plpgsql';
|
||||||
|
SELECT recursion_test(4,3);
|
||||||
|
recursion_test
|
||||||
|
----------------
|
||||||
|
4,3,2,1,3
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
@ -1399,3 +1399,18 @@ delete from HSlot;
|
|||||||
insert into IFace values ('IF', 'notthere', 'eth0', '');
|
insert into IFace values ('IF', 'notthere', 'eth0', '');
|
||||||
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
|
insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', '');
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test recursion, per bug report 7-Sep-01
|
||||||
|
--
|
||||||
|
CREATE FUNCTION recursion_test(int,int) RETURNS text AS '
|
||||||
|
DECLARE rslt text;
|
||||||
|
BEGIN
|
||||||
|
IF $1 <= 0 THEN
|
||||||
|
rslt = CAST($2 AS TEXT);
|
||||||
|
ELSE
|
||||||
|
rslt = CAST($1 AS TEXT) || '','' || recursion_test($1 - 1, $2);
|
||||||
|
END IF;
|
||||||
|
RETURN rslt;
|
||||||
|
END;' LANGUAGE 'plpgsql';
|
||||||
|
|
||||||
|
SELECT recursion_test(4,3);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user