Fix domain_in() bug exhibited by Darcy Buskermolen. The idea of an EState

that's shorter-lived than the expression state being evaluated in it really
doesn't work :-( --- we end up with fn_extra caches getting deleted while
still in use.  Rather than abandon the notion of caching expression state
across domain_in calls altogether, I chose to make domain_in a bit cozier
with ExprContext.  All we really need for evaluating variable-free
expressions is an ExprContext, not an EState, so I invented the notion of a
"standalone" ExprContext.  domain_in can prevent resource leakages by doing
a ReScanExprContext on this rather than having to free it entirely; so we
can make the ExprContext have the same lifespan (and particularly the same
per_query memory context) as the expression state structs.
This commit is contained in:
Tom Lane 2006-08-04 21:33:36 +00:00
parent bf7b205e16
commit c68489863c
4 changed files with 105 additions and 32 deletions

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.138 2006/07/31 20:09:04 tgl Exp $ * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.139 2006/08/04 21:33:36 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -17,6 +17,7 @@
* CreateExecutorState Create/delete executor working state * CreateExecutorState Create/delete executor working state
* FreeExecutorState * FreeExecutorState
* CreateExprContext * CreateExprContext
* CreateStandaloneExprContext
* FreeExprContext * FreeExprContext
* ReScanExprContext * ReScanExprContext
* *
@ -331,6 +332,68 @@ CreateExprContext(EState *estate)
return econtext; return econtext;
} }
/* ----------------
* CreateStandaloneExprContext
*
* Create a context for standalone expression evaluation.
*
* An ExprContext made this way can be used for evaluation of expressions
* that contain no Params, subplans, or Var references (it might work to
* put tuple references into the scantuple field, but it seems unwise).
*
* The ExprContext struct is allocated in the caller's current memory
* context, which also becomes its "per query" context.
*
* It is caller's responsibility to free the ExprContext when done,
* or at least ensure that any shutdown callbacks have been called
* (ReScanExprContext() is suitable). Otherwise, non-memory resources
* might be leaked.
* ----------------
*/
ExprContext *
CreateStandaloneExprContext(void)
{
ExprContext *econtext;
/* Create the ExprContext node within the caller's memory context */
econtext = makeNode(ExprContext);
/* Initialize fields of ExprContext */
econtext->ecxt_scantuple = NULL;
econtext->ecxt_innertuple = NULL;
econtext->ecxt_outertuple = NULL;
econtext->ecxt_per_query_memory = CurrentMemoryContext;
/*
* Create working memory for expression evaluation in this context.
*/
econtext->ecxt_per_tuple_memory =
AllocSetContextCreate(CurrentMemoryContext,
"ExprContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
econtext->ecxt_param_exec_vals = NULL;
econtext->ecxt_param_list_info = NULL;
econtext->ecxt_aggvalues = NULL;
econtext->ecxt_aggnulls = NULL;
econtext->caseValue_datum = (Datum) 0;
econtext->caseValue_isNull = true;
econtext->domainValue_datum = (Datum) 0;
econtext->domainValue_isNull = true;
econtext->ecxt_estate = NULL;
econtext->ecxt_callbacks = NULL;
return econtext;
}
/* ---------------- /* ----------------
* FreeExprContext * FreeExprContext
* *
@ -352,9 +415,11 @@ FreeExprContext(ExprContext *econtext)
ShutdownExprContext(econtext); ShutdownExprContext(econtext);
/* And clean up the memory used */ /* And clean up the memory used */
MemoryContextDelete(econtext->ecxt_per_tuple_memory); MemoryContextDelete(econtext->ecxt_per_tuple_memory);
/* Unlink self from owning EState */ /* Unlink self from owning EState, if any */
estate = econtext->ecxt_estate; estate = econtext->ecxt_estate;
estate->es_exprcontexts = list_delete_ptr(estate->es_exprcontexts, econtext); if (estate)
estate->es_exprcontexts = list_delete_ptr(estate->es_exprcontexts,
econtext);
/* And delete the ExprContext node */ /* And delete the ExprContext node */
pfree(econtext); pfree(econtext);
} }

View File

@ -11,17 +11,13 @@
* *
* The overhead required for constraint checking can be high, since examining * The overhead required for constraint checking can be high, since examining
* the catalogs to discover the constraints for a given domain is not cheap. * the catalogs to discover the constraints for a given domain is not cheap.
* We have two mechanisms for minimizing this cost: * We have three mechanisms for minimizing this cost:
* 1. In a nest of domains, we flatten the checking of all the levels * 1. In a nest of domains, we flatten the checking of all the levels
* into just one operation. * into just one operation.
* 2. We cache the list of constraint items in the FmgrInfo struct * 2. We cache the list of constraint items in the FmgrInfo struct
* passed by the caller. * passed by the caller.
* * 3. If there are CHECK constraints, we cache a standalone ExprContext
* We also have to create an EState to evaluate CHECK expressions in. * to evaluate them in.
* Creating and destroying an EState is somewhat expensive, and so it's
* tempting to cache the EState too. However, that would mean that the
* EState never gets an explicit FreeExecutorState call, which is a bad idea
* because it risks leaking non-memory resources.
* *
* *
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
@ -29,7 +25,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/domains.c,v 1.2 2006/07/14 14:52:24 momjian Exp $ * $PostgreSQL: pgsql/src/backend/utils/adt/domains.c,v 1.3 2006/08/04 21:33:36 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -54,6 +50,10 @@ typedef struct DomainIOData
FmgrInfo proc; FmgrInfo proc;
/* List of constraint items to check */ /* List of constraint items to check */
List *constraint_list; List *constraint_list;
/* Context for evaluating CHECK constraints in */
ExprContext *econtext;
/* Memory context this cache is in */
MemoryContext mcxt;
} DomainIOData; } DomainIOData;
@ -95,6 +95,10 @@ domain_state_setup(DomainIOData *my_extra, Oid domainType, bool binary,
my_extra->constraint_list = GetDomainConstraints(domainType); my_extra->constraint_list = GetDomainConstraints(domainType);
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
/* We don't make an ExprContext until needed */
my_extra->econtext = NULL;
my_extra->mcxt = mcxt;
/* Mark cache valid */ /* Mark cache valid */
my_extra->domain_type = domainType; my_extra->domain_type = domainType;
} }
@ -107,7 +111,7 @@ domain_state_setup(DomainIOData *my_extra, Oid domainType, bool binary,
static void static void
domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
{ {
EState *estate = NULL; ExprContext *econtext = my_extra->econtext;
ListCell *l; ListCell *l;
foreach(l, my_extra->constraint_list) foreach(l, my_extra->constraint_list)
@ -125,25 +129,26 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
break; break;
case DOM_CONSTRAINT_CHECK: case DOM_CONSTRAINT_CHECK:
{ {
ExprContext *econtext;
Datum conResult; Datum conResult;
bool conIsNull; bool conIsNull;
Datum save_datum;
bool save_isNull;
if (estate == NULL) /* Make the econtext if we didn't already */
estate = CreateExecutorState(); if (econtext == NULL)
econtext = GetPerTupleExprContext(estate); {
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(my_extra->mcxt);
econtext = CreateStandaloneExprContext();
MemoryContextSwitchTo(oldcontext);
my_extra->econtext = econtext;
}
/* /*
* Set up value to be returned by CoerceToDomainValue * Set up value to be returned by CoerceToDomainValue
* nodes. We must save and restore prior setting of * nodes. Unlike ExecEvalCoerceToDomain, this econtext
* econtext's domainValue fields, in case this node is * couldn't be shared with anything else, so no need
* itself within a check expression for another domain. * to save and restore fields.
*/ */
save_datum = econtext->domainValue_datum;
save_isNull = econtext->domainValue_isNull;
econtext->domainValue_datum = value; econtext->domainValue_datum = value;
econtext->domainValue_isNull = isnull; econtext->domainValue_isNull = isnull;
@ -158,9 +163,6 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
errmsg("value for domain %s violates check constraint \"%s\"", errmsg("value for domain %s violates check constraint \"%s\"",
format_type_be(my_extra->domain_type), format_type_be(my_extra->domain_type),
con->name))); con->name)));
econtext->domainValue_datum = save_datum;
econtext->domainValue_isNull = save_isNull;
break; break;
} }
default: default:
@ -170,8 +172,13 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
} }
} }
if (estate) /*
FreeExecutorState(estate); * Before exiting, call any shutdown callbacks and reset econtext's
* per-tuple memory. This avoids leaking non-memory resources,
* if anything in the expression(s) has any.
*/
if (econtext)
ReScanExprContext(econtext);
} }

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.127 2006/06/16 18:42:23 tgl Exp $ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.128 2006/08/04 21:33:36 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -224,6 +224,7 @@ extern void end_tup_output(TupOutputState *tstate);
extern EState *CreateExecutorState(void); extern EState *CreateExecutorState(void);
extern void FreeExecutorState(EState *estate); extern void FreeExecutorState(EState *estate);
extern ExprContext *CreateExprContext(EState *estate); extern ExprContext *CreateExprContext(EState *estate);
extern ExprContext *CreateStandaloneExprContext(void);
extern void FreeExprContext(ExprContext *econtext); extern void FreeExprContext(ExprContext *econtext);
extern void ReScanExprContext(ExprContext *econtext); extern void ReScanExprContext(ExprContext *econtext);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.157 2006/08/02 18:58:21 tgl Exp $ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.158 2006/08/04 21:33:36 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -118,7 +118,7 @@ typedef struct ExprContext
Datum domainValue_datum; Datum domainValue_datum;
bool domainValue_isNull; bool domainValue_isNull;
/* Link to containing EState */ /* Link to containing EState (NULL if a standalone ExprContext) */
struct EState *ecxt_estate; struct EState *ecxt_estate;
/* Functions to call back when ExprContext is shut down */ /* Functions to call back when ExprContext is shut down */