Caching for stable expressions with constant arguments v6
Hi list,
Here's v6 of my expression caching patch. The only change in v6 is
added expression cost estimation in costsize.c. I'm setting per-tuple
cost of CacheExpr to 0 and moving sub-expression tuple costs into the
startup cost.
As always, this work is also available from my Github "cache" branch:
https://github.com/intgr/postgres/commits/cache
This patch was marked as "Returned with Feedback" from the 2011-11
commitfest. I expected to get to tweak the patch in response to
feedback before posting to the next commitfest. But said feedback
didn't arrive, and with me being on vacation, I missed the 2012-01 CF
deadline. :(
I will add it to the 2012-01 commitfest now, I hope that's OK. If not,
feel free to remove it and I'll put it in 2012-Next.
PS: Jaime, have you had a chance to look at the patch? Even if you're
not done with the review, I'd be glad to get some comments earlier.
And thanks for reviewing.
Regards,
Marti
On Mon, Jan 16, 2012 at 12:06 PM, Marti Raudsepp <marti@juffo.org> wrote:
I will add it to the 2012-01 commitfest now, I hope that's OK. If not,
feel free to remove it and I'll put it in 2012-Next.
i'm not the CF manager so he can disagree with me...
but IMHO your patch has been almost complete since last CF and seems
something we want... i made some performance test that didn't give me
good results last time but didn't show them and wasn't able to
reproduce so maybe it was something in my machine...
so, i would say add it and if the CF manager disagrees he can say so
--
Jaime Casanova www.2ndQuadrant.com
Professional PostgreSQL: Soporte 24x7 y capacitación
On Mon, Jan 16, 2012 at 12:06 PM, Marti Raudsepp <marti@juffo.org> wrote:
Here's v6 of my expression caching patch.
The patch is not attached.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Fri, Jan 27, 2012 at 16:49, Robert Haas <robertmhaas@gmail.com> wrote:
The patch is not attached.
Very sorry, I keep doing that :(
Attached now.
Regards,
Marti
Attachments:
cacheexpr-v6.patchtext/x-patch; charset=US-ASCII; name=cacheexpr-v6.patchDownload
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 110480f..a9dbb87 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2296,7 +2296,9 @@ BeginCopyFrom(Relation rel,
{
/* Initialize expressions in copycontext. */
defexprs[num_defaults] = ExecInitExpr(
- expression_planner((Expr *) defexpr), NULL);
+ expression_planner((Expr *) defexpr),
+ NULL,
+ true);
defmap[num_defaults] = attnum - 1;
num_defaults++;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d0843b2..41293e5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3562,7 +3562,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
NewColumnValue *ex = lfirst(l);
/* expr already planned */
- ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
+ ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL, true);
}
notnull_attrs = NIL;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 0043bf1..b2de895 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3048,7 +3048,7 @@ GetDomainConstraints(Oid typeOid)
r = makeNode(DomainConstraintState);
r->constrainttype = DOM_CONSTRAINT_CHECK;
r->name = pstrdup(NameStr(c->conname));
- r->check_expr = ExecInitExpr(check_expr, NULL);
+ r->check_expr = ExecInitExpr(check_expr, NULL, false);
/*
* use lcons() here because constraints of lower domains should be
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 4a6baeb..3113f83 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -51,12 +51,20 @@
#include "pgstat.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/typcache.h"
#include "utils/xml.h"
+typedef struct
+{
+ PlanState *parent;
+ bool useCache;
+} ExecInitExprContext;
+
+
/* static function decls */
static Datum ExecEvalArrayRef(ArrayRefExprState *astate,
ExprContext *econtext,
@@ -157,6 +165,12 @@ static Datum ExecEvalCoerceToDomain(CoerceToDomainState *cstate,
static Datum ExecEvalCoerceToDomainValue(ExprState *exprstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalCacheExpr(CacheExprState *cstate,
+ ExprContext *econtext,
+ bool *isNull,ExprDoneCond *isDone);
+static Datum ExecEvalCacheExprResult(CacheExprState *cstate,
+ ExprContext *econtext,
+ bool *isNull,ExprDoneCond *isDone);
static Datum ExecEvalFieldSelect(FieldSelectState *fstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
@@ -174,6 +188,8 @@ static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
+static ExprState *ExecInitExprMutator(Expr *node,
+ const ExecInitExprContext *context);
/* ----------------------------------------------------------------
@@ -3754,6 +3770,63 @@ ExecEvalBooleanTest(GenericExprState *bstate,
}
}
+/* ----------------------------------------------------------------
+ * ExecEvalCacheExpr
+ *
+ * Evaluates a cachable expression for the first time and updates
+ * xprstate.evalfunc to return cached result next time
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalCacheExpr(CacheExprState *cstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ MemoryContext oldcontext;
+ Datum result;
+ bool resultTypByVal;
+ int16 resultTypLen;
+ Oid resultType;
+
+ result = ExecEvalExpr(cstate->arg, econtext, isNull, isDone);
+
+ if (!cstate->enabled)
+ return result; /* Cache disabled, pass thru the result */
+
+ /* Set-returning expressions can't be cached */
+ Assert(isDone == NULL || *isDone == ExprSingleResult);
+
+ /* Figure out type and size for copy */
+ resultType = exprType((Node *) ((CacheExpr *) cstate->xprstate.expr)->arg);
+ get_typlenbyval(resultType, &resultTypLen, &resultTypByVal);
+
+ /* This cached datum has to persist for the whole query */
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ cstate->result = datumCopy(result, resultTypByVal, resultTypLen);
+ cstate->isNull = *isNull;
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Subsequent calls will return the cached result */
+ cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCacheExprResult;
+
+ return cstate->result;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEvalCacheExprResult
+ *
+ * Return the already-cached result, computed in ExecEvalCacheExpr
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalCacheExprResult(CacheExprState *cstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+{
+ if (isDone)
+ *isDone = ExprSingleResult;
+ *isNull = cstate->isNull;
+ return cstate->result;
+}
+
/*
* ExecEvalCoerceToDomain
*
@@ -4217,13 +4290,30 @@ ExecEvalExprSwitchContext(ExprState *expression,
*
* 'node' is the root of the expression tree to examine
* 'parent' is the PlanState node that owns the expression.
+ * 'useCache' enables caching for stable/parameterized expressions.
*
* 'parent' may be NULL if we are preparing an expression that is not
* associated with a plan tree. (If so, it can't have aggs or subplans.)
* This case should usually come through ExecPrepareExpr, not directly here.
+ *
+ * 'useCache' may only be true if it's guaranteed that all executions of the
+ * expression use the same snapshot and same external params. It should also
+ * be false if the expression is only executed once.
*/
ExprState *
-ExecInitExpr(Expr *node, PlanState *parent)
+ExecInitExpr(Expr *node, PlanState *parent, bool useCache)
+{
+ ExecInitExprContext context;
+
+ context.parent = parent;
+ context.useCache = useCache;
+
+ return ExecInitExprMutator(node, &context);
+}
+
+static ExprState *
+ExecInitExprMutator(Expr *node,
+ const ExecInitExprContext *context)
{
ExprState *state;
@@ -4273,16 +4363,16 @@ ExecInitExpr(Expr *node, PlanState *parent)
AggrefExprState *astate = makeNode(AggrefExprState);
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAggref;
- if (parent && IsA(parent, AggState))
+ if (context->parent && IsA(context->parent, AggState))
{
- AggState *aggstate = (AggState *) parent;
+ AggState *aggstate = (AggState *) context->parent;
int naggs;
aggstate->aggs = lcons(astate, aggstate->aggs);
naggs = ++aggstate->numaggs;
- astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
- parent);
+ astate->args = (List *)
+ ExecInitExprMutator((Expr *) aggref->args, context);
/*
* Complain if the aggregate's arguments contain any
@@ -4309,9 +4399,9 @@ ExecInitExpr(Expr *node, PlanState *parent)
WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc;
- if (parent && IsA(parent, WindowAggState))
+ if (context->parent && IsA(context->parent, WindowAggState))
{
- WindowAggState *winstate = (WindowAggState *) parent;
+ WindowAggState *winstate = (WindowAggState *) context->parent;
int nfuncs;
winstate->funcs = lcons(wfstate, winstate->funcs);
@@ -4319,8 +4409,8 @@ ExecInitExpr(Expr *node, PlanState *parent)
if (wfunc->winagg)
winstate->numaggs++;
- wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
- parent);
+ wfstate->args = (List *)
+ ExecInitExprMutator((Expr *) wfunc->args, context);
/*
* Complain if the windowfunc's arguments contain any
@@ -4348,12 +4438,12 @@ ExecInitExpr(Expr *node, PlanState *parent)
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayRef;
astate->refupperindexpr = (List *)
- ExecInitExpr((Expr *) aref->refupperindexpr, parent);
+ ExecInitExprMutator((Expr *) aref->refupperindexpr, context);
astate->reflowerindexpr = (List *)
- ExecInitExpr((Expr *) aref->reflowerindexpr, parent);
- astate->refexpr = ExecInitExpr(aref->refexpr, parent);
- astate->refassgnexpr = ExecInitExpr(aref->refassgnexpr,
- parent);
+ ExecInitExprMutator((Expr *) aref->reflowerindexpr, context);
+ astate->refexpr = ExecInitExprMutator(aref->refexpr, context);
+ astate->refassgnexpr =
+ ExecInitExprMutator(aref->refassgnexpr, context);
/* do one-time catalog lookups for type info */
astate->refattrlength = get_typlen(aref->refarraytype);
get_typlenbyvalalign(aref->refelemtype,
@@ -4370,7 +4460,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFunc;
fstate->args = (List *)
- ExecInitExpr((Expr *) funcexpr->args, parent);
+ ExecInitExprMutator((Expr *) funcexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
@@ -4382,7 +4472,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOper;
fstate->args = (List *)
- ExecInitExpr((Expr *) opexpr->args, parent);
+ ExecInitExprMutator((Expr *) opexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
@@ -4394,7 +4484,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalDistinct;
fstate->args = (List *)
- ExecInitExpr((Expr *) distinctexpr->args, parent);
+ ExecInitExprMutator((Expr *) distinctexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
@@ -4406,7 +4496,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullIf;
fstate->args = (List *)
- ExecInitExpr((Expr *) nullifexpr->args, parent);
+ ExecInitExprMutator((Expr *) nullifexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
@@ -4418,7 +4508,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
sstate->fxprstate.xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalScalarArrayOp;
sstate->fxprstate.args = (List *)
- ExecInitExpr((Expr *) opexpr->args, parent);
+ ExecInitExprMutator((Expr *) opexpr->args, context);
sstate->fxprstate.func.fn_oid = InvalidOid; /* not initialized */
sstate->element_type = InvalidOid; /* ditto */
state = (ExprState *) sstate;
@@ -4446,7 +4536,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
break;
}
bstate->args = (List *)
- ExecInitExpr((Expr *) boolexpr->args, parent);
+ ExecInitExprMutator((Expr *) boolexpr->args, context);
state = (ExprState *) bstate;
}
break;
@@ -4455,13 +4545,14 @@ ExecInitExpr(Expr *node, PlanState *parent)
SubPlan *subplan = (SubPlan *) node;
SubPlanState *sstate;
- if (!parent)
+ if (!context->parent)
elog(ERROR, "SubPlan found with no parent plan");
- sstate = ExecInitSubPlan(subplan, parent);
+ sstate = ExecInitSubPlan(subplan, context->parent);
/* Add SubPlanState nodes to parent->subPlan */
- parent->subPlan = lappend(parent->subPlan, sstate);
+ context->parent->subPlan = lappend(context->parent->subPlan,
+ sstate);
state = (ExprState *) sstate;
}
@@ -4471,10 +4562,10 @@ ExecInitExpr(Expr *node, PlanState *parent)
AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
AlternativeSubPlanState *asstate;
- if (!parent)
+ if (!context->parent)
elog(ERROR, "AlternativeSubPlan found with no parent plan");
- asstate = ExecInitAlternativeSubPlan(asplan, parent);
+ asstate = ExecInitAlternativeSubPlan(asplan, context->parent);
state = (ExprState *) asstate;
}
@@ -4485,7 +4576,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
FieldSelectState *fstate = makeNode(FieldSelectState);
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldSelect;
- fstate->arg = ExecInitExpr(fselect->arg, parent);
+ fstate->arg = ExecInitExprMutator(fselect->arg, context);
fstate->argdesc = NULL;
state = (ExprState *) fstate;
}
@@ -4496,8 +4587,9 @@ ExecInitExpr(Expr *node, PlanState *parent)
FieldStoreState *fstate = makeNode(FieldStoreState);
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldStore;
- fstate->arg = ExecInitExpr(fstore->arg, parent);
- fstate->newvals = (List *) ExecInitExpr((Expr *) fstore->newvals, parent);
+ fstate->arg = ExecInitExprMutator(fstore->arg, context);
+ fstate->newvals = (List *)
+ ExecInitExprMutator((Expr *) fstore->newvals, context);
fstate->argdesc = NULL;
state = (ExprState *) fstate;
}
@@ -4508,7 +4600,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRelabelType;
- gstate->arg = ExecInitExpr(relabel->arg, parent);
+ gstate->arg = ExecInitExprMutator(relabel->arg, context);
state = (ExprState *) gstate;
}
break;
@@ -4520,7 +4612,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
bool typisvarlena;
iostate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceViaIO;
- iostate->arg = ExecInitExpr(iocoerce->arg, parent);
+ iostate->arg = ExecInitExprMutator(iocoerce->arg, context);
/* lookup the result type's input function */
getTypeInputInfo(iocoerce->resulttype, &iofunc,
&iostate->intypioparam);
@@ -4538,7 +4630,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
ArrayCoerceExprState *astate = makeNode(ArrayCoerceExprState);
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayCoerceExpr;
- astate->arg = ExecInitExpr(acoerce->arg, parent);
+ astate->arg = ExecInitExprMutator(acoerce->arg, context);
astate->resultelemtype = get_element_type(acoerce->resulttype);
if (astate->resultelemtype == InvalidOid)
ereport(ERROR,
@@ -4558,7 +4650,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
ConvertRowtypeExprState *cstate = makeNode(ConvertRowtypeExprState);
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype;
- cstate->arg = ExecInitExpr(convert->arg, parent);
+ cstate->arg = ExecInitExprMutator(convert->arg, context);
state = (ExprState *) cstate;
}
break;
@@ -4570,7 +4662,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
ListCell *l;
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase;
- cstate->arg = ExecInitExpr(caseexpr->arg, parent);
+ cstate->arg = ExecInitExprMutator(caseexpr->arg, context);
foreach(l, caseexpr->args)
{
CaseWhen *when = (CaseWhen *) lfirst(l);
@@ -4579,12 +4671,13 @@ ExecInitExpr(Expr *node, PlanState *parent)
Assert(IsA(when, CaseWhen));
wstate->xprstate.evalfunc = NULL; /* not used */
wstate->xprstate.expr = (Expr *) when;
- wstate->expr = ExecInitExpr(when->expr, parent);
- wstate->result = ExecInitExpr(when->result, parent);
+ wstate->expr = ExecInitExprMutator(when->expr, context);
+ wstate->result = ExecInitExprMutator(when->result, context);
outlist = lappend(outlist, wstate);
}
cstate->args = outlist;
- cstate->defresult = ExecInitExpr(caseexpr->defresult, parent);
+ cstate->defresult =
+ ExecInitExprMutator(caseexpr->defresult, context);
state = (ExprState *) cstate;
}
break;
@@ -4601,7 +4694,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
- estate = ExecInitExpr(e, parent);
+ estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
astate->elements = outlist;
@@ -4669,7 +4762,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
*/
e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid);
}
- estate = ExecInitExpr(e, parent);
+ estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
i++;
}
@@ -4696,7 +4789,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
- estate = ExecInitExpr(e, parent);
+ estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
rstate->largs = outlist;
@@ -4707,7 +4800,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
- estate = ExecInitExpr(e, parent);
+ estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
rstate->rargs = outlist;
@@ -4760,7 +4853,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
- estate = ExecInitExpr(e, parent);
+ estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
cstate->args = outlist;
@@ -4781,7 +4874,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
- estate = ExecInitExpr(e, parent);
+ estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
mstate->args = outlist;
@@ -4818,7 +4911,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
Expr *e = (Expr *) lfirst(arg);
ExprState *estate;
- estate = ExecInitExpr(e, parent);
+ estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
xstate->named_args = outlist;
@@ -4829,7 +4922,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
Expr *e = (Expr *) lfirst(arg);
ExprState *estate;
- estate = ExecInitExpr(e, parent);
+ estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
xstate->args = outlist;
@@ -4843,7 +4936,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
NullTestState *nstate = makeNode(NullTestState);
nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest;
- nstate->arg = ExecInitExpr(ntest->arg, parent);
+ nstate->arg = ExecInitExprMutator(ntest->arg, context);
nstate->argdesc = NULL;
state = (ExprState *) nstate;
}
@@ -4854,17 +4947,34 @@ ExecInitExpr(Expr *node, PlanState *parent)
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalBooleanTest;
- gstate->arg = ExecInitExpr(btest->arg, parent);
+ gstate->arg = ExecInitExprMutator(btest->arg, context);
state = (ExprState *) gstate;
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+ CacheExprState *cstate = makeNode(CacheExprState);
+
+ cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCacheExpr;
+ cstate->arg = ExecInitExprMutator(cache->arg, context);
+ /*
+ * If useCache=false, in theory we could simply skip creating
+ * the CacheExprState node in the first place, but that might
+ * be surprising to future developers.
+ */
+ cstate->enabled = context->useCache;
+
+ state = (ExprState *) cstate;
+ }
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
CoerceToDomainState *cstate = makeNode(CoerceToDomainState);
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceToDomain;
- cstate->arg = ExecInitExpr(ctest->arg, parent);
+ cstate->arg = ExecInitExprMutator(ctest->arg, context);
cstate->constraints = GetDomainConstraints(ctest->resulttype);
state = (ExprState *) cstate;
}
@@ -4879,7 +4989,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = NULL; /* not used */
- gstate->arg = ExecInitExpr(tle->expr, parent);
+ gstate->arg = ExecInitExprMutator(tle->expr, context);
state = (ExprState *) gstate;
}
break;
@@ -4891,8 +5001,8 @@ ExecInitExpr(Expr *node, PlanState *parent)
foreach(l, (List *) node)
{
outlist = lappend(outlist,
- ExecInitExpr((Expr *) lfirst(l),
- parent));
+ ExecInitExprMutator((Expr *) lfirst(l),
+ context));
}
/* Don't fall through to the "common" code below */
return (ExprState *) outlist;
@@ -4931,7 +5041,7 @@ ExecPrepareExpr(Expr *node, EState *estate)
node = expression_planner(node);
- result = ExecInitExpr(node, NULL);
+ result = ExecInitExpr(node, NULL, false);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index c9aa921..618a485 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -1444,10 +1444,12 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
*/
aggstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) aggstate);
+ (PlanState *) aggstate,
+ true);
aggstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) aggstate);
+ (PlanState *) aggstate,
+ true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 849665d..f7e2d1c 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -569,13 +569,16 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
scanstate->bitmapqualorig = (List *)
ExecInitExpr((Expr *) node->bitmapqualorig,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c
index 7ab15ac..93c2c04 100644
--- a/src/backend/executor/nodeCtescan.c
+++ b/src/backend/executor/nodeCtescan.c
@@ -241,10 +241,12 @@ ExecInitCteScan(CteScan *node, EState *estate, int eflags)
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 9204859..1602290 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -129,10 +129,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index a700762..955c4ee 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -153,10 +153,12 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
/*
* Now determine if the function returns a simple or composite type, and
@@ -217,7 +219,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
*/
scanstate->tuplestorestate = NULL;
scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
scanstate->ss.ps.ps_TupFromTlist = false;
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 80e282b..881e8d7 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -228,10 +228,12 @@ ExecInitGroup(Group *node, EState *estate, int eflags)
*/
grpstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) grpstate);
+ (PlanState *) grpstate,
+ true);
grpstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) grpstate);
+ (PlanState *) grpstate,
+ true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index cabe663..7d65e6d 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -179,10 +179,12 @@ ExecInitHash(Hash *node, EState *estate, int eflags)
*/
hashstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) hashstate);
+ (PlanState *) hashstate,
+ true);
hashstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) hashstate);
+ (PlanState *) hashstate,
+ true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 0ea490b..b42ad3f 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -463,17 +463,21 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
*/
hjstate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
- (PlanState *) hjstate);
+ (PlanState *) hjstate,
+ true);
hjstate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
- (PlanState *) hjstate);
+ (PlanState *) hjstate,
+ true);
hjstate->js.jointype = node->join.jointype;
hjstate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
- (PlanState *) hjstate);
+ (PlanState *) hjstate,
+ true);
hjstate->hashclauses = (List *)
ExecInitExpr((Expr *) node->hashclauses,
- (PlanState *) hjstate);
+ (PlanState *) hjstate,
+ true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 7f1100e..e7386ec 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -364,13 +364,16 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
*/
indexstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) indexstate);
+ (PlanState *) indexstate,
+ true);
indexstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) indexstate);
+ (PlanState *) indexstate,
+ true);
indexstate->indexqual = (List *)
ExecInitExpr((Expr *) node->indexqual,
- (PlanState *) indexstate);
+ (PlanState *) indexstate,
+ true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index e577bac..a2c979d 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -494,13 +494,16 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
*/
indexstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) indexstate);
+ (PlanState *) indexstate,
+ true);
indexstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) indexstate);
+ (PlanState *) indexstate,
+ true);
indexstate->indexqualorig = (List *)
ExecInitExpr((Expr *) node->indexqualorig,
- (PlanState *) indexstate);
+ (PlanState *) indexstate,
+ true);
/*
* tuple table initialization
@@ -817,7 +820,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
- ExecInitExpr(rightop, planstate);
+ ExecInitExpr(rightop, planstate, true);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
@@ -944,7 +947,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
}
runtime_keys[n_runtime_keys].scan_key = this_sub_key;
runtime_keys[n_runtime_keys].key_expr =
- ExecInitExpr(rightop, planstate);
+ ExecInitExpr(rightop, planstate, true);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
@@ -1062,7 +1065,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
- ExecInitExpr(rightop, planstate);
+ ExecInitExpr(rightop, planstate, true);
/*
* Careful here: the runtime expression is not of
@@ -1080,7 +1083,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
/* Executor has to expand the array value */
array_keys[n_array_keys].scan_key = this_scan_key;
array_keys[n_array_keys].array_expr =
- ExecInitExpr(rightop, planstate);
+ ExecInitExpr(rightop, planstate, true);
/* the remaining fields were zeroed by palloc0 */
n_array_keys++;
scanvalue = (Datum) 0;
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index 8936187..3e2b643 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -399,9 +399,11 @@ ExecInitLimit(Limit *node, EState *estate, int eflags)
* initialize child expressions
*/
limitstate->limitOffset = ExecInitExpr((Expr *) node->limitOffset,
- (PlanState *) limitstate);
+ (PlanState *) limitstate,
+ true);
limitstate->limitCount = ExecInitExpr((Expr *) node->limitCount,
- (PlanState *) limitstate);
+ (PlanState *) limitstate,
+ true);
/*
* Tuple table initialization (XXX not actually used...)
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index f921847..be45c3e 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -206,8 +206,8 @@ MJExamineQuals(List *mergeclauses,
/*
* Prepare the input expressions for execution.
*/
- clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent);
- clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent);
+ clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent, true);
+ clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent, true);
/* Set up sort support data */
clause->ssup.ssup_cxt = CurrentMemoryContext;
@@ -1498,14 +1498,17 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
*/
mergestate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
- (PlanState *) mergestate);
+ (PlanState *) mergestate,
+ true);
mergestate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
- (PlanState *) mergestate);
+ (PlanState *) mergestate,
+ true);
mergestate->js.jointype = node->join.jointype;
mergestate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
- (PlanState *) mergestate);
+ (PlanState *) mergestate,
+ true);
mergestate->mj_ConstFalseJoin = false;
/* mergeclauses are handled below */
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 37b70b8..88098f8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -995,7 +995,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
List *rlist = (List *) lfirst(l);
List *rliststate;
- rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
+ rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps, true);
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rliststate, econtext, slot,
resultRelInfo->ri_RelationDesc->rd_att);
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 11d027b..0452478 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -322,14 +322,17 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
*/
nlstate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
- (PlanState *) nlstate);
+ (PlanState *) nlstate,
+ true);
nlstate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
- (PlanState *) nlstate);
+ (PlanState *) nlstate,
+ true);
nlstate->js.jointype = node->join.jointype;
nlstate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
- (PlanState *) nlstate);
+ (PlanState *) nlstate,
+ true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
index b51efd8..5e0869c 100644
--- a/src/backend/executor/nodeResult.c
+++ b/src/backend/executor/nodeResult.c
@@ -240,12 +240,15 @@ ExecInitResult(Result *node, EState *estate, int eflags)
*/
resstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) resstate);
+ (PlanState *) resstate,
+ true);
resstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) resstate);
+ (PlanState *) resstate,
+ true);
resstate->resconstantqual = ExecInitExpr((Expr *) node->resconstantqual,
- (PlanState *) resstate);
+ (PlanState *) resstate,
+ true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 9fcd332..17589fa 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -182,10 +182,12 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
*/
scanstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
scanstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index da31820..b24a372 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -661,8 +661,8 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
subplan->plan_id - 1);
/* Initialize subexpressions */
- sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
- sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
+ sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent, true);
+ sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent, true);
/*
* initialize my state
@@ -1102,7 +1102,8 @@ ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent)
* we're going to use?)
*/
asstate->subplans = (List *) ExecInitExpr((Expr *) asplan->subplans,
- parent);
+ parent,
+ true);
/*
* Select the one to be used. For this, we need an estimate of the number
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
index b5ef152..040ba5c 100644
--- a/src/backend/executor/nodeSubqueryscan.c
+++ b/src/backend/executor/nodeSubqueryscan.c
@@ -122,10 +122,12 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags)
*/
subquerystate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) subquerystate);
+ (PlanState *) subquerystate,
+ true);
subquerystate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) subquerystate);
+ (PlanState *) subquerystate,
+ true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 32b8abf..3842911 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -506,14 +506,17 @@ ExecInitTidScan(TidScan *node, EState *estate, int eflags)
*/
tidstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) tidstate);
+ (PlanState *) tidstate,
+ true);
tidstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) tidstate);
+ (PlanState *) tidstate,
+ true);
tidstate->tss_tidquals = (List *)
ExecInitExpr((Expr *) node->tidquals,
- (PlanState *) tidstate);
+ (PlanState *) tidstate,
+ true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index fc17677..c43ef37 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -118,7 +118,7 @@ ValuesNext(ValuesScanState *node)
* is a SubPlan, and there shouldn't be any (any subselects in the
* VALUES list should be InitPlans).
*/
- exprstatelist = (List *) ExecInitExpr((Expr *) exprlist, NULL);
+ exprstatelist = (List *) ExecInitExpr((Expr *) exprlist, NULL, true);
/* parser should have checked all sublists are the same length */
Assert(list_length(exprstatelist) == slot->tts_tupleDescriptor->natts);
@@ -231,10 +231,12 @@ ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags)
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
/*
* get info about values list
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 5082a50..4692005 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -1455,7 +1455,8 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
winstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) winstate);
+ (PlanState *) winstate,
+ true);
/*
* WindowAgg nodes never have quals, since they can only occur at the
@@ -1622,9 +1623,11 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
/* initialize frame bound offset expressions */
winstate->startOffset = ExecInitExpr((Expr *) node->startOffset,
- (PlanState *) winstate);
+ (PlanState *) winstate,
+ true);
winstate->endOffset = ExecInitExpr((Expr *) node->endOffset,
- (PlanState *) winstate);
+ (PlanState *) winstate,
+ true);
winstate->all_first = true;
winstate->partition_spooled = false;
diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c
index e2f3dd4..e68d268 100644
--- a/src/backend/executor/nodeWorktablescan.c
+++ b/src/backend/executor/nodeWorktablescan.c
@@ -160,10 +160,12 @@ ExecInitWorkTableScan(WorkTableScan *node, EState *estate, int eflags)
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
+ (PlanState *) scanstate,
+ true);
/*
* tuple table initialization
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 71da0d8..e84ed69 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1684,6 +1684,19 @@ _copyBooleanTest(const BooleanTest *from)
}
/*
+ * _copyCacheExpr
+ */
+static CacheExpr *
+_copyCacheExpr(const CacheExpr *from)
+{
+ CacheExpr *newnode = makeNode(CacheExpr);
+
+ COPY_NODE_FIELD(arg);
+
+ return newnode;
+}
+
+/*
* _copyCoerceToDomain
*/
static CoerceToDomain *
@@ -4006,6 +4019,9 @@ copyObject(const void *from)
case T_BooleanTest:
retval = _copyBooleanTest(from);
break;
+ case T_CacheExpr:
+ retval = _copyCacheExpr(from);
+ break;
case T_CoerceToDomain:
retval = _copyCoerceToDomain(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index ba949db..b3aed1a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -678,6 +678,14 @@ _equalBooleanTest(const BooleanTest *a, const BooleanTest *b)
}
static bool
+_equalCacheExpr(const CacheExpr *a, const CacheExpr *b)
+{
+ COMPARE_NODE_FIELD(arg);
+
+ return true;
+}
+
+static bool
_equalCoerceToDomain(const CoerceToDomain *a, const CoerceToDomain *b)
{
COMPARE_NODE_FIELD(arg);
@@ -2562,6 +2570,9 @@ equal(const void *a, const void *b)
case T_BooleanTest:
retval = _equalBooleanTest(a, b);
break;
+ case T_CacheExpr:
+ retval = _equalCacheExpr(a, b);
+ break;
case T_CoerceToDomain:
retval = _equalCoerceToDomain(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index bb5cdae..c8549eb 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -471,6 +471,21 @@ makeFuncExpr(Oid funcid, Oid rettype, List *args,
}
/*
+ * makeCacheExpr -
+ * build an expression node for a cachable expression.
+ */
+CacheExpr *
+makeCacheExpr(Expr *arg)
+{
+ CacheExpr *cacheexpr;
+
+ cacheexpr = makeNode(CacheExpr);
+ cacheexpr->arg = arg;
+
+ return cacheexpr;
+}
+
+/*
* makeDefElem -
* build a DefElem node
*
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 51459c4..fab2104 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -212,6 +212,9 @@ exprType(const Node *expr)
case T_BooleanTest:
type = BOOLOID;
break;
+ case T_CacheExpr:
+ type = exprType((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
type = ((const CoerceToDomain *) expr)->resulttype;
break;
@@ -792,6 +795,9 @@ exprCollation(const Node *expr)
case T_BooleanTest:
coll = InvalidOid; /* result is always boolean */
break;
+ case T_CacheExpr:
+ coll = exprCollation((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
coll = ((const CoerceToDomain *) expr)->resultcollid;
break;
@@ -1263,6 +1269,10 @@ exprLocation(const Node *expr)
/* just use argument's location */
loc = exprLocation((Node *) ((const BooleanTest *) expr)->arg);
break;
+ case T_CacheExpr:
+ /* original expression location */
+ loc = exprLocation((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
{
const CoerceToDomain *cexpr = (const CoerceToDomain *) expr;
@@ -1722,6 +1732,8 @@ expression_tree_walker(Node *node,
return walker(((NullTest *) node)->arg, context);
case T_BooleanTest:
return walker(((BooleanTest *) node)->arg, context);
+ case T_CacheExpr:
+ return walker(((CacheExpr *) node)->arg, context);
case T_CoerceToDomain:
return walker(((CoerceToDomain *) node)->arg, context);
case T_TargetEntry:
@@ -2374,6 +2386,16 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+ CacheExpr *newnode;
+
+ FLATCOPY(newnode, cache, CacheExpr);
+ MUTATE(newnode->arg, cache->arg, Expr *);
+ return (Node *) newnode;
+ }
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
@@ -2781,6 +2803,8 @@ bool
return walker(((NullTest *) node)->arg, context);
case T_BooleanTest:
return walker(((BooleanTest *) node)->arg, context);
+ case T_CacheExpr:
+ return walker(((CacheExpr *) node)->arg, context);
case T_JoinExpr:
{
JoinExpr *join = (JoinExpr *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8bc1947..5b42a72 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1367,6 +1367,14 @@ _outBooleanTest(StringInfo str, const BooleanTest *node)
}
static void
+_outCacheExpr(StringInfo str, const CacheExpr *node)
+{
+ WRITE_NODE_TYPE("CACHEEXPR");
+
+ WRITE_NODE_FIELD(arg);
+}
+
+static void
_outCoerceToDomain(StringInfo str, const CoerceToDomain *node)
{
WRITE_NODE_TYPE("COERCETODOMAIN");
@@ -2921,6 +2929,9 @@ _outNode(StringInfo str, const void *obj)
case T_BooleanTest:
_outBooleanTest(str, obj);
break;
+ case T_CacheExpr:
+ _outCacheExpr(str, obj);
+ break;
case T_CoerceToDomain:
_outCoerceToDomain(str, obj);
break;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index e1c070e..d9f06dc 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -2752,6 +2752,24 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
*/
return false; /* don't recurse into children */
}
+ else if (IsA(node, CacheExpr))
+ {
+ cost_qual_eval_context locContext;
+ CacheExpr *cache = (CacheExpr *) node;
+
+ locContext.root = context->root;
+ locContext.total.startup = 0;
+ locContext.total.per_tuple = 0;
+
+ cost_qual_eval_walker((Node *) cache->arg, &locContext);
+
+ /* Account all cached per-tuple costs once in the startup cost. */
+ context->total.startup +=
+ locContext.total.startup + locContext.total.per_tuple;
+
+ /* do NOT recurse into children */
+ return false;
+ }
else if (IsA(node, CoerceViaIO))
{
CoerceViaIO *iocoerce = (CoerceViaIO *) node;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 126d494..6c681d8 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -97,43 +97,50 @@ static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
static List *find_nonnullable_vars_walker(Node *node, bool top_level);
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
static bool set_coercionform_dontcare_walker(Node *node, void *context);
-static Node *eval_const_expressions_mutator(Node *node,
- eval_const_expressions_context *context);
+static Node *caching_const_expressions_mutator(Node *node,
+ eval_const_expressions_context *context);
+static Node *const_expressions_mutator(Node *node,
+ eval_const_expressions_context *context,
+ bool *cachable);
static List *simplify_or_arguments(List *args,
eval_const_expressions_context *context,
- bool *haveNull, bool *forceTrue);
+ bool *haveNull, bool *forceTrue, bool *cachable);
static List *simplify_and_arguments(List *args,
eval_const_expressions_context *context,
- bool *haveNull, bool *forceFalse);
+ bool *haveNull, bool *forceFalse, bool *cachable);
static Node *simplify_boolean_equality(Oid opno, List *args);
static Expr *simplify_function(Expr *oldexpr, Oid funcid,
Oid result_type, int32 result_typmod, Oid result_collid,
Oid input_collid, List **args,
- bool has_named_args,
bool allow_inline,
- eval_const_expressions_context *context);
+ eval_const_expressions_context *context,
+ bool *cachable);
+static List *simplify_copy_function_arguments(List *old_args, Oid result_type,
+ HeapTuple func_tuple);
static List *reorder_function_arguments(List *args, Oid result_type,
- HeapTuple func_tuple,
- eval_const_expressions_context *context);
+ HeapTuple func_tuple);
static List *add_function_defaults(List *args, Oid result_type,
- HeapTuple func_tuple,
- eval_const_expressions_context *context);
+ HeapTuple func_tuple);
static List *fetch_function_defaults(HeapTuple func_tuple);
static void recheck_cast_function_args(List *args, Oid result_type,
HeapTuple func_tuple);
static Expr *evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
HeapTuple func_tuple,
- eval_const_expressions_context *context);
+ eval_const_expressions_context *context,
+ bool *cachable);
static Expr *inline_function(Oid funcid, Oid result_type, Oid result_collid,
Oid input_collid, List *args,
HeapTuple func_tuple,
- eval_const_expressions_context *context);
+ eval_const_expressions_context *context,
+ bool *cachable);
static Node *substitute_actual_parameters(Node *expr, int nargs, List *args,
int *usecounts);
static Node *substitute_actual_parameters_mutator(Node *node,
substitute_actual_parameters_context *context);
static void sql_inline_error_callback(void *arg);
+static inline bool is_cache_useful(Expr *expr);
+static Expr *insert_cache(Expr *expr);
static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
static Query *substitute_actual_srf_parameters(Query *expr,
@@ -2028,6 +2035,11 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum,
* assumption in the presence of user-defined functions; do we need a
* pg_proc flag that prevents discarding the execution of a function?)
*
+ * We also insert CacheExpr nodes above expressions that cannot be
+ * evaluated at planning time, but are constant at execution time.
+ * This includes expressions that contain stable function calls and
+ * Param references.
+ *
* We do understand that certain functions may deliver non-constant
* results even with constant inputs, "nextval()" being the classic
* example. Functions that are not marked "immutable" in pg_proc
@@ -2066,7 +2078,8 @@ eval_const_expressions(PlannerInfo *root, Node *node)
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = false; /* safe transformations only */
- return eval_const_expressions_mutator(node, &context);
+
+ return caching_const_expressions_mutator(node, &context);
}
/*--------------------
@@ -2084,12 +2097,14 @@ eval_const_expressions(PlannerInfo *root, Node *node)
* value of the Param.
* 2. Fold stable, as well as immutable, functions to constants.
* 3. Reduce PlaceHolderVar nodes to their contained expressions.
+ * 4. Strip CacheExpr nodes, as planner only wants to evaluate once.
*--------------------
*/
Node *
estimate_expression_value(PlannerInfo *root, Node *node)
{
eval_const_expressions_context context;
+ bool isCachable = true;
context.boundParams = root->glob->boundParams; /* bound Params */
/* we do not need to mark the plan as depending on inlined functions */
@@ -2097,13 +2112,43 @@ estimate_expression_value(PlannerInfo *root, Node *node)
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = true; /* unsafe transformations OK */
- return eval_const_expressions_mutator(node, &context);
+
+ return const_expressions_mutator(node, &context, &isCachable);
}
+/*
+ * Calls const_expressions_mutator on the expression tree and automatically
+ * adds a CacheExpr node if the expression is cachable.
+ */
static Node *
-eval_const_expressions_mutator(Node *node,
- eval_const_expressions_context *context)
+caching_const_expressions_mutator(Node *node,
+ eval_const_expressions_context *context)
{
+ bool isCachable = true;
+
+ if (node == NULL)
+ return NULL;
+
+ node = const_expressions_mutator(node, context, &isCachable);
+ if (isCachable)
+ node = (Node *) insert_cache((Expr *) node);
+
+ return node;
+}
+
+/*
+ * Returns a mutated node tree and determines its cachability.
+ *
+ * The caller must make sure that cachable points to a boolean value that's
+ * initialized to TRUE.
+ */
+static Node *
+const_expressions_mutator(Node *node,
+ eval_const_expressions_context *context,
+ bool *cachable)
+{
+ Assert(*cachable == true);
+
if (node == NULL)
return NULL;
switch (nodeTag(node))
@@ -2112,6 +2157,14 @@ eval_const_expressions_mutator(Node *node,
{
Param *param = (Param *) node;
+ /*
+ * Only externally-supplied parameters are stable. Other
+ * params are used for passing changing values within the
+ * executor
+ */
+ if (param->paramkind != PARAM_EXTERN)
+ *cachable = false;
+
/* Look to see if we've been given a value for this Param */
if (param->paramkind == PARAM_EXTERN &&
context->boundParams != NULL &&
@@ -2163,27 +2216,9 @@ eval_const_expressions_mutator(Node *node,
case T_FuncExpr:
{
FuncExpr *expr = (FuncExpr *) node;
- List *args;
- bool has_named_args;
+ List *args = expr->args;
Expr *simple;
FuncExpr *newexpr;
- ListCell *lc;
-
- /*
- * Reduce constants in the FuncExpr's arguments, and check to
- * see if there are any named args.
- */
- args = NIL;
- has_named_args = false;
- foreach(lc, expr->args)
- {
- Node *arg = (Node *) lfirst(lc);
-
- arg = eval_const_expressions_mutator(arg, context);
- if (IsA(arg, NamedArgExpr))
- has_named_args = true;
- args = lappend(args, arg);
- }
/*
* Code for op/func reduction is pretty bulky, so split it out
@@ -2199,9 +2234,9 @@ eval_const_expressions_mutator(Node *node,
expr->funccollid,
expr->inputcollid,
&args,
- has_named_args,
true,
- context);
+ context,
+ cachable);
if (simple) /* successfully simplified it */
return (Node *) simple;
@@ -2225,21 +2260,11 @@ eval_const_expressions_mutator(Node *node,
case T_OpExpr:
{
OpExpr *expr = (OpExpr *) node;
- List *args;
+ List *args = expr->args;
Expr *simple;
OpExpr *newexpr;
/*
- * Reduce constants in the OpExpr's arguments. We know args
- * is either NIL or a List node, so we can call
- * expression_tree_mutator directly rather than recursing to
- * self.
- */
- args = (List *) expression_tree_mutator((Node *) expr->args,
- eval_const_expressions_mutator,
- (void *) context);
-
- /*
* Need to get OID of underlying function. Okay to scribble
* on input to this extent.
*/
@@ -2255,7 +2280,8 @@ eval_const_expressions_mutator(Node *node,
expr->opcollid,
expr->inputcollid,
&args,
- false, true, context);
+ true, context,
+ cachable);
if (simple) /* successfully simplified it */
return (Node *) simple;
@@ -2292,35 +2318,49 @@ eval_const_expressions_mutator(Node *node,
case T_DistinctExpr:
{
DistinctExpr *expr = (DistinctExpr *) node;
- List *args;
- ListCell *arg;
+ List *args = NIL;
+ ListCell *lc;
+ Node *arg;
bool has_null_input = false;
bool all_null_input = true;
bool has_nonconst_input = false;
Expr *simple;
DistinctExpr *newexpr;
+ bool leftCachable = true;
+ bool rightCachable = true;
/*
- * Reduce constants in the DistinctExpr's arguments. We know
- * args is either NIL or a List node, so we can call
- * expression_tree_mutator directly rather than recursing to
- * self.
+ * Reduce constants in the DistinctExpr's arguments
+ *
+ * Note that simplify_function() might call the mutator
+ * function on arguments for a second time. However, this is
+ * harmless because it's only called when arguments are
+ * constant.
*/
- args = (List *) expression_tree_mutator((Node *) expr->args,
- eval_const_expressions_mutator,
- (void *) context);
+ Assert(list_length(expr->args) == 2);
+
+ arg = const_expressions_mutator(linitial(expr->args),
+ context,
+ &leftCachable);
+ args = lappend(args, arg);
+
+ arg = const_expressions_mutator(lsecond(expr->args),
+ context,
+ &rightCachable);
+ args = lappend(args, arg);
/*
* We must do our own check for NULLs because DistinctExpr has
* different results for NULL input than the underlying
* operator does.
*/
- foreach(arg, args)
+ foreach(lc, args)
{
- if (IsA(lfirst(arg), Const))
+ arg = lfirst(lc);
+ if (IsA(arg, Const))
{
- has_null_input |= ((Const *) lfirst(arg))->constisnull;
- all_null_input &= ((Const *) lfirst(arg))->constisnull;
+ has_null_input |= ((Const *) arg)->constisnull;
+ all_null_input &= ((Const *) arg)->constisnull;
}
else
has_nonconst_input = true;
@@ -2357,7 +2397,9 @@ eval_const_expressions_mutator(Node *node,
expr->opcollid,
expr->inputcollid,
&args,
- false, false, context);
+ false, context,
+ cachable);
+
if (simple) /* successfully simplified it */
{
/*
@@ -2372,6 +2414,32 @@ eval_const_expressions_mutator(Node *node,
return (Node *) csimple;
}
}
+ else if (!leftCachable || !rightCachable)
+ {
+ *cachable = false;
+ }
+ else
+ {
+ /*
+ * This expression is only cachable if the equality
+ * operator is not volatile.
+ */
+ HeapTuple func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(expr->opfuncid));
+ Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
+
+ if (funcform->provolatile == PROVOLATILE_VOLATILE)
+ *cachable = false;
+
+ ReleaseSysCache(func_tuple);
+ }
+
+ if (!(*cachable))
+ {
+ if (leftCachable)
+ linitial(args) = insert_cache((Expr *) linitial(args));
+ if (rightCachable)
+ lsecond(args) = insert_cache((Expr *) lsecond(args));
+ }
/*
* The expression cannot be simplified any further, so build
@@ -2404,7 +2472,8 @@ eval_const_expressions_mutator(Node *node,
newargs = simplify_or_arguments(expr->args,
context,
&haveNull,
- &forceTrue);
+ &forceTrue,
+ cachable);
if (forceTrue)
return makeBoolConst(true, false);
if (haveNull)
@@ -2432,7 +2501,8 @@ eval_const_expressions_mutator(Node *node,
newargs = simplify_and_arguments(expr->args,
context,
&haveNull,
- &forceFalse);
+ &forceFalse,
+ cachable);
if (forceFalse)
return makeBoolConst(false, false);
if (haveNull)
@@ -2456,8 +2526,9 @@ eval_const_expressions_mutator(Node *node,
Node *arg;
Assert(list_length(expr->args) == 1);
- arg = eval_const_expressions_mutator(linitial(expr->args),
- context);
+ arg = const_expressions_mutator(linitial(expr->args),
+ context,
+ cachable);
/*
* Use negate_clause() to see if we can simplify
@@ -2481,6 +2552,7 @@ eval_const_expressions_mutator(Node *node,
* XXX should we ereport() here instead? Probably this routine
* should never be invoked after SubPlan creation.
*/
+ *cachable = false;
return node;
case T_RelabelType:
{
@@ -2493,8 +2565,9 @@ eval_const_expressions_mutator(Node *node,
RelabelType *relabel = (RelabelType *) node;
Node *arg;
- arg = eval_const_expressions_mutator((Node *) relabel->arg,
- context);
+ arg = const_expressions_mutator((Node *) relabel->arg,
+ context,
+ cachable);
/*
* If we find stacked RelabelTypes (eg, from foo :: int ::
@@ -2528,7 +2601,6 @@ eval_const_expressions_mutator(Node *node,
case T_CoerceViaIO:
{
CoerceViaIO *expr = (CoerceViaIO *) node;
- Expr *arg;
List *args;
Oid outfunc;
bool outtypisvarlena;
@@ -2540,9 +2612,7 @@ eval_const_expressions_mutator(Node *node,
/*
* Reduce constants in the CoerceViaIO's argument.
*/
- arg = (Expr *) eval_const_expressions_mutator((Node *) expr->arg,
- context);
- args = list_make1(arg);
+ args = list_make1(expr->arg);
/*
* CoerceViaIO represents calling the source type's output
@@ -2553,7 +2623,7 @@ eval_const_expressions_mutator(Node *node,
* Note that the coercion functions are assumed not to care
* about input collation, so we just pass InvalidOid for that.
*/
- getTypeOutputInfo(exprType((Node *) arg),
+ getTypeOutputInfo(exprType((Node *) expr->arg),
&outfunc, &outtypisvarlena);
getTypeInputInfo(expr->resulttype,
&infunc, &intypioparam);
@@ -2564,7 +2634,8 @@ eval_const_expressions_mutator(Node *node,
InvalidOid,
InvalidOid,
&args,
- false, true, context);
+ true, context,
+ cachable);
if (simple) /* successfully simplified output fn */
{
/*
@@ -2594,7 +2665,8 @@ eval_const_expressions_mutator(Node *node,
expr->resultcollid,
InvalidOid,
&args,
- false, true, context);
+ true, context,
+ cachable);
if (simple) /* successfully simplified input fn */
return (Node *) simple;
}
@@ -2605,7 +2677,7 @@ eval_const_expressions_mutator(Node *node,
* possibly-simplified argument.
*/
newexpr = makeNode(CoerceViaIO);
- newexpr->arg = arg;
+ newexpr->arg = (Expr *) linitial(args);
newexpr->resulttype = expr->resulttype;
newexpr->resultcollid = expr->resultcollid;
newexpr->coerceformat = expr->coerceformat;
@@ -2622,8 +2694,9 @@ eval_const_expressions_mutator(Node *node,
* Reduce constants in the ArrayCoerceExpr's argument, then
* build a new ArrayCoerceExpr.
*/
- arg = (Expr *) eval_const_expressions_mutator((Node *) expr->arg,
- context);
+ arg = (Expr *) const_expressions_mutator((Node *) expr->arg,
+ context,
+ cachable);
newexpr = makeNode(ArrayCoerceExpr);
newexpr->arg = arg;
@@ -2646,6 +2719,16 @@ eval_const_expressions_mutator(Node *node,
newexpr->resulttype,
newexpr->resulttypmod,
newexpr->resultcollid);
+ /*
+ * If the argument is cachable, but conversion isn't, insert a
+ * CacheExpr above the argument
+ */
+ if (arg && *cachable && (OidIsValid(newexpr->elemfuncid) &&
+ func_volatile(newexpr->elemfuncid) == PROVOLATILE_VOLATILE))
+ {
+ *cachable = false;
+ newexpr->arg = insert_cache(arg);
+ }
/* Else we must return the partially-simplified node */
return (Node *) newexpr;
@@ -2663,8 +2746,9 @@ eval_const_expressions_mutator(Node *node,
CollateExpr *collate = (CollateExpr *) node;
Node *arg;
- arg = eval_const_expressions_mutator((Node *) collate->arg,
- context);
+ arg = const_expressions_mutator((Node *) collate->arg,
+ context,
+ cachable);
if (arg && IsA(arg, Const))
{
@@ -2733,13 +2817,14 @@ eval_const_expressions_mutator(Node *node,
Node *save_case_val;
Node *newarg;
List *newargs;
+ List *cachable_args = NIL;
bool const_true_cond;
Node *defresult = NULL;
ListCell *arg;
/* Simplify the test expression, if any */
- newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
- context);
+ newarg = caching_const_expressions_mutator((Node *) caseexpr->arg,
+ context);
/* Set up for contained CaseTestExpr nodes */
save_case_val = context->case_val;
@@ -2759,12 +2844,15 @@ eval_const_expressions_mutator(Node *node,
CaseWhen *oldcasewhen = (CaseWhen *) lfirst(arg);
Node *casecond;
Node *caseresult;
+ bool condCachable = true;
+ bool resultCachable = true;
Assert(IsA(oldcasewhen, CaseWhen));
/* Simplify this alternative's test condition */
- casecond = eval_const_expressions_mutator((Node *) oldcasewhen->expr,
- context);
+ casecond = const_expressions_mutator((Node *) oldcasewhen->expr,
+ context,
+ &condCachable);
/*
* If the test condition is constant FALSE (or NULL), then
@@ -2783,8 +2871,9 @@ eval_const_expressions_mutator(Node *node,
}
/* Simplify this alternative's result value */
- caseresult = eval_const_expressions_mutator((Node *) oldcasewhen->result,
- context);
+ caseresult = const_expressions_mutator((Node *) oldcasewhen->result,
+ context,
+ &resultCachable);
/* If non-constant test condition, emit a new WHEN node */
if (!const_true_cond)
@@ -2795,6 +2884,23 @@ eval_const_expressions_mutator(Node *node,
newcasewhen->result = (Expr *) caseresult;
newcasewhen->location = oldcasewhen->location;
newargs = lappend(newargs, newcasewhen);
+
+ if (condCachable)
+ {
+ if (is_cache_useful((Expr *) casecond))
+ cachable_args = lappend(cachable_args, &newcasewhen->expr);
+ }
+ else
+ *cachable = false;
+
+ if (resultCachable)
+ {
+ if (is_cache_useful((Expr *) caseresult))
+ cachable_args = lappend(cachable_args, &newcasewhen->result);
+ }
+ else
+ *cachable = false;
+
continue;
}
@@ -2809,8 +2915,21 @@ eval_const_expressions_mutator(Node *node,
/* Simplify the default result, unless we replaced it above */
if (!const_true_cond)
- defresult = eval_const_expressions_mutator((Node *) caseexpr->defresult,
- context);
+ {
+ bool isCachable = true;
+
+ defresult = const_expressions_mutator((Node *) caseexpr->defresult,
+ context,
+ &isCachable);
+
+ if (isCachable)
+ {
+ if (is_cache_useful((Expr *) defresult))
+ cachable_args = lappend(cachable_args, &defresult);
+ }
+ else
+ *cachable = false;
+ }
context->case_val = save_case_val;
@@ -2820,6 +2939,19 @@ eval_const_expressions_mutator(Node *node,
*/
if (newargs == NIL)
return defresult;
+
+ if (!(*cachable))
+ {
+ ListCell *lc;
+
+ foreach(lc, cachable_args)
+ {
+ Expr **arg = (Expr **) lfirst(lc);
+
+ *arg = (Expr *) makeCacheExpr(*arg);
+ }
+ }
+
/* Otherwise we need a new CASE node */
newcase = makeNode(CaseExpr);
newcase->casetype = caseexpr->casetype;
@@ -2840,7 +2972,10 @@ eval_const_expressions_mutator(Node *node,
if (context->case_val)
return copyObject(context->case_val);
else
+ {
+ *cachable = false;
return copyObject(node);
+ }
}
case T_ArrayExpr:
{
@@ -2850,13 +2985,15 @@ eval_const_expressions_mutator(Node *node,
List *newelems;
ListCell *element;
+ *cachable = false; /* Not implemented */
+
newelems = NIL;
foreach(element, arrayexpr->elements)
{
Node *e;
- e = eval_const_expressions_mutator((Node *) lfirst(element),
- context);
+ e = caching_const_expressions_mutator((Node *) lfirst(element),
+ context);
if (!IsA(e, Const))
all_const = false;
newelems = lappend(newelems, e);
@@ -2883,15 +3020,18 @@ eval_const_expressions_mutator(Node *node,
CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
CoalesceExpr *newcoalesce;
List *newargs;
- ListCell *arg;
+ List *cachable_args = NIL;
+ ListCell *lc;
newargs = NIL;
- foreach(arg, coalesceexpr->args)
+ foreach(lc, coalesceexpr->args)
{
- Node *e;
+ Node *arg = lfirst(lc);
+ bool isCachable = true;
- e = eval_const_expressions_mutator((Node *) lfirst(arg),
- context);
+ arg = const_expressions_mutator((Node *) arg,
+ context,
+ &isCachable);
/*
* We can remove null constants from the list. For a
@@ -2901,16 +3041,25 @@ eval_const_expressions_mutator(Node *node,
* drop following arguments since they will never be
* reached.
*/
- if (IsA(e, Const))
+ if (IsA(arg, Const))
{
- if (((Const *) e)->constisnull)
+ if (((Const *) arg)->constisnull)
continue; /* drop null constant */
if (newargs == NIL)
- return e; /* first expr */
- newargs = lappend(newargs, e);
+ return arg; /* first expr */
+ newargs = lappend(newargs, arg);
break;
}
- newargs = lappend(newargs, e);
+
+ newargs = lappend(newargs, arg);
+
+ if (isCachable)
+ {
+ if (is_cache_useful((Expr *) arg))
+ cachable_args = lappend(cachable_args, &llast(newargs));
+ }
+ else
+ *cachable = false;
}
/*
@@ -2921,6 +3070,15 @@ eval_const_expressions_mutator(Node *node,
return (Node *) makeNullConst(coalesceexpr->coalescetype,
-1,
coalesceexpr->coalescecollid);
+ if (!(*cachable))
+ {
+ foreach(lc, cachable_args)
+ {
+ Expr **arg = (Expr **) lfirst(lc);
+
+ *arg = (Expr *) makeCacheExpr(*arg);
+ }
+ }
newcoalesce = makeNode(CoalesceExpr);
newcoalesce->coalescetype = coalesceexpr->coalescetype;
@@ -2942,13 +3100,18 @@ eval_const_expressions_mutator(Node *node,
* is still the same as when the FieldSelect was created ---
* this can change if someone did ALTER COLUMN TYPE on the
* rowtype.
+ *
+ * This is never cachable because Var references aren't
+ * constants. simplify_function() also refuses caching of
+ * row-returning functions
*/
FieldSelect *fselect = (FieldSelect *) node;
FieldSelect *newfselect;
Node *arg;
- arg = eval_const_expressions_mutator((Node *) fselect->arg,
- context);
+ arg = const_expressions_mutator((Node *) fselect->arg,
+ context,
+ cachable);
if (arg && IsA(arg, Var) &&
((Var *) arg)->varattno == InvalidAttrNumber)
{
@@ -2999,8 +3162,9 @@ eval_const_expressions_mutator(Node *node,
NullTest *newntest;
Node *arg;
- arg = eval_const_expressions_mutator((Node *) ntest->arg,
- context);
+ arg = const_expressions_mutator((Node *) ntest->arg,
+ context,
+ cachable);
if (arg && IsA(arg, RowExpr))
{
/*
@@ -3083,8 +3247,9 @@ eval_const_expressions_mutator(Node *node,
BooleanTest *newbtest;
Node *arg;
- arg = eval_const_expressions_mutator((Node *) btest->arg,
- context);
+ arg = const_expressions_mutator((Node *) btest->arg,
+ context,
+ cachable);
if (arg && IsA(arg, Const))
{
Const *carg = (Const *) arg;
@@ -3130,7 +3295,6 @@ eval_const_expressions_mutator(Node *node,
return (Node *) newbtest;
}
case T_PlaceHolderVar:
-
/*
* In estimation mode, just strip the PlaceHolderVar node
* altogether; this amounts to estimating that the contained value
@@ -3138,15 +3302,27 @@ eval_const_expressions_mutator(Node *node,
* just use the default behavior (ie, simplify the expression but
* leave the PlaceHolderVar node intact).
*/
+ *cachable = false;
+
if (context->estimate)
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
+ bool isCachable = true; /* ignored */
- return eval_const_expressions_mutator((Node *) phv->phexpr,
- context);
+ return const_expressions_mutator((Node *) phv->phexpr,
+ context,
+ &isCachable);
}
break;
+ case T_Const:
+ /* Keep *cachable=true */
+ break;
+ case T_CacheExpr:
+ /* We already have CacheExpr in the appropriate place */
+ /* FALL THRU */
default:
+ /* Everything else is not cachable */
+ *cachable = false;
break;
}
@@ -3154,10 +3330,11 @@ eval_const_expressions_mutator(Node *node,
* For any node type not handled above, we recurse using
* expression_tree_mutator, which will copy the node unchanged but try to
* simplify its arguments (if any) using this routine. For example: we
- * cannot eliminate an ArrayRef node, but we might be able to simplify
- * constant expressions in its subscripts.
+ * cannot eliminate an ArrayRef node, but we might be able to simplify or
+ * cache constant expressions in its subscripts.
*/
- return expression_tree_mutator(node, eval_const_expressions_mutator,
+ return expression_tree_mutator(node,
+ caching_const_expressions_mutator,
(void *) context);
}
@@ -3179,13 +3356,23 @@ eval_const_expressions_mutator(Node *node,
* The output arguments *haveNull and *forceTrue must be initialized FALSE
* by the caller. They will be set TRUE if a null constant or true constant,
* respectively, is detected anywhere in the argument list.
+ *
+ * We divide elements into two separate lists, one for cachable items and one
+ * for non-cachable items. Upon returning, the cachable sub-list is turned
+ * into a new BoolExpr, cached and prepended. This is done in hopes that the
+ * cachable sub-list is faster to evaluate and short-cicruits the rest of the
+ * expression.
+ *
+ * Input: (cachable OR uncachable OR cachable OR uncachable)
+ * Output: (CACHE(cachable OR cachable) OR uncachable OR uncachable)
*/
static List *
simplify_or_arguments(List *args,
eval_const_expressions_context *context,
- bool *haveNull, bool *forceTrue)
+ bool *haveNull, bool *forceTrue, bool *cachable)
{
- List *newargs = NIL;
+ List *nocache_args = NIL;
+ List *cachable_args = NIL;
List *unprocessed_args;
/*
@@ -3200,6 +3387,7 @@ simplify_or_arguments(List *args,
while (unprocessed_args)
{
Node *arg = (Node *) linitial(unprocessed_args);
+ bool isCachable = true;
unprocessed_args = list_delete_first(unprocessed_args);
@@ -3222,7 +3410,7 @@ simplify_or_arguments(List *args,
}
/* If it's not an OR, simplify it */
- arg = eval_const_expressions_mutator(arg, context);
+ arg = const_expressions_mutator(arg, context, &isCachable);
/*
* It is unlikely but not impossible for simplification of a non-OR
@@ -3264,10 +3452,40 @@ simplify_or_arguments(List *args,
}
/* else emit the simplified arg into the result list */
- newargs = lappend(newargs, arg);
+ if (isCachable)
+ cachable_args = lappend(cachable_args, arg);
+ else
+ nocache_args = lappend(nocache_args, arg);
}
- return newargs;
+ if (cachable_args && nocache_args)
+ {
+ Expr *arg;
+
+ /* Build a new expression for cachable sub-list */
+ if (list_length(cachable_args) == 1)
+ arg = linitial(cachable_args);
+ else
+ arg = makeBoolExpr(OR_EXPR, cachable_args, -1);
+
+ arg = insert_cache(arg);
+
+ /*
+ * Assume that the cachable expression is cheaper to evaluate, so put
+ * it first
+ */
+ nocache_args = lcons(arg, nocache_args);
+
+ *cachable = false;
+ return nocache_args;
+ }
+ else if (nocache_args)
+ {
+ *cachable = false;
+ return nocache_args;
+ }
+ else
+ return cachable_args;
}
/*
@@ -3288,13 +3506,23 @@ simplify_or_arguments(List *args,
* The output arguments *haveNull and *forceFalse must be initialized FALSE
* by the caller. They will be set TRUE if a null constant or false constant,
* respectively, is detected anywhere in the argument list.
+ *
+ * We divide elements into two separate lists, one for cachable items and one
+ * for non-cachable items. Upon returning, the cachable sub-list is turned
+ * into a new BoolExpr, cached and prepended. This is done in hopes that the
+ * cachable sub-list is faster to evaluate and short-cicruits the rest of the
+ * expression.
+ *
+ * Input: (cachable OR uncachable OR cachable OR uncachable)
+ * Output: (CACHE(cachable OR cachable) OR uncachable OR uncachable)
*/
static List *
simplify_and_arguments(List *args,
eval_const_expressions_context *context,
- bool *haveNull, bool *forceFalse)
+ bool *haveNull, bool *forceFalse, bool *cachable)
{
- List *newargs = NIL;
+ List *nocache_args = NIL;
+ List *cachable_args = NIL;
List *unprocessed_args;
/* See comments in simplify_or_arguments */
@@ -3302,6 +3530,7 @@ simplify_and_arguments(List *args,
while (unprocessed_args)
{
Node *arg = (Node *) linitial(unprocessed_args);
+ bool isCachable = true;
unprocessed_args = list_delete_first(unprocessed_args);
@@ -3324,7 +3553,7 @@ simplify_and_arguments(List *args,
}
/* If it's not an AND, simplify it */
- arg = eval_const_expressions_mutator(arg, context);
+ arg = const_expressions_mutator(arg, context, &isCachable);
/*
* It is unlikely but not impossible for simplification of a non-AND
@@ -3366,10 +3595,40 @@ simplify_and_arguments(List *args,
}
/* else emit the simplified arg into the result list */
- newargs = lappend(newargs, arg);
+ if (isCachable)
+ cachable_args = lappend(cachable_args, arg);
+ else
+ nocache_args = lappend(nocache_args, arg);
}
- return newargs;
+ if (cachable_args && nocache_args)
+ {
+ Expr *arg;
+
+ /* Build a new expression for cachable sub-list */
+ if (list_length(cachable_args) == 1)
+ arg = linitial(cachable_args);
+ else
+ arg = makeBoolExpr(AND_EXPR, cachable_args, -1);
+
+ arg = insert_cache(arg);
+
+ /*
+ * Assume that the cachable expression is cheaper to evaluate, so put
+ * it first
+ */
+ nocache_args = lcons(arg, nocache_args);
+
+ *cachable = false;
+ return nocache_args;
+ }
+ else if (nocache_args)
+ {
+ *cachable = false;
+ return nocache_args;
+ }
+ else
+ return cachable_args;
}
/*
@@ -3444,7 +3703,7 @@ simplify_boolean_equality(Oid opno, List *args)
* Inputs are the original expression (can be NULL), function OID, actual
* result type OID (which is needed for polymorphic functions), result typmod,
* result collation, the input collation to use for the function, the
- * pre-simplified argument list, and some flags; also the context data for
+ * un-simplified argument list, and some flags; also the context data for
* eval_const_expressions. In common cases, several of the arguments could be
* derived from the original expression. Sending them separately avoids
* duplicating NodeTag-specific knowledge, and it's necessary for CoerceViaIO.
@@ -3463,14 +3722,17 @@ simplify_boolean_equality(Oid opno, List *args)
static Expr *
simplify_function(Expr *oldexpr, Oid funcid,
Oid result_type, int32 result_typmod, Oid result_collid,
- Oid input_collid, List **args,
- bool has_named_args,
+ Oid input_collid, List **old_args,
bool allow_inline,
- eval_const_expressions_context *context)
+ eval_const_expressions_context *context,
+ bool *cachable)
{
HeapTuple func_tuple;
Expr *newexpr;
Oid transform;
+ ListCell *lc;
+ List *args = NIL;
+ List *cachable_args = NIL;
/*
* We have three strategies for simplification: execute the function to
@@ -3484,19 +3746,53 @@ simplify_function(Expr *oldexpr, Oid funcid,
if (!HeapTupleIsValid(func_tuple))
elog(ERROR, "cache lookup failed for function %u", funcid);
+ if (oldexpr && IsA(oldexpr, FuncExpr))
+
+ /*
+ * Reorder named arguments and add defaults if needed. Returns a
+ * copied list, so we can mutate it later.
+ */
+ args = simplify_copy_function_arguments(*old_args, result_type, func_tuple);
+ else
+ /* Copy argument list before we start mutating it */
+ args = list_copy(*old_args);
+
+ /* Reduce constants in the FuncExpr's arguments */
+ foreach(lc, args)
+ {
+ Node *arg = (Node *) lfirst(lc);
+ bool isCachable = true;
+
+ arg = const_expressions_mutator(arg, context, &isCachable);
+ lfirst(lc) = arg;
+
+ /*
+ * We're stuck in a catch-22 here. If all arguments and the call
+ * itself is cachable, we don't want to insert cache nodes for
+ * arguments. But we don't know that until we walk through all the
+ * arguments.
+ *
+ * So we accumulate cachable arguments in a list of ListCell pointers,
+ * which we will update later if necessary.
+ *
+ * Note: The args list may not be mutated from here on this until we
+ * handle cachable_args below.
+ */
+ if (isCachable)
+ {
+ if (is_cache_useful((Expr *) arg))
+ cachable_args = lappend(cachable_args, &lfirst(lc));
+ }
+ else
+ *cachable = false; /* One bad arg spoils the whole cache */
+ }
+
/*
- * While we have the tuple, reorder named arguments and add default
- * arguments if needed.
+ * evaluate_function tells us about the cachability of the function call
*/
- if (has_named_args)
- *args = reorder_function_arguments(*args, result_type, func_tuple,
- context);
- else if (((Form_pg_proc) GETSTRUCT(func_tuple))->pronargs > list_length(*args))
- *args = add_function_defaults(*args, result_type, func_tuple, context);
-
newexpr = evaluate_function(funcid, result_type, result_typmod,
- result_collid, input_collid, *args,
- func_tuple, context);
+ result_collid, input_collid, args,
+ func_tuple, context, cachable);
/*
* Some functions calls can be simplified at plan time based on properties
@@ -3533,30 +3829,111 @@ simplify_function(Expr *oldexpr, Oid funcid,
PointerGetDatum(oldexpr)));
if (!newexpr && allow_inline)
+ {
+ /*
+ * The inlined expression may be cachable regardless of the above, if
+ * the function's volatility was mis-labeled or if volatile parts are
+ * removed (possible due to constant folding of conditionals).
+ *
+ * inline_function() also takes care of caching all cachable subtrees
+ */
+ bool isCachable = true;
+
newexpr = inline_function(funcid, result_type, result_collid,
- input_collid, *args,
- func_tuple, context);
+ input_collid, args,
+ func_tuple, context, &isCachable);
+
+ if (newexpr)
+ *cachable = isCachable;
+ }
ReleaseSysCache(func_tuple);
+ /*
+ * If function call can't be cached/inlined, update all cachable arguments
+ */
+ if (!newexpr && !(*cachable))
+ {
+ foreach(lc, cachable_args)
+ {
+ Node **arg = (Node **) lfirst(lc);
+
+ *arg = (Node *) makeCacheExpr((Expr *) *arg);
+ }
+ }
+
+ /* Argument processing done, give it back to the caller */
+ *old_args = args;
+
return newexpr;
}
/*
+ * This function prepares a function's argument list -- converting
+ * named-notation argument list into positional notation while adding any
+ * needed default argument expressions.
+ *
+ * Always returns a copy of the argument list, the original list is not
+ * modified.
+ */
+static List *
+simplify_copy_function_arguments(List *old_args, Oid result_type,
+ HeapTuple func_tuple)
+{
+ List *args = NIL;
+ ListCell *lc;
+ bool has_named_args = false;
+ int nargs_before;
+
+ /* Do we need to reorder named arguments? */
+ foreach(lc, old_args)
+ {
+ Node *arg = (Node *) lfirst(lc);
+
+ if (IsA(arg, NamedArgExpr))
+ {
+ has_named_args = true;
+ break;
+ }
+ }
+
+ nargs_before = list_length(old_args);
+
+ /*
+ * Reorder named arguments and add default arguments if needed.
+ */
+ if (has_named_args)
+ args = reorder_function_arguments(old_args, result_type, func_tuple);
+
+ else
+ {
+ args = list_copy(old_args);
+
+ /* Append missing default arguments to the list */
+ if (((Form_pg_proc) GETSTRUCT(func_tuple))->pronargs > list_length(args))
+ args = add_function_defaults(args, result_type, func_tuple);
+ }
+
+ if (list_length(args) != nargs_before)
+ /* Added defaults may need casts */
+ recheck_cast_function_args(args, result_type, func_tuple);
+
+ return args;
+}
+
+/*
* reorder_function_arguments: convert named-notation args to positional args
*
* This function also inserts default argument values as needed, since it's
* impossible to form a truly valid positional call without that.
*/
static List *
-reorder_function_arguments(List *args, Oid result_type, HeapTuple func_tuple,
- eval_const_expressions_context *context)
+reorder_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
int pronargs = funcform->pronargs;
int nargsprovided = list_length(args);
Node *argarray[FUNC_MAX_ARGS];
- Bitmapset *defargnumbers;
ListCell *lc;
int i;
@@ -3590,7 +3967,6 @@ reorder_function_arguments(List *args, Oid result_type, HeapTuple func_tuple,
* Fetch default expressions, if needed, and insert into array at proper
* locations (they aren't necessarily consecutive or all used)
*/
- defargnumbers = NULL;
if (nargsprovided < pronargs)
{
List *defaults = fetch_function_defaults(func_tuple);
@@ -3599,10 +3975,7 @@ reorder_function_arguments(List *args, Oid result_type, HeapTuple func_tuple,
foreach(lc, defaults)
{
if (argarray[i] == NULL)
- {
argarray[i] = (Node *) lfirst(lc);
- defargnumbers = bms_add_member(defargnumbers, i);
- }
i++;
}
}
@@ -3615,32 +3988,6 @@ reorder_function_arguments(List *args, Oid result_type, HeapTuple func_tuple,
args = lappend(args, argarray[i]);
}
- /* Recheck argument types and add casts if needed */
- recheck_cast_function_args(args, result_type, func_tuple);
-
- /*
- * Lastly, we have to recursively simplify the defaults we just added (but
- * don't recurse on the args passed in, as we already did those). This
- * isn't merely an optimization, it's *necessary* since there could be
- * functions with named or defaulted arguments down in there.
- *
- * Note that we do this last in hopes of simplifying any typecasts that
- * were added by recheck_cast_function_args --- there shouldn't be any new
- * casts added to the explicit arguments, but casts on the defaults are
- * possible.
- */
- if (defargnumbers != NULL)
- {
- i = 0;
- foreach(lc, args)
- {
- if (bms_is_member(i, defargnumbers))
- lfirst(lc) = eval_const_expressions_mutator((Node *) lfirst(lc),
- context);
- i++;
- }
- }
-
return args;
}
@@ -3651,20 +3998,17 @@ reorder_function_arguments(List *args, Oid result_type, HeapTuple func_tuple,
* and so we know we just need to add defaults at the end.
*/
static List *
-add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple,
- eval_const_expressions_context *context)
+add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
- int nargsprovided = list_length(args);
List *defaults;
int ndelete;
- ListCell *lc;
/* Get all the default expressions from the pg_proc tuple */
defaults = fetch_function_defaults(func_tuple);
/* Delete any unused defaults from the list */
- ndelete = nargsprovided + list_length(defaults) - funcform->pronargs;
+ ndelete = list_length(args) + list_length(defaults) - funcform->pronargs;
if (ndelete < 0)
elog(ERROR, "not enough default arguments");
while (ndelete-- > 0)
@@ -3673,28 +4017,6 @@ add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple,
/* And form the combined argument list */
args = list_concat(args, defaults);
- /* Recheck argument types and add casts if needed */
- recheck_cast_function_args(args, result_type, func_tuple);
-
- /*
- * Lastly, we have to recursively simplify the defaults we just added (but
- * don't recurse on the args passed in, as we already did those). This
- * isn't merely an optimization, it's *necessary* since there could be
- * functions with named or defaulted arguments down in there.
- *
- * Note that we do this last in hopes of simplifying any typecasts that
- * were added by recheck_cast_function_args --- there shouldn't be any new
- * casts added to the explicit arguments, but casts on the defaults are
- * possible.
- */
- foreach(lc, args)
- {
- if (nargsprovided-- > 0)
- continue; /* skip original arg positions */
- lfirst(lc) = eval_const_expressions_mutator((Node *) lfirst(lc),
- context);
- }
-
return args;
}
@@ -3784,7 +4106,8 @@ static Expr *
evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
HeapTuple func_tuple,
- eval_const_expressions_context *context)
+ eval_const_expressions_context *context,
+ bool *cachable)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
bool has_nonconst_input = false;
@@ -3796,7 +4119,10 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
* Can't simplify if it returns a set.
*/
if (funcform->proretset)
+ {
+ *cachable = false;
return NULL;
+ }
/*
* Can't simplify if it returns RECORD. The immediate problem is that it
@@ -3810,7 +4136,13 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
* gotchas, seems best to leave the function call unreduced.
*/
if (funcform->prorettype == RECORDOID)
+ {
+ *cachable = false;
return NULL;
+ }
+
+ if (funcform->provolatile == PROVOLATILE_VOLATILE)
+ *cachable = false;
/*
* Check for constant inputs and especially constant-NULL inputs.
@@ -3907,7 +4239,8 @@ static Expr *
inline_function(Oid funcid, Oid result_type, Oid result_collid,
Oid input_collid, List *args,
HeapTuple func_tuple,
- eval_const_expressions_context *context)
+ eval_const_expressions_context *context,
+ bool *cachable)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
char *src;
@@ -4186,7 +4519,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
* the current function to the context list of active functions.
*/
context->active_fns = lcons_oid(funcid, context->active_fns);
- newexpr = eval_const_expressions_mutator(newexpr, context);
+ newexpr = const_expressions_mutator(newexpr, context, cachable);
context->active_fns = list_delete_first(context->active_fns);
error_context_stack = sqlerrcontext.previous;
@@ -4266,6 +4599,34 @@ sql_inline_error_callback(void *arg)
}
/*
+ * Is it useful to cache this expression? Constants and param references are
+ * always fast to access so don't insert cache in front of those.
+ *
+ * Without inline, we lose almost 10% time in some very simple queries (!)
+ */
+static inline bool
+is_cache_useful(Expr *expr)
+{
+ if (IsA(expr, Const))
+ return false;
+ if (IsA(expr, Param))
+ return false;
+ return true;
+}
+
+static Expr *
+insert_cache(Expr *expr)
+{
+ /* Don't cache obviously cheap expressions */
+ if (!is_cache_useful(expr))
+ return expr;
+
+ Assert(!IsA(expr, CacheExpr));
+
+ return (Expr *) makeCacheExpr(expr);
+}
+
+/*
* evaluate_expr: pre-evaluate a constant expression
*
* We use the executor's routine ExecEvalExpr() to avoid duplication of
@@ -4298,7 +4659,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
- exprstate = ExecInitExpr(expr, NULL);
+ exprstate = ExecInitExpr(expr, NULL, false);
/*
* And evaluate it.
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index c3161c5..f98854d 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -1486,7 +1486,7 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it)
fix_opfuncids((Node *) test_expr);
/* Prepare it for execution */
- test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_exprstate = ExecInitExpr(test_expr, NULL, false);
/* And execute it. */
test_result = ExecEvalExprSwitchContext(test_exprstate,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9ad54c5..d7b6ea2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -4612,8 +4612,12 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
/* function-like: name(..) or name[..] */
return true;
- /* CASE keywords act as parentheses */
+ case T_CacheExpr:
+ /* hidden from user */
+ return true;
+
case T_CaseExpr:
+ /* CASE keywords act as parentheses */
return true;
case T_FieldSelect:
@@ -5734,6 +5738,20 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+
+#ifdef DEBUG_CACHEEXPR
+ appendStringInfo(buf, "CACHE[");
+ get_rule_expr((Node *) cache->arg, context, true);
+ appendStringInfoChar(buf, ']');
+#else
+ get_rule_expr((Node *) cache->arg, context, true);
+#endif
+ }
+ break;
+
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 9a74541..56a4bce 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -227,7 +227,7 @@ extern Tuplestorestate *ExecMakeTableFunctionResult(ExprState *funcexpr,
bool randomAccess);
extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
-extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExpr(Expr *node, PlanState *parent, bool cacheEnabled);
extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull);
extern int ExecTargetListLength(List *targetlist);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index da4b695..cc577e6 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -674,6 +674,23 @@ typedef struct FuncExprState
} FuncExprState;
/* ----------------
+ * CacheExprState node
+ *
+ * Takes care of caching execution-time constants that cannot be constant
+ * folded at plan-time.
+ * ----------------
+ */
+typedef struct CacheExprState
+{
+ ExprState xprstate;
+ ExprState *arg; /* state of sub-expression */
+
+ bool enabled; /* is cache enabled? */
+ Datum result; /* cached result */
+ bool isNull; /* is result NULL? */
+} CacheExprState;
+
+/* ----------------
* ScalarArrayOpExprState node
*
* This is a FuncExprState plus some additional data.
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index a6dffe6..fa5d8e7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -74,6 +74,7 @@ extern TypeName *makeTypeNameFromOid(Oid typeOid, int32 typmod);
extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
Oid funccollid, Oid inputcollid, CoercionForm fformat);
+extern CacheExpr *makeCacheExpr(Expr *arg);
extern DefElem *makeDefElem(char *name, Node *arg);
extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b116808..7124ac0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -172,6 +172,7 @@ typedef enum NodeTag
T_JoinExpr,
T_FromExpr,
T_IntoClause,
+ T_CacheExpr,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -205,6 +206,7 @@ typedef enum NodeTag
T_NullTestState,
T_CoerceToDomainState,
T_DomainConstraintState,
+ T_CacheExprState,
/*
* TAGS FOR PLANNER NODES (relation.h)
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 261e7a0..56e1db5 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1029,6 +1029,21 @@ typedef struct BooleanTest
} BooleanTest;
/*
+ * CacheExpr
+ *
+ * CacheExpr is a constant expression to be cached at execution time. The
+ * eval_const_expressions() function inserts CacheExpr nodes nodes at
+ * strategic locations when it recognizes constant expressions that cannot be
+ * constant-folded at plan time, such as expressions with Param references,
+ * stable function and operator calls with constant arguments, etc
+ */
+typedef struct CacheExpr
+{
+ Expr xpr;
+ Expr *arg;
+} CacheExpr;
+
+/*
* CoerceToDomain
*
* CoerceToDomain represents the operation of coercing a value to a domain
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 5ce8d6e..4535074 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4935,7 +4935,9 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
if (expr->expr_simple_lxid != curlxid)
{
oldcontext = MemoryContextSwitchTo(simple_eval_estate->es_query_cxt);
- expr->expr_simple_state = ExecInitExpr(expr->expr_simple_expr, NULL);
+ expr->expr_simple_state = ExecInitExpr(expr->expr_simple_expr,
+ NULL,
+ false);
expr->expr_simple_in_use = false;
expr->expr_simple_lxid = curlxid;
MemoryContextSwitchTo(oldcontext);
@@ -5446,6 +5448,11 @@ exec_simple_check_node(Node *node)
return TRUE;
case T_Param:
+ /*
+ * If we have other kinds of params here, then earlier tests
+ * should have ruled out this as simple expression
+ */
+ Assert(((Param *) node)->paramkind == PARAM_EXTERN);
return TRUE;
case T_ArrayRef:
@@ -5678,6 +5685,10 @@ exec_simple_check_node(Node *node)
return TRUE;
}
+ case T_CacheExpr:
+ /* Caching is disabled for simple expressions */
+ return TRUE;
+
default:
return FALSE;
}
diff --git a/src/test/regress/expected/cache.out b/src/test/regress/expected/cache.out
new file mode 100644
index 0000000..592263d
--- /dev/null
+++ b/src/test/regress/expected/cache.out
@@ -0,0 +1,685 @@
+--
+-- Test cachable expressions
+--
+-- If the NOTICE outputs of these functions change, you've probably broken
+-- something with the CacheExpr optimization
+--
+create function stable_true() returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE TRUE'; return true; end$$;
+create function volatile_true() returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE TRUE'; return true; end$$;
+create function stable_false() returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE FALSE'; return false; end$$;
+create function volatile_false() returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE FALSE'; return false; end$$;
+-- Table with two rows
+create table two (i int);
+insert into two values (1), (2);
+-- Boolean expressions
+select stable_false() or volatile_true() or stable_true() as b from two;
+NOTICE: STABLE FALSE
+NOTICE: STABLE TRUE
+ b
+---
+ t
+ t
+(2 rows)
+
+select stable_true() or volatile_false() or stable_false() as b from two;
+NOTICE: STABLE TRUE
+ b
+---
+ t
+ t
+(2 rows)
+
+select stable_false() or volatile_true() as b from two;
+NOTICE: STABLE FALSE
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE TRUE
+ b
+---
+ t
+ t
+(2 rows)
+
+select stable_false() or stable_false() or volatile_true() as b from two;
+NOTICE: STABLE FALSE
+NOTICE: STABLE FALSE
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE TRUE
+ b
+---
+ t
+ t
+(2 rows)
+
+select volatile_true() or volatile_false() or stable_false() as b from two;
+NOTICE: STABLE FALSE
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE TRUE
+ b
+---
+ t
+ t
+(2 rows)
+
+select volatile_false() or volatile_true() or stable_false() as b from two;
+NOTICE: STABLE FALSE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE TRUE
+ b
+---
+ t
+ t
+(2 rows)
+
+select stable_true() and volatile_false() and stable_false() as b from two;
+NOTICE: STABLE TRUE
+NOTICE: STABLE FALSE
+ b
+---
+ f
+ f
+(2 rows)
+
+select stable_false() and volatile_true() and stable_true() as b from two;
+NOTICE: STABLE FALSE
+ b
+---
+ f
+ f
+(2 rows)
+
+select stable_true() and volatile_false() as b from two;
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE FALSE
+ b
+---
+ f
+ f
+(2 rows)
+
+select stable_true() and stable_true() and volatile_false() as b from two;
+NOTICE: STABLE TRUE
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE FALSE
+ b
+---
+ f
+ f
+(2 rows)
+
+select volatile_true() and volatile_false() and stable_true() as b from two;
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+ b
+---
+ f
+ f
+(2 rows)
+
+select volatile_false() and volatile_true() and stable_true() as b from two;
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE FALSE
+ b
+---
+ f
+ f
+(2 rows)
+
+select not stable_true() as b from two;
+NOTICE: STABLE TRUE
+ b
+---
+ f
+ f
+(2 rows)
+
+select not volatile_true() as b from two;
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE TRUE
+ b
+---
+ f
+ f
+(2 rows)
+
+-- Bind params
+prepare param_test(bool) as select $1 or stable_false() or volatile_true() as b from two;
+execute param_test(true);
+ b
+---
+ t
+ t
+(2 rows)
+
+execute param_test(false);
+NOTICE: STABLE FALSE
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE TRUE
+ b
+---
+ t
+ t
+(2 rows)
+
+-- Function calls
+create function stable(bool) returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE(%)', $1; return $1; end$$;
+create function volatile(bool) returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE(%)', $1; return $1; end$$;
+select volatile(volatile_true()) from two;
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE(t)
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE(t)
+ volatile
+----------
+ t
+ t
+(2 rows)
+
+select stable(stable_true()) from two;
+NOTICE: STABLE TRUE
+NOTICE: STABLE(t)
+ stable
+--------
+ t
+ t
+(2 rows)
+
+select stable(volatile_true()) from two;
+NOTICE: VOLATILE TRUE
+NOTICE: STABLE(t)
+NOTICE: VOLATILE TRUE
+NOTICE: STABLE(t)
+ stable
+--------
+ t
+ t
+(2 rows)
+
+select volatile(stable_true()) from two;
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE(t)
+NOTICE: VOLATILE(t)
+ volatile
+----------
+ t
+ t
+(2 rows)
+
+create function stable(bool, bool) returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+create function volatile(bool, bool) returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE(%, %)', $1, $2; return $1; end$$;
+select stable(volatile_true(), volatile_false()) from two;
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: STABLE(t, f)
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: STABLE(t, f)
+ stable
+--------
+ t
+ t
+(2 rows)
+
+select stable(stable_true(), volatile_false()) from two;
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: STABLE(t, f)
+NOTICE: VOLATILE FALSE
+NOTICE: STABLE(t, f)
+ stable
+--------
+ t
+ t
+(2 rows)
+
+select stable(stable_true(), stable_false()) from two;
+NOTICE: STABLE TRUE
+NOTICE: STABLE FALSE
+NOTICE: STABLE(t, f)
+ stable
+--------
+ t
+ t
+(2 rows)
+
+select volatile(volatile_true(), volatile_false()) from two;
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE(t, f)
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE(t, f)
+ volatile
+----------
+ t
+ t
+(2 rows)
+
+select volatile(stable_true(), volatile_false()) from two;
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE(t, f)
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE(t, f)
+ volatile
+----------
+ t
+ t
+(2 rows)
+
+select volatile(stable_true(), stable_false()) from two;
+NOTICE: STABLE TRUE
+NOTICE: STABLE FALSE
+NOTICE: VOLATILE(t, f)
+NOTICE: VOLATILE(t, f)
+ volatile
+----------
+ t
+ t
+(2 rows)
+
+-- Default arguments
+create function stable_def(a bool = stable_false(), b bool = volatile_true())
+returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+select stable_def() from two;
+NOTICE: STABLE FALSE
+NOTICE: VOLATILE TRUE
+NOTICE: STABLE(f, t)
+NOTICE: VOLATILE TRUE
+NOTICE: STABLE(f, t)
+ stable_def
+------------
+ f
+ f
+(2 rows)
+
+select stable_def(b := stable_true()) from two;
+NOTICE: STABLE FALSE
+NOTICE: STABLE TRUE
+NOTICE: STABLE(f, t)
+ stable_def
+------------
+ f
+ f
+(2 rows)
+
+select stable_def(volatile_false()) from two;
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE TRUE
+NOTICE: STABLE(f, t)
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE TRUE
+NOTICE: STABLE(f, t)
+ stable_def
+------------
+ f
+ f
+(2 rows)
+
+-- Operators
+create function stable_eq(bool, bool) returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE % == %', $1, $2; return $1 = $2; end$$;
+create function volatile_eq(bool, bool) returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE % =%%= %', $1, $2; return $1 = $2; end$$;
+create operator == (procedure = stable_eq, leftarg=bool, rightarg=bool);
+create operator =%= (procedure = volatile_eq, leftarg=bool, rightarg=bool);
+select volatile_true() == volatile_false() from two;
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: STABLE t == f
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: STABLE t == f
+ ?column?
+----------
+ f
+ f
+(2 rows)
+
+select stable_true() == volatile_false() from two;
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: STABLE t == f
+NOTICE: VOLATILE FALSE
+NOTICE: STABLE t == f
+ ?column?
+----------
+ f
+ f
+(2 rows)
+
+select stable_true() == stable_false() from two;
+NOTICE: STABLE TRUE
+NOTICE: STABLE FALSE
+NOTICE: STABLE t == f
+ ?column?
+----------
+ f
+ f
+(2 rows)
+
+select volatile_true() =%= volatile_false() from two;
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE t =%= f
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE t =%= f
+ ?column?
+----------
+ f
+ f
+(2 rows)
+
+select stable_true() =%= volatile_false() from two;
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE t =%= f
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE t =%= f
+ ?column?
+----------
+ f
+ f
+(2 rows)
+
+select stable_true() =%= stable_false() from two;
+NOTICE: STABLE TRUE
+NOTICE: STABLE FALSE
+NOTICE: VOLATILE t =%= f
+NOTICE: VOLATILE t =%= f
+ ?column?
+----------
+ f
+ f
+(2 rows)
+
+select (volatile_true() or stable_true()) == true as b from two;
+NOTICE: STABLE TRUE
+NOTICE: STABLE t == t
+NOTICE: STABLE t == t
+ b
+---
+ t
+ t
+(2 rows)
+
+-- Coalesce
+create function stable_null() returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE NULL'; return null; end$$;
+create function volatile_null() returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE NULL'; return null; end$$;
+select coalesce(stable_null(), stable_true()) from two;
+NOTICE: STABLE NULL
+NOTICE: STABLE TRUE
+ coalesce
+----------
+ t
+ t
+(2 rows)
+
+select coalesce(stable_true(), volatile_null()) from two;
+NOTICE: STABLE TRUE
+ coalesce
+----------
+ t
+ t
+(2 rows)
+
+select coalesce(volatile_null(), stable_null(), volatile_true()) from two;
+NOTICE: VOLATILE NULL
+NOTICE: STABLE NULL
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE NULL
+NOTICE: VOLATILE TRUE
+ coalesce
+----------
+ t
+ t
+(2 rows)
+
+-- Case/when
+select case when stable_true() then 't' else volatile_false() end as b from two;
+NOTICE: STABLE TRUE
+ b
+---
+ t
+ t
+(2 rows)
+
+select case when volatile_true() then stable_true() else stable_false() end as b from two;
+NOTICE: VOLATILE TRUE
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE TRUE
+ b
+---
+ t
+ t
+(2 rows)
+
+select case when i=1 then stable_true() else stable_false() end as b from two;
+NOTICE: STABLE TRUE
+NOTICE: STABLE FALSE
+ b
+---
+ t
+ f
+(2 rows)
+
+select case when i=1 then volatile_true() else volatile_false() end as b from two;
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE FALSE
+ b
+---
+ t
+ f
+(2 rows)
+
+select case when 't' then 't' else volatile_false() end == true as b from two;
+NOTICE: STABLE t == t
+ b
+---
+ t
+ t
+(2 rows)
+
+-- Coerce via I/O
+select stable_true()::text::bool == true as b from two;
+NOTICE: STABLE TRUE
+NOTICE: STABLE t == t
+ b
+---
+ t
+ t
+(2 rows)
+
+select volatile_true()::text::bool == true as b from two;
+NOTICE: VOLATILE TRUE
+NOTICE: STABLE t == t
+NOTICE: VOLATILE TRUE
+NOTICE: STABLE t == t
+ b
+---
+ t
+ t
+(2 rows)
+
+-- IS DISTINCT FROM
+select (stable_true() is not distinct from volatile_false()) as b from two;
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE FALSE
+ b
+---
+ f
+ f
+(2 rows)
+
+select (stable_true() is distinct from stable_false()) == false as b from two;
+NOTICE: STABLE TRUE
+NOTICE: STABLE FALSE
+NOTICE: STABLE t == f
+ b
+---
+ f
+ f
+(2 rows)
+
+select (volatile_true() is distinct from null) as b from two;
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE TRUE
+ b
+---
+ t
+ t
+(2 rows)
+
+-- IS NULL
+select volatile_true() is null == false as b from two;
+NOTICE: VOLATILE TRUE
+NOTICE: STABLE f == f
+NOTICE: VOLATILE TRUE
+NOTICE: STABLE f == f
+ b
+---
+ t
+ t
+(2 rows)
+
+select stable_null() is not null == true as b from two;
+NOTICE: STABLE NULL
+NOTICE: STABLE f == t
+ b
+---
+ f
+ f
+(2 rows)
+
+-- Boolean tests
+select volatile_false() is true == true as b from two;
+NOTICE: VOLATILE FALSE
+NOTICE: STABLE f == t
+NOTICE: VOLATILE FALSE
+NOTICE: STABLE f == t
+ b
+---
+ f
+ f
+(2 rows)
+
+select stable_null() is not unknown == false as b from two;
+NOTICE: STABLE NULL
+NOTICE: STABLE f == f
+ b
+---
+ t
+ t
+(2 rows)
+
+-- Field select -- not currently cached
+create function stable_row(a out int, b out int) STABLE language plpgsql as
+$$begin raise notice 'STABLE ROW'; a = 1; b = 2; end$$;
+select (stable_row()).a from two;
+NOTICE: STABLE ROW
+NOTICE: STABLE ROW
+ a
+---
+ 1
+ 1
+(2 rows)
+
+-- WHERE clause
+begin;
+-- stable_true is evaluated twice due to planning estimates
+declare stable_where cursor for select * from two where i > stable_true()::int;
+NOTICE: STABLE TRUE
+fetch all from stable_where;
+NOTICE: STABLE TRUE
+ i
+---
+ 2
+(1 row)
+
+declare volatile_where cursor for select * from two where i = volatile_false()::int;
+fetch all from volatile_where;
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE FALSE
+ i
+---
+(0 rows)
+
+rollback;
+-- INSERT column default expressions
+create table defaults (
+ dummy int,
+ a bool default stable_true(),
+ b bool default volatile_true()
+);
+insert into defaults (dummy) values(0), (1);
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE TRUE
+-- ALTER COLUMN TYPE USING
+alter table defaults alter column a type bool using stable_false();
+NOTICE: STABLE FALSE
+alter table defaults alter column a type bool using volatile_false();
+NOTICE: VOLATILE FALSE
+NOTICE: VOLATILE FALSE
+-- COPY FROM with default expressions
+copy defaults (dummy) from stdin;
+NOTICE: STABLE TRUE
+CONTEXT: COPY defaults, line 1: "2"
+NOTICE: VOLATILE TRUE
+CONTEXT: COPY defaults, line 1: "2"
+NOTICE: VOLATILE TRUE
+CONTEXT: COPY defaults, line 2: "3"
+-- VALUES list expressions
+-- The fact that there are be 3 lines of 'VOLATILE TRUE' output is a quirk of
+-- the current set-returning function execution code
+insert into defaults (dummy, a, b)
+values (generate_series(4, 5), stable_true(), volatile_true());
+NOTICE: STABLE TRUE
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE TRUE
+NOTICE: VOLATILE TRUE
+-- PL/pgSQL Simple expressions
+-- Make sure we don't cache simple expressions -- these expressions are only
+-- initialized once per transaction and then executed multiple times
+create function stable_max() returns int STABLE language plpgsql as
+$$begin return (select max(i) from two); end$$;
+create function simple() returns int STABLE language plpgsql as
+$$begin return stable_max(); end$$;
+begin;
+select simple();
+ simple
+--------
+ 2
+(1 row)
+
+insert into two values(3);
+select simple();
+ simple
+--------
+ 3
+(1 row)
+
+rollback;
+-- The end
+drop table defaults;
+drop table two;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 3bedad0..052c3db 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
-test: privileges security_label collate
+test: privileges security_label collate cache
test: misc
# rules cannot run concurrently with any test that creates a view
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 0b64569..5f053d3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -92,6 +92,7 @@ test: prepared_xacts
test: privileges
test: security_label
test: collate
+test: cache
test: misc
test: rules
test: select_views
diff --git a/src/test/regress/sql/cache.sql b/src/test/regress/sql/cache.sql
new file mode 100644
index 0000000..9b68670
--- /dev/null
+++ b/src/test/regress/sql/cache.sql
@@ -0,0 +1,185 @@
+--
+-- Test cachable expressions
+--
+-- If the NOTICE outputs of these functions change, you've probably broken
+-- something with the CacheExpr optimization
+--
+
+create function stable_true() returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE TRUE'; return true; end$$;
+create function volatile_true() returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE TRUE'; return true; end$$;
+create function stable_false() returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE FALSE'; return false; end$$;
+create function volatile_false() returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE FALSE'; return false; end$$;
+
+-- Table with two rows
+create table two (i int);
+insert into two values (1), (2);
+
+-- Boolean expressions
+select stable_false() or volatile_true() or stable_true() as b from two;
+select stable_true() or volatile_false() or stable_false() as b from two;
+select stable_false() or volatile_true() as b from two;
+select stable_false() or stable_false() or volatile_true() as b from two;
+select volatile_true() or volatile_false() or stable_false() as b from two;
+select volatile_false() or volatile_true() or stable_false() as b from two;
+
+select stable_true() and volatile_false() and stable_false() as b from two;
+select stable_false() and volatile_true() and stable_true() as b from two;
+select stable_true() and volatile_false() as b from two;
+select stable_true() and stable_true() and volatile_false() as b from two;
+select volatile_true() and volatile_false() and stable_true() as b from two;
+select volatile_false() and volatile_true() and stable_true() as b from two;
+
+select not stable_true() as b from two;
+select not volatile_true() as b from two;
+
+-- Bind params
+prepare param_test(bool) as select $1 or stable_false() or volatile_true() as b from two;
+execute param_test(true);
+execute param_test(false);
+
+-- Function calls
+create function stable(bool) returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE(%)', $1; return $1; end$$;
+create function volatile(bool) returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE(%)', $1; return $1; end$$;
+
+select volatile(volatile_true()) from two;
+select stable(stable_true()) from two;
+select stable(volatile_true()) from two;
+select volatile(stable_true()) from two;
+
+create function stable(bool, bool) returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+create function volatile(bool, bool) returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE(%, %)', $1, $2; return $1; end$$;
+
+select stable(volatile_true(), volatile_false()) from two;
+select stable(stable_true(), volatile_false()) from two;
+select stable(stable_true(), stable_false()) from two;
+select volatile(volatile_true(), volatile_false()) from two;
+select volatile(stable_true(), volatile_false()) from two;
+select volatile(stable_true(), stable_false()) from two;
+
+-- Default arguments
+create function stable_def(a bool = stable_false(), b bool = volatile_true())
+returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+
+select stable_def() from two;
+select stable_def(b := stable_true()) from two;
+select stable_def(volatile_false()) from two;
+
+-- Operators
+create function stable_eq(bool, bool) returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE % == %', $1, $2; return $1 = $2; end$$;
+create function volatile_eq(bool, bool) returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE % =%%= %', $1, $2; return $1 = $2; end$$;
+
+create operator == (procedure = stable_eq, leftarg=bool, rightarg=bool);
+create operator =%= (procedure = volatile_eq, leftarg=bool, rightarg=bool);
+
+select volatile_true() == volatile_false() from two;
+select stable_true() == volatile_false() from two;
+select stable_true() == stable_false() from two;
+select volatile_true() =%= volatile_false() from two;
+select stable_true() =%= volatile_false() from two;
+select stable_true() =%= stable_false() from two;
+
+select (volatile_true() or stable_true()) == true as b from two;
+
+-- Coalesce
+create function stable_null() returns bool STABLE language plpgsql as
+$$begin raise notice 'STABLE NULL'; return null; end$$;
+create function volatile_null() returns bool VOLATILE language plpgsql as
+$$begin raise notice 'VOLATILE NULL'; return null; end$$;
+
+select coalesce(stable_null(), stable_true()) from two;
+select coalesce(stable_true(), volatile_null()) from two;
+select coalesce(volatile_null(), stable_null(), volatile_true()) from two;
+
+-- Case/when
+select case when stable_true() then 't' else volatile_false() end as b from two;
+select case when volatile_true() then stable_true() else stable_false() end as b from two;
+select case when i=1 then stable_true() else stable_false() end as b from two;
+select case when i=1 then volatile_true() else volatile_false() end as b from two;
+
+select case when 't' then 't' else volatile_false() end == true as b from two;
+
+-- Coerce via I/O
+select stable_true()::text::bool == true as b from two;
+select volatile_true()::text::bool == true as b from two;
+
+-- IS DISTINCT FROM
+select (stable_true() is not distinct from volatile_false()) as b from two;
+select (stable_true() is distinct from stable_false()) == false as b from two;
+select (volatile_true() is distinct from null) as b from two;
+
+-- IS NULL
+select volatile_true() is null == false as b from two;
+select stable_null() is not null == true as b from two;
+
+-- Boolean tests
+select volatile_false() is true == true as b from two;
+select stable_null() is not unknown == false as b from two;
+
+-- Field select -- not currently cached
+create function stable_row(a out int, b out int) STABLE language plpgsql as
+$$begin raise notice 'STABLE ROW'; a = 1; b = 2; end$$;
+
+select (stable_row()).a from two;
+
+-- WHERE clause
+begin;
+-- stable_true is evaluated twice due to planning estimates
+declare stable_where cursor for select * from two where i > stable_true()::int;
+fetch all from stable_where;
+declare volatile_where cursor for select * from two where i = volatile_false()::int;
+fetch all from volatile_where;
+rollback;
+
+-- INSERT column default expressions
+create table defaults (
+ dummy int,
+ a bool default stable_true(),
+ b bool default volatile_true()
+);
+insert into defaults (dummy) values(0), (1);
+
+-- ALTER COLUMN TYPE USING
+alter table defaults alter column a type bool using stable_false();
+alter table defaults alter column a type bool using volatile_false();
+
+-- COPY FROM with default expressions
+copy defaults (dummy) from stdin;
+2
+3
+\.
+
+-- VALUES list expressions
+-- The fact that there are be 3 lines of 'VOLATILE TRUE' output is a quirk of
+-- the current set-returning function execution code
+insert into defaults (dummy, a, b)
+values (generate_series(4, 5), stable_true(), volatile_true());
+
+-- PL/pgSQL Simple expressions
+-- Make sure we don't cache simple expressions -- these expressions are only
+-- initialized once per transaction and then executed multiple times
+create function stable_max() returns int STABLE language plpgsql as
+$$begin return (select max(i) from two); end$$;
+
+create function simple() returns int STABLE language plpgsql as
+$$begin return stable_max(); end$$;
+
+begin;
+select simple();
+insert into two values(3);
+select simple();
+rollback;
+
+-- The end
+drop table defaults;
+drop table two;
On Mon, Jan 16, 2012 at 19:06, Marti Raudsepp <marti@juffo.org> wrote:
Here's v6 of my expression caching patch. The only change in v6 is
added expression cost estimation in costsize.c.
Ok, I found another omission in my patch. Up until v4, the behavior of
estimate_expression_value() was to never insert CacheExpr nodes, and
always strip them when found. In v5, I ripped out all the conditional
CacheExpr insertion and stripping so expression trees would always be
normalized the same way.
This had an unintended consequence; operator selectivity estimation
functions aren't prepared to deal with CacheExpr nodes. E.g. STABLE
function calls and expressions with Params are constant-folded in
estimation mode, but the constant would still remain as a child of a
CacheExpr node.
The solutions I considered:
1. Selectivity functions and other estimation callers could be changed
to strip CacheExpr themself. This doesn't seem very attractive since
there are quite a lot of them and it seems like duplication of code.
2. Strip CacheExpr nodes in the estimation pass. This has a potential
hazard that estimate_expression_value() is no longer idempotent; a
second pass over the same expr tree might re-insert CacheExpr to some
places. However, these cases are unlikely to be problematic for
selectivity estimation anyway -- currently selectivity estimation (and
other callers) can only deal with trees that get folded to a single
Const, and those don't get cached.
3. Strip CacheExprs *and* restrict CacheExpr insertion in estimation
mode (like v4 patch). This would also add a larger amount of code to
the patch, but it would keep estimate_expression_value() idempotent.
----
The attached patch implements solution #2 as it's the least amount of
code and the downside doesn't seem problematic (as explained above). I
also rebased the patch to current Postgres master (no conflicts, just
some fuzz). Note that the create_index test fails in master, this
problem isn't introduced by my patch.
As always, this work is also available from my Github "cache" branch:
https://github.com/intgr/postgres/commits/cache
Regards,
Marti
Attachments:
cacheexpr-v7.patchtext/x-patch; charset=US-ASCII; name=cacheexpr-v7.patchDownload
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
new file mode 100644
index 110480f..a9dbb87
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
*************** BeginCopyFrom(Relation rel,
*** 2296,2302 ****
{
/* Initialize expressions in copycontext. */
defexprs[num_defaults] = ExecInitExpr(
! expression_planner((Expr *) defexpr), NULL);
defmap[num_defaults] = attnum - 1;
num_defaults++;
--- 2296,2304 ----
{
/* Initialize expressions in copycontext. */
defexprs[num_defaults] = ExecInitExpr(
! expression_planner((Expr *) defexpr),
! NULL,
! true);
defmap[num_defaults] = attnum - 1;
num_defaults++;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
new file mode 100644
index 07dc326..5d937fe
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** ATRewriteTable(AlteredTableInfo *tab, Oi
*** 3572,3578 ****
NewColumnValue *ex = lfirst(l);
/* expr already planned */
! ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
}
notnull_attrs = NIL;
--- 3572,3578 ----
NewColumnValue *ex = lfirst(l);
/* expr already planned */
! ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL, true);
}
notnull_attrs = NIL;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
new file mode 100644
index c6bc6c8..95ebf44
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
*************** GetDomainConstraints(Oid typeOid)
*** 3048,3054 ****
r = makeNode(DomainConstraintState);
r->constrainttype = DOM_CONSTRAINT_CHECK;
r->name = pstrdup(NameStr(c->conname));
! r->check_expr = ExecInitExpr(check_expr, NULL);
/*
* use lcons() here because constraints of lower domains should be
--- 3048,3054 ----
r = makeNode(DomainConstraintState);
r->constrainttype = DOM_CONSTRAINT_CHECK;
r->name = pstrdup(NameStr(c->conname));
! r->check_expr = ExecInitExpr(check_expr, NULL, false);
/*
* use lcons() here because constraints of lower domains should be
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
new file mode 100644
index 4a6baeb..3113f83
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 51,62 ****
--- 51,70 ----
#include "pgstat.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+ #include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/typcache.h"
#include "utils/xml.h"
+ typedef struct
+ {
+ PlanState *parent;
+ bool useCache;
+ } ExecInitExprContext;
+
+
/* static function decls */
static Datum ExecEvalArrayRef(ArrayRefExprState *astate,
ExprContext *econtext,
*************** static Datum ExecEvalCoerceToDomain(Coer
*** 157,162 ****
--- 165,176 ----
static Datum ExecEvalCoerceToDomainValue(ExprState *exprstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
+ static Datum ExecEvalCacheExpr(CacheExprState *cstate,
+ ExprContext *econtext,
+ bool *isNull,ExprDoneCond *isDone);
+ static Datum ExecEvalCacheExprResult(CacheExprState *cstate,
+ ExprContext *econtext,
+ bool *isNull,ExprDoneCond *isDone);
static Datum ExecEvalFieldSelect(FieldSelectState *fstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
*************** static Datum ExecEvalArrayCoerceExpr(Arr
*** 174,179 ****
--- 188,195 ----
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
+ static ExprState *ExecInitExprMutator(Expr *node,
+ const ExecInitExprContext *context);
/* ----------------------------------------------------------------
*************** ExecEvalBooleanTest(GenericExprState *bs
*** 3754,3759 ****
--- 3770,3832 ----
}
}
+ /* ----------------------------------------------------------------
+ * ExecEvalCacheExpr
+ *
+ * Evaluates a cachable expression for the first time and updates
+ * xprstate.evalfunc to return cached result next time
+ * ----------------------------------------------------------------
+ */
+ static Datum
+ ExecEvalCacheExpr(CacheExprState *cstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+ {
+ MemoryContext oldcontext;
+ Datum result;
+ bool resultTypByVal;
+ int16 resultTypLen;
+ Oid resultType;
+
+ result = ExecEvalExpr(cstate->arg, econtext, isNull, isDone);
+
+ if (!cstate->enabled)
+ return result; /* Cache disabled, pass thru the result */
+
+ /* Set-returning expressions can't be cached */
+ Assert(isDone == NULL || *isDone == ExprSingleResult);
+
+ /* Figure out type and size for copy */
+ resultType = exprType((Node *) ((CacheExpr *) cstate->xprstate.expr)->arg);
+ get_typlenbyval(resultType, &resultTypLen, &resultTypByVal);
+
+ /* This cached datum has to persist for the whole query */
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ cstate->result = datumCopy(result, resultTypByVal, resultTypLen);
+ cstate->isNull = *isNull;
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Subsequent calls will return the cached result */
+ cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCacheExprResult;
+
+ return cstate->result;
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecEvalCacheExprResult
+ *
+ * Return the already-cached result, computed in ExecEvalCacheExpr
+ * ----------------------------------------------------------------
+ */
+ static Datum
+ ExecEvalCacheExprResult(CacheExprState *cstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+ {
+ if (isDone)
+ *isDone = ExprSingleResult;
+ *isNull = cstate->isNull;
+ return cstate->result;
+ }
+
/*
* ExecEvalCoerceToDomain
*
*************** ExecEvalExprSwitchContext(ExprState *exp
*** 4217,4229 ****
*
* 'node' is the root of the expression tree to examine
* 'parent' is the PlanState node that owns the expression.
*
* 'parent' may be NULL if we are preparing an expression that is not
* associated with a plan tree. (If so, it can't have aggs or subplans.)
* This case should usually come through ExecPrepareExpr, not directly here.
*/
ExprState *
! ExecInitExpr(Expr *node, PlanState *parent)
{
ExprState *state;
--- 4290,4319 ----
*
* 'node' is the root of the expression tree to examine
* 'parent' is the PlanState node that owns the expression.
+ * 'useCache' enables caching for stable/parameterized expressions.
*
* 'parent' may be NULL if we are preparing an expression that is not
* associated with a plan tree. (If so, it can't have aggs or subplans.)
* This case should usually come through ExecPrepareExpr, not directly here.
+ *
+ * 'useCache' may only be true if it's guaranteed that all executions of the
+ * expression use the same snapshot and same external params. It should also
+ * be false if the expression is only executed once.
*/
ExprState *
! ExecInitExpr(Expr *node, PlanState *parent, bool useCache)
! {
! ExecInitExprContext context;
!
! context.parent = parent;
! context.useCache = useCache;
!
! return ExecInitExprMutator(node, &context);
! }
!
! static ExprState *
! ExecInitExprMutator(Expr *node,
! const ExecInitExprContext *context)
{
ExprState *state;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4273,4288 ****
AggrefExprState *astate = makeNode(AggrefExprState);
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAggref;
! if (parent && IsA(parent, AggState))
{
! AggState *aggstate = (AggState *) parent;
int naggs;
aggstate->aggs = lcons(astate, aggstate->aggs);
naggs = ++aggstate->numaggs;
! astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
! parent);
/*
* Complain if the aggregate's arguments contain any
--- 4363,4378 ----
AggrefExprState *astate = makeNode(AggrefExprState);
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAggref;
! if (context->parent && IsA(context->parent, AggState))
{
! AggState *aggstate = (AggState *) context->parent;
int naggs;
aggstate->aggs = lcons(astate, aggstate->aggs);
naggs = ++aggstate->numaggs;
! astate->args = (List *)
! ExecInitExprMutator((Expr *) aggref->args, context);
/*
* Complain if the aggregate's arguments contain any
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4309,4317 ****
WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc;
! if (parent && IsA(parent, WindowAggState))
{
! WindowAggState *winstate = (WindowAggState *) parent;
int nfuncs;
winstate->funcs = lcons(wfstate, winstate->funcs);
--- 4399,4407 ----
WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc;
! if (context->parent && IsA(context->parent, WindowAggState))
{
! WindowAggState *winstate = (WindowAggState *) context->parent;
int nfuncs;
winstate->funcs = lcons(wfstate, winstate->funcs);
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4319,4326 ****
if (wfunc->winagg)
winstate->numaggs++;
! wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
! parent);
/*
* Complain if the windowfunc's arguments contain any
--- 4409,4416 ----
if (wfunc->winagg)
winstate->numaggs++;
! wfstate->args = (List *)
! ExecInitExprMutator((Expr *) wfunc->args, context);
/*
* Complain if the windowfunc's arguments contain any
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4348,4359 ****
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayRef;
astate->refupperindexpr = (List *)
! ExecInitExpr((Expr *) aref->refupperindexpr, parent);
astate->reflowerindexpr = (List *)
! ExecInitExpr((Expr *) aref->reflowerindexpr, parent);
! astate->refexpr = ExecInitExpr(aref->refexpr, parent);
! astate->refassgnexpr = ExecInitExpr(aref->refassgnexpr,
! parent);
/* do one-time catalog lookups for type info */
astate->refattrlength = get_typlen(aref->refarraytype);
get_typlenbyvalalign(aref->refelemtype,
--- 4438,4449 ----
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayRef;
astate->refupperindexpr = (List *)
! ExecInitExprMutator((Expr *) aref->refupperindexpr, context);
astate->reflowerindexpr = (List *)
! ExecInitExprMutator((Expr *) aref->reflowerindexpr, context);
! astate->refexpr = ExecInitExprMutator(aref->refexpr, context);
! astate->refassgnexpr =
! ExecInitExprMutator(aref->refassgnexpr, context);
/* do one-time catalog lookups for type info */
astate->refattrlength = get_typlen(aref->refarraytype);
get_typlenbyvalalign(aref->refelemtype,
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4370,4376 ****
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFunc;
fstate->args = (List *)
! ExecInitExpr((Expr *) funcexpr->args, parent);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
--- 4460,4466 ----
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFunc;
fstate->args = (List *)
! ExecInitExprMutator((Expr *) funcexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4382,4388 ****
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOper;
fstate->args = (List *)
! ExecInitExpr((Expr *) opexpr->args, parent);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
--- 4472,4478 ----
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOper;
fstate->args = (List *)
! ExecInitExprMutator((Expr *) opexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4394,4400 ****
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalDistinct;
fstate->args = (List *)
! ExecInitExpr((Expr *) distinctexpr->args, parent);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
--- 4484,4490 ----
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalDistinct;
fstate->args = (List *)
! ExecInitExprMutator((Expr *) distinctexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4406,4412 ****
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullIf;
fstate->args = (List *)
! ExecInitExpr((Expr *) nullifexpr->args, parent);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
--- 4496,4502 ----
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullIf;
fstate->args = (List *)
! ExecInitExprMutator((Expr *) nullifexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4418,4424 ****
sstate->fxprstate.xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalScalarArrayOp;
sstate->fxprstate.args = (List *)
! ExecInitExpr((Expr *) opexpr->args, parent);
sstate->fxprstate.func.fn_oid = InvalidOid; /* not initialized */
sstate->element_type = InvalidOid; /* ditto */
state = (ExprState *) sstate;
--- 4508,4514 ----
sstate->fxprstate.xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalScalarArrayOp;
sstate->fxprstate.args = (List *)
! ExecInitExprMutator((Expr *) opexpr->args, context);
sstate->fxprstate.func.fn_oid = InvalidOid; /* not initialized */
sstate->element_type = InvalidOid; /* ditto */
state = (ExprState *) sstate;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4446,4452 ****
break;
}
bstate->args = (List *)
! ExecInitExpr((Expr *) boolexpr->args, parent);
state = (ExprState *) bstate;
}
break;
--- 4536,4542 ----
break;
}
bstate->args = (List *)
! ExecInitExprMutator((Expr *) boolexpr->args, context);
state = (ExprState *) bstate;
}
break;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4455,4467 ****
SubPlan *subplan = (SubPlan *) node;
SubPlanState *sstate;
! if (!parent)
elog(ERROR, "SubPlan found with no parent plan");
! sstate = ExecInitSubPlan(subplan, parent);
/* Add SubPlanState nodes to parent->subPlan */
! parent->subPlan = lappend(parent->subPlan, sstate);
state = (ExprState *) sstate;
}
--- 4545,4558 ----
SubPlan *subplan = (SubPlan *) node;
SubPlanState *sstate;
! if (!context->parent)
elog(ERROR, "SubPlan found with no parent plan");
! sstate = ExecInitSubPlan(subplan, context->parent);
/* Add SubPlanState nodes to parent->subPlan */
! context->parent->subPlan = lappend(context->parent->subPlan,
! sstate);
state = (ExprState *) sstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4471,4480 ****
AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
AlternativeSubPlanState *asstate;
! if (!parent)
elog(ERROR, "AlternativeSubPlan found with no parent plan");
! asstate = ExecInitAlternativeSubPlan(asplan, parent);
state = (ExprState *) asstate;
}
--- 4562,4571 ----
AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
AlternativeSubPlanState *asstate;
! if (!context->parent)
elog(ERROR, "AlternativeSubPlan found with no parent plan");
! asstate = ExecInitAlternativeSubPlan(asplan, context->parent);
state = (ExprState *) asstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4485,4491 ****
FieldSelectState *fstate = makeNode(FieldSelectState);
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldSelect;
! fstate->arg = ExecInitExpr(fselect->arg, parent);
fstate->argdesc = NULL;
state = (ExprState *) fstate;
}
--- 4576,4582 ----
FieldSelectState *fstate = makeNode(FieldSelectState);
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldSelect;
! fstate->arg = ExecInitExprMutator(fselect->arg, context);
fstate->argdesc = NULL;
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4496,4503 ****
FieldStoreState *fstate = makeNode(FieldStoreState);
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldStore;
! fstate->arg = ExecInitExpr(fstore->arg, parent);
! fstate->newvals = (List *) ExecInitExpr((Expr *) fstore->newvals, parent);
fstate->argdesc = NULL;
state = (ExprState *) fstate;
}
--- 4587,4595 ----
FieldStoreState *fstate = makeNode(FieldStoreState);
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldStore;
! fstate->arg = ExecInitExprMutator(fstore->arg, context);
! fstate->newvals = (List *)
! ExecInitExprMutator((Expr *) fstore->newvals, context);
fstate->argdesc = NULL;
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4508,4514 ****
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRelabelType;
! gstate->arg = ExecInitExpr(relabel->arg, parent);
state = (ExprState *) gstate;
}
break;
--- 4600,4606 ----
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRelabelType;
! gstate->arg = ExecInitExprMutator(relabel->arg, context);
state = (ExprState *) gstate;
}
break;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4520,4526 ****
bool typisvarlena;
iostate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceViaIO;
! iostate->arg = ExecInitExpr(iocoerce->arg, parent);
/* lookup the result type's input function */
getTypeInputInfo(iocoerce->resulttype, &iofunc,
&iostate->intypioparam);
--- 4612,4618 ----
bool typisvarlena;
iostate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceViaIO;
! iostate->arg = ExecInitExprMutator(iocoerce->arg, context);
/* lookup the result type's input function */
getTypeInputInfo(iocoerce->resulttype, &iofunc,
&iostate->intypioparam);
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4538,4544 ****
ArrayCoerceExprState *astate = makeNode(ArrayCoerceExprState);
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayCoerceExpr;
! astate->arg = ExecInitExpr(acoerce->arg, parent);
astate->resultelemtype = get_element_type(acoerce->resulttype);
if (astate->resultelemtype == InvalidOid)
ereport(ERROR,
--- 4630,4636 ----
ArrayCoerceExprState *astate = makeNode(ArrayCoerceExprState);
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayCoerceExpr;
! astate->arg = ExecInitExprMutator(acoerce->arg, context);
astate->resultelemtype = get_element_type(acoerce->resulttype);
if (astate->resultelemtype == InvalidOid)
ereport(ERROR,
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4558,4564 ****
ConvertRowtypeExprState *cstate = makeNode(ConvertRowtypeExprState);
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype;
! cstate->arg = ExecInitExpr(convert->arg, parent);
state = (ExprState *) cstate;
}
break;
--- 4650,4656 ----
ConvertRowtypeExprState *cstate = makeNode(ConvertRowtypeExprState);
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype;
! cstate->arg = ExecInitExprMutator(convert->arg, context);
state = (ExprState *) cstate;
}
break;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4570,4576 ****
ListCell *l;
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase;
! cstate->arg = ExecInitExpr(caseexpr->arg, parent);
foreach(l, caseexpr->args)
{
CaseWhen *when = (CaseWhen *) lfirst(l);
--- 4662,4668 ----
ListCell *l;
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase;
! cstate->arg = ExecInitExprMutator(caseexpr->arg, context);
foreach(l, caseexpr->args)
{
CaseWhen *when = (CaseWhen *) lfirst(l);
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4579,4590 ****
Assert(IsA(when, CaseWhen));
wstate->xprstate.evalfunc = NULL; /* not used */
wstate->xprstate.expr = (Expr *) when;
! wstate->expr = ExecInitExpr(when->expr, parent);
! wstate->result = ExecInitExpr(when->result, parent);
outlist = lappend(outlist, wstate);
}
cstate->args = outlist;
! cstate->defresult = ExecInitExpr(caseexpr->defresult, parent);
state = (ExprState *) cstate;
}
break;
--- 4671,4683 ----
Assert(IsA(when, CaseWhen));
wstate->xprstate.evalfunc = NULL; /* not used */
wstate->xprstate.expr = (Expr *) when;
! wstate->expr = ExecInitExprMutator(when->expr, context);
! wstate->result = ExecInitExprMutator(when->result, context);
outlist = lappend(outlist, wstate);
}
cstate->args = outlist;
! cstate->defresult =
! ExecInitExprMutator(caseexpr->defresult, context);
state = (ExprState *) cstate;
}
break;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4601,4607 ****
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
astate->elements = outlist;
--- 4694,4700 ----
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
astate->elements = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4669,4675 ****
*/
e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid);
}
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
i++;
}
--- 4762,4768 ----
*/
e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid);
}
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
i++;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4696,4702 ****
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
rstate->largs = outlist;
--- 4789,4795 ----
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
rstate->largs = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4707,4713 ****
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
rstate->rargs = outlist;
--- 4800,4806 ----
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
rstate->rargs = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4760,4766 ****
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
cstate->args = outlist;
--- 4853,4859 ----
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
cstate->args = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4781,4787 ****
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
mstate->args = outlist;
--- 4874,4880 ----
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
mstate->args = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4818,4824 ****
Expr *e = (Expr *) lfirst(arg);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
xstate->named_args = outlist;
--- 4911,4917 ----
Expr *e = (Expr *) lfirst(arg);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
xstate->named_args = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4829,4835 ****
Expr *e = (Expr *) lfirst(arg);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
xstate->args = outlist;
--- 4922,4928 ----
Expr *e = (Expr *) lfirst(arg);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
xstate->args = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4843,4849 ****
NullTestState *nstate = makeNode(NullTestState);
nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest;
! nstate->arg = ExecInitExpr(ntest->arg, parent);
nstate->argdesc = NULL;
state = (ExprState *) nstate;
}
--- 4936,4942 ----
NullTestState *nstate = makeNode(NullTestState);
nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest;
! nstate->arg = ExecInitExprMutator(ntest->arg, context);
nstate->argdesc = NULL;
state = (ExprState *) nstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4854,4870 ****
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalBooleanTest;
! gstate->arg = ExecInitExpr(btest->arg, parent);
state = (ExprState *) gstate;
}
break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
CoerceToDomainState *cstate = makeNode(CoerceToDomainState);
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceToDomain;
! cstate->arg = ExecInitExpr(ctest->arg, parent);
cstate->constraints = GetDomainConstraints(ctest->resulttype);
state = (ExprState *) cstate;
}
--- 4947,4980 ----
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalBooleanTest;
! gstate->arg = ExecInitExprMutator(btest->arg, context);
state = (ExprState *) gstate;
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+ CacheExprState *cstate = makeNode(CacheExprState);
+
+ cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCacheExpr;
+ cstate->arg = ExecInitExprMutator(cache->arg, context);
+ /*
+ * If useCache=false, in theory we could simply skip creating
+ * the CacheExprState node in the first place, but that might
+ * be surprising to future developers.
+ */
+ cstate->enabled = context->useCache;
+
+ state = (ExprState *) cstate;
+ }
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
CoerceToDomainState *cstate = makeNode(CoerceToDomainState);
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceToDomain;
! cstate->arg = ExecInitExprMutator(ctest->arg, context);
cstate->constraints = GetDomainConstraints(ctest->resulttype);
state = (ExprState *) cstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4879,4885 ****
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = NULL; /* not used */
! gstate->arg = ExecInitExpr(tle->expr, parent);
state = (ExprState *) gstate;
}
break;
--- 4989,4995 ----
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = NULL; /* not used */
! gstate->arg = ExecInitExprMutator(tle->expr, context);
state = (ExprState *) gstate;
}
break;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4891,4898 ****
foreach(l, (List *) node)
{
outlist = lappend(outlist,
! ExecInitExpr((Expr *) lfirst(l),
! parent));
}
/* Don't fall through to the "common" code below */
return (ExprState *) outlist;
--- 5001,5008 ----
foreach(l, (List *) node)
{
outlist = lappend(outlist,
! ExecInitExprMutator((Expr *) lfirst(l),
! context));
}
/* Don't fall through to the "common" code below */
return (ExprState *) outlist;
*************** ExecPrepareExpr(Expr *node, EState *esta
*** 4931,4937 ****
node = expression_planner(node);
! result = ExecInitExpr(node, NULL);
MemoryContextSwitchTo(oldcontext);
--- 5041,5047 ----
node = expression_planner(node);
! result = ExecInitExpr(node, NULL, false);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
new file mode 100644
index c9aa921..618a485
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1444,1453 ****
*/
aggstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) aggstate);
aggstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) aggstate);
/*
* initialize child nodes
--- 1444,1455 ----
*/
aggstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) aggstate,
! true);
aggstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) aggstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
new file mode 100644
index 849665d..f7e2d1c
*** a/src/backend/executor/nodeBitmapHeapscan.c
--- b/src/backend/executor/nodeBitmapHeapscan.c
*************** ExecInitBitmapHeapScan(BitmapHeapScan *n
*** 569,581 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
scanstate->bitmapqualorig = (List *)
ExecInitExpr((Expr *) node->bitmapqualorig,
! (PlanState *) scanstate);
/*
* tuple table initialization
--- 569,584 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
scanstate->bitmapqualorig = (List *)
ExecInitExpr((Expr *) node->bitmapqualorig,
! (PlanState *) scanstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c
new file mode 100644
index 7ab15ac..93c2c04
*** a/src/backend/executor/nodeCtescan.c
--- b/src/backend/executor/nodeCtescan.c
*************** ExecInitCteScan(CteScan *node, EState *e
*** 241,250 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
/*
* tuple table initialization
--- 241,252 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
new file mode 100644
index 9204859..1602290
*** a/src/backend/executor/nodeForeignscan.c
--- b/src/backend/executor/nodeForeignscan.c
*************** ExecInitForeignScan(ForeignScan *node, E
*** 129,138 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
/*
* tuple table initialization
--- 129,140 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
new file mode 100644
index a700762..955c4ee
*** a/src/backend/executor/nodeFunctionscan.c
--- b/src/backend/executor/nodeFunctionscan.c
*************** ExecInitFunctionScan(FunctionScan *node,
*** 153,162 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
/*
* Now determine if the function returns a simple or composite type, and
--- 153,164 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
/*
* Now determine if the function returns a simple or composite type, and
*************** ExecInitFunctionScan(FunctionScan *node,
*** 217,223 ****
*/
scanstate->tuplestorestate = NULL;
scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
! (PlanState *) scanstate);
scanstate->ss.ps.ps_TupFromTlist = false;
--- 219,226 ----
*/
scanstate->tuplestorestate = NULL;
scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.ps_TupFromTlist = false;
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
new file mode 100644
index 80e282b..881e8d7
*** a/src/backend/executor/nodeGroup.c
--- b/src/backend/executor/nodeGroup.c
*************** ExecInitGroup(Group *node, EState *estat
*** 228,237 ****
*/
grpstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) grpstate);
grpstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) grpstate);
/*
* initialize child nodes
--- 228,239 ----
*/
grpstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) grpstate,
! true);
grpstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) grpstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
new file mode 100644
index cabe663..7d65e6d
*** a/src/backend/executor/nodeHash.c
--- b/src/backend/executor/nodeHash.c
*************** ExecInitHash(Hash *node, EState *estate,
*** 179,188 ****
*/
hashstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) hashstate);
hashstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) hashstate);
/*
* initialize child nodes
--- 179,190 ----
*/
hashstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) hashstate,
! true);
hashstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) hashstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
new file mode 100644
index 0ea490b..b42ad3f
*** a/src/backend/executor/nodeHashjoin.c
--- b/src/backend/executor/nodeHashjoin.c
*************** ExecInitHashJoin(HashJoin *node, EState
*** 463,479 ****
*/
hjstate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) hjstate);
hjstate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) hjstate);
hjstate->js.jointype = node->join.jointype;
hjstate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) hjstate);
hjstate->hashclauses = (List *)
ExecInitExpr((Expr *) node->hashclauses,
! (PlanState *) hjstate);
/*
* initialize child nodes
--- 463,483 ----
*/
hjstate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) hjstate,
! true);
hjstate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) hjstate,
! true);
hjstate->js.jointype = node->join.jointype;
hjstate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) hjstate,
! true);
hjstate->hashclauses = (List *)
ExecInitExpr((Expr *) node->hashclauses,
! (PlanState *) hjstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
new file mode 100644
index 4abd805..f20f92f
*** a/src/backend/executor/nodeIndexonlyscan.c
--- b/src/backend/executor/nodeIndexonlyscan.c
*************** ExecInitIndexOnlyScan(IndexOnlyScan *nod
*** 366,378 ****
*/
indexstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) indexstate);
indexstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) indexstate);
indexstate->indexqual = (List *)
ExecInitExpr((Expr *) node->indexqual,
! (PlanState *) indexstate);
/*
* tuple table initialization
--- 366,381 ----
*/
indexstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) indexstate,
! true);
indexstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) indexstate,
! true);
indexstate->indexqual = (List *)
ExecInitExpr((Expr *) node->indexqual,
! (PlanState *) indexstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
new file mode 100644
index e577bac..a2c979d
*** a/src/backend/executor/nodeIndexscan.c
--- b/src/backend/executor/nodeIndexscan.c
*************** ExecInitIndexScan(IndexScan *node, EStat
*** 494,506 ****
*/
indexstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) indexstate);
indexstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) indexstate);
indexstate->indexqualorig = (List *)
ExecInitExpr((Expr *) node->indexqualorig,
! (PlanState *) indexstate);
/*
* tuple table initialization
--- 494,509 ----
*/
indexstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) indexstate,
! true);
indexstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) indexstate,
! true);
indexstate->indexqualorig = (List *)
ExecInitExpr((Expr *) node->indexqualorig,
! (PlanState *) indexstate,
! true);
/*
* tuple table initialization
*************** ExecIndexBuildScanKeys(PlanState *planst
*** 817,823 ****
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
--- 820,826 ----
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate, true);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
*************** ExecIndexBuildScanKeys(PlanState *planst
*** 944,950 ****
}
runtime_keys[n_runtime_keys].scan_key = this_sub_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
--- 947,953 ----
}
runtime_keys[n_runtime_keys].scan_key = this_sub_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate, true);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
*************** ExecIndexBuildScanKeys(PlanState *planst
*** 1062,1068 ****
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate);
/*
* Careful here: the runtime expression is not of
--- 1065,1071 ----
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate, true);
/*
* Careful here: the runtime expression is not of
*************** ExecIndexBuildScanKeys(PlanState *planst
*** 1080,1086 ****
/* Executor has to expand the array value */
array_keys[n_array_keys].scan_key = this_scan_key;
array_keys[n_array_keys].array_expr =
! ExecInitExpr(rightop, planstate);
/* the remaining fields were zeroed by palloc0 */
n_array_keys++;
scanvalue = (Datum) 0;
--- 1083,1089 ----
/* Executor has to expand the array value */
array_keys[n_array_keys].scan_key = this_scan_key;
array_keys[n_array_keys].array_expr =
! ExecInitExpr(rightop, planstate, true);
/* the remaining fields were zeroed by palloc0 */
n_array_keys++;
scanvalue = (Datum) 0;
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
new file mode 100644
index 8936187..3e2b643
*** a/src/backend/executor/nodeLimit.c
--- b/src/backend/executor/nodeLimit.c
*************** ExecInitLimit(Limit *node, EState *estat
*** 399,407 ****
* initialize child expressions
*/
limitstate->limitOffset = ExecInitExpr((Expr *) node->limitOffset,
! (PlanState *) limitstate);
limitstate->limitCount = ExecInitExpr((Expr *) node->limitCount,
! (PlanState *) limitstate);
/*
* Tuple table initialization (XXX not actually used...)
--- 399,409 ----
* initialize child expressions
*/
limitstate->limitOffset = ExecInitExpr((Expr *) node->limitOffset,
! (PlanState *) limitstate,
! true);
limitstate->limitCount = ExecInitExpr((Expr *) node->limitCount,
! (PlanState *) limitstate,
! true);
/*
* Tuple table initialization (XXX not actually used...)
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
new file mode 100644
index f921847..be45c3e
*** a/src/backend/executor/nodeMergejoin.c
--- b/src/backend/executor/nodeMergejoin.c
*************** MJExamineQuals(List *mergeclauses,
*** 206,213 ****
/*
* Prepare the input expressions for execution.
*/
! clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent);
! clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent);
/* Set up sort support data */
clause->ssup.ssup_cxt = CurrentMemoryContext;
--- 206,213 ----
/*
* Prepare the input expressions for execution.
*/
! clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent, true);
! clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent, true);
/* Set up sort support data */
clause->ssup.ssup_cxt = CurrentMemoryContext;
*************** ExecInitMergeJoin(MergeJoin *node, EStat
*** 1498,1511 ****
*/
mergestate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) mergestate);
mergestate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) mergestate);
mergestate->js.jointype = node->join.jointype;
mergestate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) mergestate);
mergestate->mj_ConstFalseJoin = false;
/* mergeclauses are handled below */
--- 1498,1514 ----
*/
mergestate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) mergestate,
! true);
mergestate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) mergestate,
! true);
mergestate->js.jointype = node->join.jointype;
mergestate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) mergestate,
! true);
mergestate->mj_ConstFalseJoin = false;
/* mergeclauses are handled below */
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
new file mode 100644
index dfdcb20..3e2810b
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
*************** ExecInitModifyTable(ModifyTable *node, E
*** 1003,1009 ****
List *rlist = (List *) lfirst(l);
List *rliststate;
! rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rliststate, econtext, slot,
resultRelInfo->ri_RelationDesc->rd_att);
--- 1003,1009 ----
List *rlist = (List *) lfirst(l);
List *rliststate;
! rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps, true);
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rliststate, econtext, slot,
resultRelInfo->ri_RelationDesc->rd_att);
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
new file mode 100644
index 11d027b..0452478
*** a/src/backend/executor/nodeNestloop.c
--- b/src/backend/executor/nodeNestloop.c
*************** ExecInitNestLoop(NestLoop *node, EState
*** 322,335 ****
*/
nlstate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) nlstate);
nlstate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) nlstate);
nlstate->js.jointype = node->join.jointype;
nlstate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) nlstate);
/*
* initialize child nodes
--- 322,338 ----
*/
nlstate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) nlstate,
! true);
nlstate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) nlstate,
! true);
nlstate->js.jointype = node->join.jointype;
nlstate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) nlstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
new file mode 100644
index b51efd8..5e0869c
*** a/src/backend/executor/nodeResult.c
--- b/src/backend/executor/nodeResult.c
*************** ExecInitResult(Result *node, EState *est
*** 240,251 ****
*/
resstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) resstate);
resstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) resstate);
resstate->resconstantqual = ExecInitExpr((Expr *) node->resconstantqual,
! (PlanState *) resstate);
/*
* initialize child nodes
--- 240,254 ----
*/
resstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) resstate,
! true);
resstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) resstate,
! true);
resstate->resconstantqual = ExecInitExpr((Expr *) node->resconstantqual,
! (PlanState *) resstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
new file mode 100644
index 9fcd332..17589fa
*** a/src/backend/executor/nodeSeqscan.c
--- b/src/backend/executor/nodeSeqscan.c
*************** ExecInitSeqScan(SeqScan *node, EState *e
*** 182,191 ****
*/
scanstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) scanstate);
scanstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) scanstate);
/*
* tuple table initialization
--- 182,193 ----
*/
scanstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) scanstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
new file mode 100644
index da31820..b24a372
*** a/src/backend/executor/nodeSubplan.c
--- b/src/backend/executor/nodeSubplan.c
*************** ExecInitSubPlan(SubPlan *subplan, PlanSt
*** 661,668 ****
subplan->plan_id - 1);
/* Initialize subexpressions */
! sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
! sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
/*
* initialize my state
--- 661,668 ----
subplan->plan_id - 1);
/* Initialize subexpressions */
! sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent, true);
! sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent, true);
/*
* initialize my state
*************** ExecInitAlternativeSubPlan(AlternativeSu
*** 1102,1108 ****
* we're going to use?)
*/
asstate->subplans = (List *) ExecInitExpr((Expr *) asplan->subplans,
! parent);
/*
* Select the one to be used. For this, we need an estimate of the number
--- 1102,1109 ----
* we're going to use?)
*/
asstate->subplans = (List *) ExecInitExpr((Expr *) asplan->subplans,
! parent,
! true);
/*
* Select the one to be used. For this, we need an estimate of the number
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
new file mode 100644
index b5ef152..040ba5c
*** a/src/backend/executor/nodeSubqueryscan.c
--- b/src/backend/executor/nodeSubqueryscan.c
*************** ExecInitSubqueryScan(SubqueryScan *node,
*** 122,131 ****
*/
subquerystate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) subquerystate);
subquerystate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) subquerystate);
/*
* tuple table initialization
--- 122,133 ----
*/
subquerystate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) subquerystate,
! true);
subquerystate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) subquerystate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
new file mode 100644
index 32b8abf..3842911
*** a/src/backend/executor/nodeTidscan.c
--- b/src/backend/executor/nodeTidscan.c
*************** ExecInitTidScan(TidScan *node, EState *e
*** 506,519 ****
*/
tidstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) tidstate);
tidstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) tidstate);
tidstate->tss_tidquals = (List *)
ExecInitExpr((Expr *) node->tidquals,
! (PlanState *) tidstate);
/*
* tuple table initialization
--- 506,522 ----
*/
tidstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) tidstate,
! true);
tidstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) tidstate,
! true);
tidstate->tss_tidquals = (List *)
ExecInitExpr((Expr *) node->tidquals,
! (PlanState *) tidstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
new file mode 100644
index fc17677..c43ef37
*** a/src/backend/executor/nodeValuesscan.c
--- b/src/backend/executor/nodeValuesscan.c
*************** ValuesNext(ValuesScanState *node)
*** 118,124 ****
* is a SubPlan, and there shouldn't be any (any subselects in the
* VALUES list should be InitPlans).
*/
! exprstatelist = (List *) ExecInitExpr((Expr *) exprlist, NULL);
/* parser should have checked all sublists are the same length */
Assert(list_length(exprstatelist) == slot->tts_tupleDescriptor->natts);
--- 118,124 ----
* is a SubPlan, and there shouldn't be any (any subselects in the
* VALUES list should be InitPlans).
*/
! exprstatelist = (List *) ExecInitExpr((Expr *) exprlist, NULL, true);
/* parser should have checked all sublists are the same length */
Assert(list_length(exprstatelist) == slot->tts_tupleDescriptor->natts);
*************** ExecInitValuesScan(ValuesScan *node, ESt
*** 231,240 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
/*
* get info about values list
--- 231,242 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
/*
* get info about values list
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
new file mode 100644
index ce89ff2..2d2c28b
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1455,1461 ****
winstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) winstate);
/*
* WindowAgg nodes never have quals, since they can only occur at the
--- 1455,1462 ----
winstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) winstate,
! true);
/*
* WindowAgg nodes never have quals, since they can only occur at the
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1622,1630 ****
/* initialize frame bound offset expressions */
winstate->startOffset = ExecInitExpr((Expr *) node->startOffset,
! (PlanState *) winstate);
winstate->endOffset = ExecInitExpr((Expr *) node->endOffset,
! (PlanState *) winstate);
winstate->all_first = true;
winstate->partition_spooled = false;
--- 1623,1633 ----
/* initialize frame bound offset expressions */
winstate->startOffset = ExecInitExpr((Expr *) node->startOffset,
! (PlanState *) winstate,
! true);
winstate->endOffset = ExecInitExpr((Expr *) node->endOffset,
! (PlanState *) winstate,
! true);
winstate->all_first = true;
winstate->partition_spooled = false;
diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c
new file mode 100644
index e2f3dd4..e68d268
*** a/src/backend/executor/nodeWorktablescan.c
--- b/src/backend/executor/nodeWorktablescan.c
*************** ExecInitWorkTableScan(WorkTableScan *nod
*** 160,169 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
/*
* tuple table initialization
--- 160,171 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index cc3168d..8d305b5
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyBooleanTest(const BooleanTest *from
*** 1684,1689 ****
--- 1684,1702 ----
}
/*
+ * _copyCacheExpr
+ */
+ static CacheExpr *
+ _copyCacheExpr(const CacheExpr *from)
+ {
+ CacheExpr *newnode = makeNode(CacheExpr);
+
+ COPY_NODE_FIELD(arg);
+
+ return newnode;
+ }
+
+ /*
* _copyCoerceToDomain
*/
static CoerceToDomain *
*************** copyObject(const void *from)
*** 4010,4015 ****
--- 4023,4031 ----
case T_BooleanTest:
retval = _copyBooleanTest(from);
break;
+ case T_CacheExpr:
+ retval = _copyCacheExpr(from);
+ break;
case T_CoerceToDomain:
retval = _copyCoerceToDomain(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 2295195..709086a
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalBooleanTest(const BooleanTest *a,
*** 678,683 ****
--- 678,691 ----
}
static bool
+ _equalCacheExpr(const CacheExpr *a, const CacheExpr *b)
+ {
+ COMPARE_NODE_FIELD(arg);
+
+ return true;
+ }
+
+ static bool
_equalCoerceToDomain(const CoerceToDomain *a, const CoerceToDomain *b)
{
COMPARE_NODE_FIELD(arg);
*************** equal(const void *a, const void *b)
*** 2566,2571 ****
--- 2574,2582 ----
case T_BooleanTest:
retval = _equalBooleanTest(a, b);
break;
+ case T_CacheExpr:
+ retval = _equalCacheExpr(a, b);
+ break;
case T_CoerceToDomain:
retval = _equalCoerceToDomain(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
new file mode 100644
index bb5cdae..c8549eb
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
*************** makeFuncExpr(Oid funcid, Oid rettype, Li
*** 471,476 ****
--- 471,491 ----
}
/*
+ * makeCacheExpr -
+ * build an expression node for a cachable expression.
+ */
+ CacheExpr *
+ makeCacheExpr(Expr *arg)
+ {
+ CacheExpr *cacheexpr;
+
+ cacheexpr = makeNode(CacheExpr);
+ cacheexpr->arg = arg;
+
+ return cacheexpr;
+ }
+
+ /*
* makeDefElem -
* build a DefElem node
*
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index 51459c4..fab2104
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** exprType(const Node *expr)
*** 212,217 ****
--- 212,220 ----
case T_BooleanTest:
type = BOOLOID;
break;
+ case T_CacheExpr:
+ type = exprType((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
type = ((const CoerceToDomain *) expr)->resulttype;
break;
*************** exprCollation(const Node *expr)
*** 792,797 ****
--- 795,803 ----
case T_BooleanTest:
coll = InvalidOid; /* result is always boolean */
break;
+ case T_CacheExpr:
+ coll = exprCollation((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
coll = ((const CoerceToDomain *) expr)->resultcollid;
break;
*************** exprLocation(const Node *expr)
*** 1263,1268 ****
--- 1269,1278 ----
/* just use argument's location */
loc = exprLocation((Node *) ((const BooleanTest *) expr)->arg);
break;
+ case T_CacheExpr:
+ /* original expression location */
+ loc = exprLocation((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
{
const CoerceToDomain *cexpr = (const CoerceToDomain *) expr;
*************** expression_tree_walker(Node *node,
*** 1722,1727 ****
--- 1732,1739 ----
return walker(((NullTest *) node)->arg, context);
case T_BooleanTest:
return walker(((BooleanTest *) node)->arg, context);
+ case T_CacheExpr:
+ return walker(((CacheExpr *) node)->arg, context);
case T_CoerceToDomain:
return walker(((CoerceToDomain *) node)->arg, context);
case T_TargetEntry:
*************** expression_tree_mutator(Node *node,
*** 2374,2379 ****
--- 2386,2401 ----
return (Node *) newnode;
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+ CacheExpr *newnode;
+
+ FLATCOPY(newnode, cache, CacheExpr);
+ MUTATE(newnode->arg, cache->arg, Expr *);
+ return (Node *) newnode;
+ }
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
*************** bool
*** 2781,2786 ****
--- 2803,2810 ----
return walker(((NullTest *) node)->arg, context);
case T_BooleanTest:
return walker(((BooleanTest *) node)->arg, context);
+ case T_CacheExpr:
+ return walker(((CacheExpr *) node)->arg, context);
case T_JoinExpr:
{
JoinExpr *join = (JoinExpr *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 829f6d4..b7e6d68
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outBooleanTest(StringInfo str, const Bo
*** 1367,1372 ****
--- 1367,1380 ----
}
static void
+ _outCacheExpr(StringInfo str, const CacheExpr *node)
+ {
+ WRITE_NODE_TYPE("CACHEEXPR");
+
+ WRITE_NODE_FIELD(arg);
+ }
+
+ static void
_outCoerceToDomain(StringInfo str, const CoerceToDomain *node)
{
WRITE_NODE_TYPE("COERCETODOMAIN");
*************** _outNode(StringInfo str, const void *obj
*** 2909,2914 ****
--- 2917,2925 ----
case T_BooleanTest:
_outBooleanTest(str, obj);
break;
+ case T_CacheExpr:
+ _outCacheExpr(str, obj);
+ break;
case T_CoerceToDomain:
_outCoerceToDomain(str, obj);
break;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
new file mode 100644
index 885d855..59514b8
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** cost_qual_eval_walker(Node *node, cost_q
*** 3036,3041 ****
--- 3036,3059 ----
*/
return false; /* don't recurse into children */
}
+ else if (IsA(node, CacheExpr))
+ {
+ cost_qual_eval_context locContext;
+ CacheExpr *cache = (CacheExpr *) node;
+
+ locContext.root = context->root;
+ locContext.total.startup = 0;
+ locContext.total.per_tuple = 0;
+
+ cost_qual_eval_walker((Node *) cache->arg, &locContext);
+
+ /* Account all cached per-tuple costs once in the startup cost. */
+ context->total.startup +=
+ locContext.total.startup + locContext.total.per_tuple;
+
+ /* do NOT recurse into children */
+ return false;
+ }
else if (IsA(node, CoerceViaIO))
{
CoerceViaIO *iocoerce = (CoerceViaIO *) node;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
new file mode 100644
index 126d494..1f6dd22
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
*************** static Relids find_nonnullable_rels_walk
*** 97,139 ****
static List *find_nonnullable_vars_walker(Node *node, bool top_level);
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
static bool set_coercionform_dontcare_walker(Node *node, void *context);
! static Node *eval_const_expressions_mutator(Node *node,
! eval_const_expressions_context *context);
static List *simplify_or_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceTrue);
static List *simplify_and_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceFalse);
static Node *simplify_boolean_equality(Oid opno, List *args);
static Expr *simplify_function(Expr *oldexpr, Oid funcid,
Oid result_type, int32 result_typmod, Oid result_collid,
Oid input_collid, List **args,
- bool has_named_args,
bool allow_inline,
! eval_const_expressions_context *context);
static List *reorder_function_arguments(List *args, Oid result_type,
! HeapTuple func_tuple,
! eval_const_expressions_context *context);
static List *add_function_defaults(List *args, Oid result_type,
! HeapTuple func_tuple,
! eval_const_expressions_context *context);
static List *fetch_function_defaults(HeapTuple func_tuple);
static void recheck_cast_function_args(List *args, Oid result_type,
HeapTuple func_tuple);
static Expr *evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context);
static Expr *inline_function(Oid funcid, Oid result_type, Oid result_collid,
Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context);
static Node *substitute_actual_parameters(Node *expr, int nargs, List *args,
int *usecounts);
static Node *substitute_actual_parameters_mutator(Node *node,
substitute_actual_parameters_context *context);
static void sql_inline_error_callback(void *arg);
static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
static Query *substitute_actual_srf_parameters(Query *expr,
--- 97,146 ----
static List *find_nonnullable_vars_walker(Node *node, bool top_level);
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
static bool set_coercionform_dontcare_walker(Node *node, void *context);
! static Node *caching_const_expressions_mutator(Node *node,
! eval_const_expressions_context *context);
! static Node *const_expressions_mutator(Node *node,
! eval_const_expressions_context *context,
! bool *cachable);
static List *simplify_or_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceTrue, bool *cachable);
static List *simplify_and_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceFalse, bool *cachable);
static Node *simplify_boolean_equality(Oid opno, List *args);
static Expr *simplify_function(Expr *oldexpr, Oid funcid,
Oid result_type, int32 result_typmod, Oid result_collid,
Oid input_collid, List **args,
bool allow_inline,
! eval_const_expressions_context *context,
! bool *cachable);
! static List *simplify_copy_function_arguments(List *old_args, Oid result_type,
! HeapTuple func_tuple);
static List *reorder_function_arguments(List *args, Oid result_type,
! HeapTuple func_tuple);
static List *add_function_defaults(List *args, Oid result_type,
! HeapTuple func_tuple);
static List *fetch_function_defaults(HeapTuple func_tuple);
static void recheck_cast_function_args(List *args, Oid result_type,
HeapTuple func_tuple);
static Expr *evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context,
! bool *cachable);
static Expr *inline_function(Oid funcid, Oid result_type, Oid result_collid,
Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context,
! bool *cachable);
static Node *substitute_actual_parameters(Node *expr, int nargs, List *args,
int *usecounts);
static Node *substitute_actual_parameters_mutator(Node *node,
substitute_actual_parameters_context *context);
static void sql_inline_error_callback(void *arg);
+ static inline bool is_cache_useful(Expr *expr);
+ static Expr *insert_cache(Expr *expr);
static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
static Query *substitute_actual_srf_parameters(Query *expr,
*************** rowtype_field_matches(Oid rowtypeid, int
*** 2028,2033 ****
--- 2035,2045 ----
* assumption in the presence of user-defined functions; do we need a
* pg_proc flag that prevents discarding the execution of a function?)
*
+ * We also insert CacheExpr nodes above expressions that cannot be
+ * evaluated at planning time, but are constant at execution time.
+ * This includes expressions that contain stable function calls and
+ * Param references.
+ *
* We do understand that certain functions may deliver non-constant
* results even with constant inputs, "nextval()" being the classic
* example. Functions that are not marked "immutable" in pg_proc
*************** eval_const_expressions(PlannerInfo *root
*** 2066,2072 ****
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = false; /* safe transformations only */
! return eval_const_expressions_mutator(node, &context);
}
/*--------------------
--- 2078,2085 ----
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = false; /* safe transformations only */
!
! return caching_const_expressions_mutator(node, &context);
}
/*--------------------
*************** eval_const_expressions(PlannerInfo *root
*** 2084,2095 ****
--- 2097,2115 ----
* value of the Param.
* 2. Fold stable, as well as immutable, functions to constants.
* 3. Reduce PlaceHolderVar nodes to their contained expressions.
+ * 4. Strip CacheExpr nodes, as selectivity estimation functions aren't
+ * prepared to deal with CacheExpr nodes.
+ *
+ * NOTE: This function is not indempotent. Calling it twice over an expression
+ * tree causes CacheExpr nodes to be removed in the first pass, then re-added
+ * in the 2nd pass. Make sure it only gets called once.
*--------------------
*/
Node *
estimate_expression_value(PlannerInfo *root, Node *node)
{
eval_const_expressions_context context;
+ bool isCachable = true;
context.boundParams = root->glob->boundParams; /* bound Params */
/* we do not need to mark the plan as depending on inlined functions */
*************** estimate_expression_value(PlannerInfo *r
*** 2097,2109 ****
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = true; /* unsafe transformations OK */
! return eval_const_expressions_mutator(node, &context);
}
static Node *
! eval_const_expressions_mutator(Node *node,
! eval_const_expressions_context *context)
{
if (node == NULL)
return NULL;
switch (nodeTag(node))
--- 2117,2159 ----
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = true; /* unsafe transformations OK */
!
! return const_expressions_mutator(node, &context, &isCachable);
}
+ /*
+ * Calls const_expressions_mutator on the expression tree and automatically
+ * adds a CacheExpr node if the expression is cachable.
+ */
static Node *
! caching_const_expressions_mutator(Node *node,
! eval_const_expressions_context *context)
{
+ bool isCachable = true;
+
+ if (node == NULL)
+ return NULL;
+
+ node = const_expressions_mutator(node, context, &isCachable);
+ if (isCachable)
+ node = (Node *) insert_cache((Expr *) node);
+
+ return node;
+ }
+
+ /*
+ * Returns a mutated node tree and determines its cachability.
+ *
+ * The caller must make sure that cachable points to a boolean value that's
+ * initialized to TRUE.
+ */
+ static Node *
+ const_expressions_mutator(Node *node,
+ eval_const_expressions_context *context,
+ bool *cachable)
+ {
+ Assert(*cachable == true);
+
if (node == NULL)
return NULL;
switch (nodeTag(node))
*************** eval_const_expressions_mutator(Node *nod
*** 2112,2117 ****
--- 2162,2175 ----
{
Param *param = (Param *) node;
+ /*
+ * Only externally-supplied parameters are stable. Other
+ * params are used for passing changing values within the
+ * executor
+ */
+ if (param->paramkind != PARAM_EXTERN)
+ *cachable = false;
+
/* Look to see if we've been given a value for this Param */
if (param->paramkind == PARAM_EXTERN &&
context->boundParams != NULL &&
*************** eval_const_expressions_mutator(Node *nod
*** 2163,2189 ****
case T_FuncExpr:
{
FuncExpr *expr = (FuncExpr *) node;
! List *args;
! bool has_named_args;
Expr *simple;
FuncExpr *newexpr;
- ListCell *lc;
-
- /*
- * Reduce constants in the FuncExpr's arguments, and check to
- * see if there are any named args.
- */
- args = NIL;
- has_named_args = false;
- foreach(lc, expr->args)
- {
- Node *arg = (Node *) lfirst(lc);
-
- arg = eval_const_expressions_mutator(arg, context);
- if (IsA(arg, NamedArgExpr))
- has_named_args = true;
- args = lappend(args, arg);
- }
/*
* Code for op/func reduction is pretty bulky, so split it out
--- 2221,2229 ----
case T_FuncExpr:
{
FuncExpr *expr = (FuncExpr *) node;
! List *args = expr->args;
Expr *simple;
FuncExpr *newexpr;
/*
* Code for op/func reduction is pretty bulky, so split it out
*************** eval_const_expressions_mutator(Node *nod
*** 2199,2207 ****
expr->funccollid,
expr->inputcollid,
&args,
- has_named_args,
true,
! context);
if (simple) /* successfully simplified it */
return (Node *) simple;
--- 2239,2247 ----
expr->funccollid,
expr->inputcollid,
&args,
true,
! context,
! cachable);
if (simple) /* successfully simplified it */
return (Node *) simple;
*************** eval_const_expressions_mutator(Node *nod
*** 2225,2245 ****
case T_OpExpr:
{
OpExpr *expr = (OpExpr *) node;
! List *args;
Expr *simple;
OpExpr *newexpr;
/*
- * Reduce constants in the OpExpr's arguments. We know args
- * is either NIL or a List node, so we can call
- * expression_tree_mutator directly rather than recursing to
- * self.
- */
- args = (List *) expression_tree_mutator((Node *) expr->args,
- eval_const_expressions_mutator,
- (void *) context);
-
- /*
* Need to get OID of underlying function. Okay to scribble
* on input to this extent.
*/
--- 2265,2275 ----
case T_OpExpr:
{
OpExpr *expr = (OpExpr *) node;
! List *args = expr->args;
Expr *simple;
OpExpr *newexpr;
/*
* Need to get OID of underlying function. Okay to scribble
* on input to this extent.
*/
*************** eval_const_expressions_mutator(Node *nod
*** 2255,2261 ****
expr->opcollid,
expr->inputcollid,
&args,
! false, true, context);
if (simple) /* successfully simplified it */
return (Node *) simple;
--- 2285,2292 ----
expr->opcollid,
expr->inputcollid,
&args,
! true, context,
! cachable);
if (simple) /* successfully simplified it */
return (Node *) simple;
*************** eval_const_expressions_mutator(Node *nod
*** 2292,2326 ****
case T_DistinctExpr:
{
DistinctExpr *expr = (DistinctExpr *) node;
! List *args;
! ListCell *arg;
bool has_null_input = false;
bool all_null_input = true;
bool has_nonconst_input = false;
Expr *simple;
DistinctExpr *newexpr;
/*
! * Reduce constants in the DistinctExpr's arguments. We know
! * args is either NIL or a List node, so we can call
! * expression_tree_mutator directly rather than recursing to
! * self.
*/
! args = (List *) expression_tree_mutator((Node *) expr->args,
! eval_const_expressions_mutator,
! (void *) context);
/*
* We must do our own check for NULLs because DistinctExpr has
* different results for NULL input than the underlying
* operator does.
*/
! foreach(arg, args)
{
! if (IsA(lfirst(arg), Const))
{
! has_null_input |= ((Const *) lfirst(arg))->constisnull;
! all_null_input &= ((Const *) lfirst(arg))->constisnull;
}
else
has_nonconst_input = true;
--- 2323,2371 ----
case T_DistinctExpr:
{
DistinctExpr *expr = (DistinctExpr *) node;
! List *args = NIL;
! ListCell *lc;
! Node *arg;
bool has_null_input = false;
bool all_null_input = true;
bool has_nonconst_input = false;
Expr *simple;
DistinctExpr *newexpr;
+ bool leftCachable = true;
+ bool rightCachable = true;
/*
! * Reduce constants in the DistinctExpr's arguments
! *
! * Note that simplify_function() might call the mutator
! * function on arguments for a second time. However, this is
! * harmless because it's only called when arguments are
! * constant.
*/
! Assert(list_length(expr->args) == 2);
!
! arg = const_expressions_mutator(linitial(expr->args),
! context,
! &leftCachable);
! args = lappend(args, arg);
!
! arg = const_expressions_mutator(lsecond(expr->args),
! context,
! &rightCachable);
! args = lappend(args, arg);
/*
* We must do our own check for NULLs because DistinctExpr has
* different results for NULL input than the underlying
* operator does.
*/
! foreach(lc, args)
{
! arg = lfirst(lc);
! if (IsA(arg, Const))
{
! has_null_input |= ((Const *) arg)->constisnull;
! all_null_input &= ((Const *) arg)->constisnull;
}
else
has_nonconst_input = true;
*************** eval_const_expressions_mutator(Node *nod
*** 2357,2363 ****
expr->opcollid,
expr->inputcollid,
&args,
! false, false, context);
if (simple) /* successfully simplified it */
{
/*
--- 2402,2410 ----
expr->opcollid,
expr->inputcollid,
&args,
! false, context,
! cachable);
!
if (simple) /* successfully simplified it */
{
/*
*************** eval_const_expressions_mutator(Node *nod
*** 2372,2377 ****
--- 2419,2450 ----
return (Node *) csimple;
}
}
+ else if (!leftCachable || !rightCachable)
+ {
+ *cachable = false;
+ }
+ else
+ {
+ /*
+ * This expression is only cachable if the equality
+ * operator is not volatile.
+ */
+ HeapTuple func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(expr->opfuncid));
+ Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
+
+ if (funcform->provolatile == PROVOLATILE_VOLATILE)
+ *cachable = false;
+
+ ReleaseSysCache(func_tuple);
+ }
+
+ if (!(*cachable))
+ {
+ if (leftCachable)
+ linitial(args) = insert_cache((Expr *) linitial(args));
+ if (rightCachable)
+ lsecond(args) = insert_cache((Expr *) lsecond(args));
+ }
/*
* The expression cannot be simplified any further, so build
*************** eval_const_expressions_mutator(Node *nod
*** 2404,2410 ****
newargs = simplify_or_arguments(expr->args,
context,
&haveNull,
! &forceTrue);
if (forceTrue)
return makeBoolConst(true, false);
if (haveNull)
--- 2477,2484 ----
newargs = simplify_or_arguments(expr->args,
context,
&haveNull,
! &forceTrue,
! cachable);
if (forceTrue)
return makeBoolConst(true, false);
if (haveNull)
*************** eval_const_expressions_mutator(Node *nod
*** 2432,2438 ****
newargs = simplify_and_arguments(expr->args,
context,
&haveNull,
! &forceFalse);
if (forceFalse)
return makeBoolConst(false, false);
if (haveNull)
--- 2506,2513 ----
newargs = simplify_and_arguments(expr->args,
context,
&haveNull,
! &forceFalse,
! cachable);
if (forceFalse)
return makeBoolConst(false, false);
if (haveNull)
*************** eval_const_expressions_mutator(Node *nod
*** 2456,2463 ****
Node *arg;
Assert(list_length(expr->args) == 1);
! arg = eval_const_expressions_mutator(linitial(expr->args),
! context);
/*
* Use negate_clause() to see if we can simplify
--- 2531,2539 ----
Node *arg;
Assert(list_length(expr->args) == 1);
! arg = const_expressions_mutator(linitial(expr->args),
! context,
! cachable);
/*
* Use negate_clause() to see if we can simplify
*************** eval_const_expressions_mutator(Node *nod
*** 2481,2486 ****
--- 2557,2563 ----
* XXX should we ereport() here instead? Probably this routine
* should never be invoked after SubPlan creation.
*/
+ *cachable = false;
return node;
case T_RelabelType:
{
*************** eval_const_expressions_mutator(Node *nod
*** 2493,2500 ****
RelabelType *relabel = (RelabelType *) node;
Node *arg;
! arg = eval_const_expressions_mutator((Node *) relabel->arg,
! context);
/*
* If we find stacked RelabelTypes (eg, from foo :: int ::
--- 2570,2578 ----
RelabelType *relabel = (RelabelType *) node;
Node *arg;
! arg = const_expressions_mutator((Node *) relabel->arg,
! context,
! cachable);
/*
* If we find stacked RelabelTypes (eg, from foo :: int ::
*************** eval_const_expressions_mutator(Node *nod
*** 2528,2534 ****
case T_CoerceViaIO:
{
CoerceViaIO *expr = (CoerceViaIO *) node;
- Expr *arg;
List *args;
Oid outfunc;
bool outtypisvarlena;
--- 2606,2611 ----
*************** eval_const_expressions_mutator(Node *nod
*** 2540,2548 ****
/*
* Reduce constants in the CoerceViaIO's argument.
*/
! arg = (Expr *) eval_const_expressions_mutator((Node *) expr->arg,
! context);
! args = list_make1(arg);
/*
* CoerceViaIO represents calling the source type's output
--- 2617,2623 ----
/*
* Reduce constants in the CoerceViaIO's argument.
*/
! args = list_make1(expr->arg);
/*
* CoerceViaIO represents calling the source type's output
*************** eval_const_expressions_mutator(Node *nod
*** 2553,2559 ****
* Note that the coercion functions are assumed not to care
* about input collation, so we just pass InvalidOid for that.
*/
! getTypeOutputInfo(exprType((Node *) arg),
&outfunc, &outtypisvarlena);
getTypeInputInfo(expr->resulttype,
&infunc, &intypioparam);
--- 2628,2634 ----
* Note that the coercion functions are assumed not to care
* about input collation, so we just pass InvalidOid for that.
*/
! getTypeOutputInfo(exprType((Node *) expr->arg),
&outfunc, &outtypisvarlena);
getTypeInputInfo(expr->resulttype,
&infunc, &intypioparam);
*************** eval_const_expressions_mutator(Node *nod
*** 2564,2570 ****
InvalidOid,
InvalidOid,
&args,
! false, true, context);
if (simple) /* successfully simplified output fn */
{
/*
--- 2639,2646 ----
InvalidOid,
InvalidOid,
&args,
! true, context,
! cachable);
if (simple) /* successfully simplified output fn */
{
/*
*************** eval_const_expressions_mutator(Node *nod
*** 2594,2600 ****
expr->resultcollid,
InvalidOid,
&args,
! false, true, context);
if (simple) /* successfully simplified input fn */
return (Node *) simple;
}
--- 2670,2677 ----
expr->resultcollid,
InvalidOid,
&args,
! true, context,
! cachable);
if (simple) /* successfully simplified input fn */
return (Node *) simple;
}
*************** eval_const_expressions_mutator(Node *nod
*** 2605,2611 ****
* possibly-simplified argument.
*/
newexpr = makeNode(CoerceViaIO);
! newexpr->arg = arg;
newexpr->resulttype = expr->resulttype;
newexpr->resultcollid = expr->resultcollid;
newexpr->coerceformat = expr->coerceformat;
--- 2682,2688 ----
* possibly-simplified argument.
*/
newexpr = makeNode(CoerceViaIO);
! newexpr->arg = (Expr *) linitial(args);
newexpr->resulttype = expr->resulttype;
newexpr->resultcollid = expr->resultcollid;
newexpr->coerceformat = expr->coerceformat;
*************** eval_const_expressions_mutator(Node *nod
*** 2622,2629 ****
* Reduce constants in the ArrayCoerceExpr's argument, then
* build a new ArrayCoerceExpr.
*/
! arg = (Expr *) eval_const_expressions_mutator((Node *) expr->arg,
! context);
newexpr = makeNode(ArrayCoerceExpr);
newexpr->arg = arg;
--- 2699,2707 ----
* Reduce constants in the ArrayCoerceExpr's argument, then
* build a new ArrayCoerceExpr.
*/
! arg = (Expr *) const_expressions_mutator((Node *) expr->arg,
! context,
! cachable);
newexpr = makeNode(ArrayCoerceExpr);
newexpr->arg = arg;
*************** eval_const_expressions_mutator(Node *nod
*** 2646,2651 ****
--- 2724,2739 ----
newexpr->resulttype,
newexpr->resulttypmod,
newexpr->resultcollid);
+ /*
+ * If the argument is cachable, but conversion isn't, insert a
+ * CacheExpr above the argument
+ */
+ if (arg && *cachable && (OidIsValid(newexpr->elemfuncid) &&
+ func_volatile(newexpr->elemfuncid) == PROVOLATILE_VOLATILE))
+ {
+ *cachable = false;
+ newexpr->arg = insert_cache(arg);
+ }
/* Else we must return the partially-simplified node */
return (Node *) newexpr;
*************** eval_const_expressions_mutator(Node *nod
*** 2663,2670 ****
CollateExpr *collate = (CollateExpr *) node;
Node *arg;
! arg = eval_const_expressions_mutator((Node *) collate->arg,
! context);
if (arg && IsA(arg, Const))
{
--- 2751,2759 ----
CollateExpr *collate = (CollateExpr *) node;
Node *arg;
! arg = const_expressions_mutator((Node *) collate->arg,
! context,
! cachable);
if (arg && IsA(arg, Const))
{
*************** eval_const_expressions_mutator(Node *nod
*** 2733,2745 ****
Node *save_case_val;
Node *newarg;
List *newargs;
bool const_true_cond;
Node *defresult = NULL;
ListCell *arg;
/* Simplify the test expression, if any */
! newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
! context);
/* Set up for contained CaseTestExpr nodes */
save_case_val = context->case_val;
--- 2822,2835 ----
Node *save_case_val;
Node *newarg;
List *newargs;
+ List *cachable_args = NIL;
bool const_true_cond;
Node *defresult = NULL;
ListCell *arg;
/* Simplify the test expression, if any */
! newarg = caching_const_expressions_mutator((Node *) caseexpr->arg,
! context);
/* Set up for contained CaseTestExpr nodes */
save_case_val = context->case_val;
*************** eval_const_expressions_mutator(Node *nod
*** 2759,2770 ****
CaseWhen *oldcasewhen = (CaseWhen *) lfirst(arg);
Node *casecond;
Node *caseresult;
Assert(IsA(oldcasewhen, CaseWhen));
/* Simplify this alternative's test condition */
! casecond = eval_const_expressions_mutator((Node *) oldcasewhen->expr,
! context);
/*
* If the test condition is constant FALSE (or NULL), then
--- 2849,2863 ----
CaseWhen *oldcasewhen = (CaseWhen *) lfirst(arg);
Node *casecond;
Node *caseresult;
+ bool condCachable = true;
+ bool resultCachable = true;
Assert(IsA(oldcasewhen, CaseWhen));
/* Simplify this alternative's test condition */
! casecond = const_expressions_mutator((Node *) oldcasewhen->expr,
! context,
! &condCachable);
/*
* If the test condition is constant FALSE (or NULL), then
*************** eval_const_expressions_mutator(Node *nod
*** 2783,2790 ****
}
/* Simplify this alternative's result value */
! caseresult = eval_const_expressions_mutator((Node *) oldcasewhen->result,
! context);
/* If non-constant test condition, emit a new WHEN node */
if (!const_true_cond)
--- 2876,2884 ----
}
/* Simplify this alternative's result value */
! caseresult = const_expressions_mutator((Node *) oldcasewhen->result,
! context,
! &resultCachable);
/* If non-constant test condition, emit a new WHEN node */
if (!const_true_cond)
*************** eval_const_expressions_mutator(Node *nod
*** 2795,2800 ****
--- 2889,2911 ----
newcasewhen->result = (Expr *) caseresult;
newcasewhen->location = oldcasewhen->location;
newargs = lappend(newargs, newcasewhen);
+
+ if (condCachable)
+ {
+ if (is_cache_useful((Expr *) casecond))
+ cachable_args = lappend(cachable_args, &newcasewhen->expr);
+ }
+ else
+ *cachable = false;
+
+ if (resultCachable)
+ {
+ if (is_cache_useful((Expr *) caseresult))
+ cachable_args = lappend(cachable_args, &newcasewhen->result);
+ }
+ else
+ *cachable = false;
+
continue;
}
*************** eval_const_expressions_mutator(Node *nod
*** 2809,2816 ****
/* Simplify the default result, unless we replaced it above */
if (!const_true_cond)
! defresult = eval_const_expressions_mutator((Node *) caseexpr->defresult,
! context);
context->case_val = save_case_val;
--- 2920,2940 ----
/* Simplify the default result, unless we replaced it above */
if (!const_true_cond)
! {
! bool isCachable = true;
!
! defresult = const_expressions_mutator((Node *) caseexpr->defresult,
! context,
! &isCachable);
!
! if (isCachable)
! {
! if (is_cache_useful((Expr *) defresult))
! cachable_args = lappend(cachable_args, &defresult);
! }
! else
! *cachable = false;
! }
context->case_val = save_case_val;
*************** eval_const_expressions_mutator(Node *nod
*** 2820,2825 ****
--- 2944,2962 ----
*/
if (newargs == NIL)
return defresult;
+
+ if (!(*cachable))
+ {
+ ListCell *lc;
+
+ foreach(lc, cachable_args)
+ {
+ Expr **arg = (Expr **) lfirst(lc);
+
+ *arg = (Expr *) makeCacheExpr(*arg);
+ }
+ }
+
/* Otherwise we need a new CASE node */
newcase = makeNode(CaseExpr);
newcase->casetype = caseexpr->casetype;
*************** eval_const_expressions_mutator(Node *nod
*** 2840,2846 ****
--- 2977,2986 ----
if (context->case_val)
return copyObject(context->case_val);
else
+ {
+ *cachable = false;
return copyObject(node);
+ }
}
case T_ArrayExpr:
{
*************** eval_const_expressions_mutator(Node *nod
*** 2850,2862 ****
List *newelems;
ListCell *element;
newelems = NIL;
foreach(element, arrayexpr->elements)
{
Node *e;
! e = eval_const_expressions_mutator((Node *) lfirst(element),
! context);
if (!IsA(e, Const))
all_const = false;
newelems = lappend(newelems, e);
--- 2990,3004 ----
List *newelems;
ListCell *element;
+ *cachable = false; /* Not implemented */
+
newelems = NIL;
foreach(element, arrayexpr->elements)
{
Node *e;
! e = caching_const_expressions_mutator((Node *) lfirst(element),
! context);
if (!IsA(e, Const))
all_const = false;
newelems = lappend(newelems, e);
*************** eval_const_expressions_mutator(Node *nod
*** 2883,2897 ****
CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
CoalesceExpr *newcoalesce;
List *newargs;
! ListCell *arg;
newargs = NIL;
! foreach(arg, coalesceexpr->args)
{
! Node *e;
! e = eval_const_expressions_mutator((Node *) lfirst(arg),
! context);
/*
* We can remove null constants from the list. For a
--- 3025,3042 ----
CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
CoalesceExpr *newcoalesce;
List *newargs;
! List *cachable_args = NIL;
! ListCell *lc;
newargs = NIL;
! foreach(lc, coalesceexpr->args)
{
! Node *arg = lfirst(lc);
! bool isCachable = true;
! arg = const_expressions_mutator((Node *) arg,
! context,
! &isCachable);
/*
* We can remove null constants from the list. For a
*************** eval_const_expressions_mutator(Node *nod
*** 2901,2916 ****
* drop following arguments since they will never be
* reached.
*/
! if (IsA(e, Const))
{
! if (((Const *) e)->constisnull)
continue; /* drop null constant */
if (newargs == NIL)
! return e; /* first expr */
! newargs = lappend(newargs, e);
break;
}
! newargs = lappend(newargs, e);
}
/*
--- 3046,3070 ----
* drop following arguments since they will never be
* reached.
*/
! if (IsA(arg, Const))
{
! if (((Const *) arg)->constisnull)
continue; /* drop null constant */
if (newargs == NIL)
! return arg; /* first expr */
! newargs = lappend(newargs, arg);
break;
}
!
! newargs = lappend(newargs, arg);
!
! if (isCachable)
! {
! if (is_cache_useful((Expr *) arg))
! cachable_args = lappend(cachable_args, &llast(newargs));
! }
! else
! *cachable = false;
}
/*
*************** eval_const_expressions_mutator(Node *nod
*** 2921,2926 ****
--- 3075,3089 ----
return (Node *) makeNullConst(coalesceexpr->coalescetype,
-1,
coalesceexpr->coalescecollid);
+ if (!(*cachable))
+ {
+ foreach(lc, cachable_args)
+ {
+ Expr **arg = (Expr **) lfirst(lc);
+
+ *arg = (Expr *) makeCacheExpr(*arg);
+ }
+ }
newcoalesce = makeNode(CoalesceExpr);
newcoalesce->coalescetype = coalesceexpr->coalescetype;
*************** eval_const_expressions_mutator(Node *nod
*** 2942,2954 ****
* is still the same as when the FieldSelect was created ---
* this can change if someone did ALTER COLUMN TYPE on the
* rowtype.
*/
FieldSelect *fselect = (FieldSelect *) node;
FieldSelect *newfselect;
Node *arg;
! arg = eval_const_expressions_mutator((Node *) fselect->arg,
! context);
if (arg && IsA(arg, Var) &&
((Var *) arg)->varattno == InvalidAttrNumber)
{
--- 3105,3122 ----
* is still the same as when the FieldSelect was created ---
* this can change if someone did ALTER COLUMN TYPE on the
* rowtype.
+ *
+ * This is never cachable because Var references aren't
+ * constants. simplify_function() also refuses caching of
+ * row-returning functions
*/
FieldSelect *fselect = (FieldSelect *) node;
FieldSelect *newfselect;
Node *arg;
! arg = const_expressions_mutator((Node *) fselect->arg,
! context,
! cachable);
if (arg && IsA(arg, Var) &&
((Var *) arg)->varattno == InvalidAttrNumber)
{
*************** eval_const_expressions_mutator(Node *nod
*** 2999,3006 ****
NullTest *newntest;
Node *arg;
! arg = eval_const_expressions_mutator((Node *) ntest->arg,
! context);
if (arg && IsA(arg, RowExpr))
{
/*
--- 3167,3175 ----
NullTest *newntest;
Node *arg;
! arg = const_expressions_mutator((Node *) ntest->arg,
! context,
! cachable);
if (arg && IsA(arg, RowExpr))
{
/*
*************** eval_const_expressions_mutator(Node *nod
*** 3083,3090 ****
BooleanTest *newbtest;
Node *arg;
! arg = eval_const_expressions_mutator((Node *) btest->arg,
! context);
if (arg && IsA(arg, Const))
{
Const *carg = (Const *) arg;
--- 3252,3260 ----
BooleanTest *newbtest;
Node *arg;
! arg = const_expressions_mutator((Node *) btest->arg,
! context,
! cachable);
if (arg && IsA(arg, Const))
{
Const *carg = (Const *) arg;
*************** eval_const_expressions_mutator(Node *nod
*** 3130,3136 ****
return (Node *) newbtest;
}
case T_PlaceHolderVar:
-
/*
* In estimation mode, just strip the PlaceHolderVar node
* altogether; this amounts to estimating that the contained value
--- 3300,3305 ----
*************** eval_const_expressions_mutator(Node *nod
*** 3138,3152 ****
* just use the default behavior (ie, simplify the expression but
* leave the PlaceHolderVar node intact).
*/
if (context->estimate)
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
! return eval_const_expressions_mutator((Node *) phv->phexpr,
! context);
}
break;
default:
break;
}
--- 3307,3342 ----
* just use the default behavior (ie, simplify the expression but
* leave the PlaceHolderVar node intact).
*/
+ *cachable = false;
+
if (context->estimate)
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
+ bool isCachable = true; /* ignored */
! return const_expressions_mutator((Node *) phv->phexpr,
! context,
! &isCachable);
! }
! break;
! case T_Const:
! /* Keep *cachable=true */
! break;
! case T_CacheExpr:
! /* We already have CacheExpr in the appropriate place.
! * In estimation mode, strip CacheExpr nodes here to simplify
! * selectivity estimation functions.
! */
! *cachable = false;
! if (context->estimate)
! {
! CacheExpr *cache = (CacheExpr *) node;
! return const_expressions_mutator((Node *) cache->arg, context, cachable);
}
break;
default:
+ /* Everything else is not cachable */
+ *cachable = false;
break;
}
*************** eval_const_expressions_mutator(Node *nod
*** 3154,3163 ****
* For any node type not handled above, we recurse using
* expression_tree_mutator, which will copy the node unchanged but try to
* simplify its arguments (if any) using this routine. For example: we
! * cannot eliminate an ArrayRef node, but we might be able to simplify
! * constant expressions in its subscripts.
*/
! return expression_tree_mutator(node, eval_const_expressions_mutator,
(void *) context);
}
--- 3344,3354 ----
* For any node type not handled above, we recurse using
* expression_tree_mutator, which will copy the node unchanged but try to
* simplify its arguments (if any) using this routine. For example: we
! * cannot eliminate an ArrayRef node, but we might be able to simplify or
! * cache constant expressions in its subscripts.
*/
! return expression_tree_mutator(node,
! caching_const_expressions_mutator,
(void *) context);
}
*************** eval_const_expressions_mutator(Node *nod
*** 3179,3191 ****
* The output arguments *haveNull and *forceTrue must be initialized FALSE
* by the caller. They will be set TRUE if a null constant or true constant,
* respectively, is detected anywhere in the argument list.
*/
static List *
simplify_or_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceTrue)
{
! List *newargs = NIL;
List *unprocessed_args;
/*
--- 3370,3392 ----
* The output arguments *haveNull and *forceTrue must be initialized FALSE
* by the caller. They will be set TRUE if a null constant or true constant,
* respectively, is detected anywhere in the argument list.
+ *
+ * We divide elements into two separate lists, one for cachable items and one
+ * for non-cachable items. Upon returning, the cachable sub-list is turned
+ * into a new BoolExpr, cached and prepended. This is done in hopes that the
+ * cachable sub-list is faster to evaluate and short-cicruits the rest of the
+ * expression.
+ *
+ * Input: (cachable OR uncachable OR cachable OR uncachable)
+ * Output: (CACHE(cachable OR cachable) OR uncachable OR uncachable)
*/
static List *
simplify_or_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceTrue, bool *cachable)
{
! List *nocache_args = NIL;
! List *cachable_args = NIL;
List *unprocessed_args;
/*
*************** simplify_or_arguments(List *args,
*** 3200,3205 ****
--- 3401,3407 ----
while (unprocessed_args)
{
Node *arg = (Node *) linitial(unprocessed_args);
+ bool isCachable = true;
unprocessed_args = list_delete_first(unprocessed_args);
*************** simplify_or_arguments(List *args,
*** 3222,3228 ****
}
/* If it's not an OR, simplify it */
! arg = eval_const_expressions_mutator(arg, context);
/*
* It is unlikely but not impossible for simplification of a non-OR
--- 3424,3430 ----
}
/* If it's not an OR, simplify it */
! arg = const_expressions_mutator(arg, context, &isCachable);
/*
* It is unlikely but not impossible for simplification of a non-OR
*************** simplify_or_arguments(List *args,
*** 3264,3273 ****
}
/* else emit the simplified arg into the result list */
! newargs = lappend(newargs, arg);
}
! return newargs;
}
/*
--- 3466,3505 ----
}
/* else emit the simplified arg into the result list */
! if (isCachable)
! cachable_args = lappend(cachable_args, arg);
! else
! nocache_args = lappend(nocache_args, arg);
}
! if (cachable_args && nocache_args)
! {
! Expr *arg;
!
! /* Build a new expression for cachable sub-list */
! if (list_length(cachable_args) == 1)
! arg = linitial(cachable_args);
! else
! arg = makeBoolExpr(OR_EXPR, cachable_args, -1);
!
! arg = insert_cache(arg);
!
! /*
! * Assume that the cachable expression is cheaper to evaluate, so put
! * it first
! */
! nocache_args = lcons(arg, nocache_args);
!
! *cachable = false;
! return nocache_args;
! }
! else if (nocache_args)
! {
! *cachable = false;
! return nocache_args;
! }
! else
! return cachable_args;
}
/*
*************** simplify_or_arguments(List *args,
*** 3288,3300 ****
* The output arguments *haveNull and *forceFalse must be initialized FALSE
* by the caller. They will be set TRUE if a null constant or false constant,
* respectively, is detected anywhere in the argument list.
*/
static List *
simplify_and_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceFalse)
{
! List *newargs = NIL;
List *unprocessed_args;
/* See comments in simplify_or_arguments */
--- 3520,3542 ----
* The output arguments *haveNull and *forceFalse must be initialized FALSE
* by the caller. They will be set TRUE if a null constant or false constant,
* respectively, is detected anywhere in the argument list.
+ *
+ * We divide elements into two separate lists, one for cachable items and one
+ * for non-cachable items. Upon returning, the cachable sub-list is turned
+ * into a new BoolExpr, cached and prepended. This is done in hopes that the
+ * cachable sub-list is faster to evaluate and short-cicruits the rest of the
+ * expression.
+ *
+ * Input: (cachable OR uncachable OR cachable OR uncachable)
+ * Output: (CACHE(cachable OR cachable) OR uncachable OR uncachable)
*/
static List *
simplify_and_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceFalse, bool *cachable)
{
! List *nocache_args = NIL;
! List *cachable_args = NIL;
List *unprocessed_args;
/* See comments in simplify_or_arguments */
*************** simplify_and_arguments(List *args,
*** 3302,3307 ****
--- 3544,3550 ----
while (unprocessed_args)
{
Node *arg = (Node *) linitial(unprocessed_args);
+ bool isCachable = true;
unprocessed_args = list_delete_first(unprocessed_args);
*************** simplify_and_arguments(List *args,
*** 3324,3330 ****
}
/* If it's not an AND, simplify it */
! arg = eval_const_expressions_mutator(arg, context);
/*
* It is unlikely but not impossible for simplification of a non-AND
--- 3567,3573 ----
}
/* If it's not an AND, simplify it */
! arg = const_expressions_mutator(arg, context, &isCachable);
/*
* It is unlikely but not impossible for simplification of a non-AND
*************** simplify_and_arguments(List *args,
*** 3366,3375 ****
}
/* else emit the simplified arg into the result list */
! newargs = lappend(newargs, arg);
}
! return newargs;
}
/*
--- 3609,3648 ----
}
/* else emit the simplified arg into the result list */
! if (isCachable)
! cachable_args = lappend(cachable_args, arg);
! else
! nocache_args = lappend(nocache_args, arg);
}
! if (cachable_args && nocache_args)
! {
! Expr *arg;
!
! /* Build a new expression for cachable sub-list */
! if (list_length(cachable_args) == 1)
! arg = linitial(cachable_args);
! else
! arg = makeBoolExpr(AND_EXPR, cachable_args, -1);
!
! arg = insert_cache(arg);
!
! /*
! * Assume that the cachable expression is cheaper to evaluate, so put
! * it first
! */
! nocache_args = lcons(arg, nocache_args);
!
! *cachable = false;
! return nocache_args;
! }
! else if (nocache_args)
! {
! *cachable = false;
! return nocache_args;
! }
! else
! return cachable_args;
}
/*
*************** simplify_boolean_equality(Oid opno, List
*** 3444,3450 ****
* Inputs are the original expression (can be NULL), function OID, actual
* result type OID (which is needed for polymorphic functions), result typmod,
* result collation, the input collation to use for the function, the
! * pre-simplified argument list, and some flags; also the context data for
* eval_const_expressions. In common cases, several of the arguments could be
* derived from the original expression. Sending them separately avoids
* duplicating NodeTag-specific knowledge, and it's necessary for CoerceViaIO.
--- 3717,3723 ----
* Inputs are the original expression (can be NULL), function OID, actual
* result type OID (which is needed for polymorphic functions), result typmod,
* result collation, the input collation to use for the function, the
! * un-simplified argument list, and some flags; also the context data for
* eval_const_expressions. In common cases, several of the arguments could be
* derived from the original expression. Sending them separately avoids
* duplicating NodeTag-specific knowledge, and it's necessary for CoerceViaIO.
*************** simplify_boolean_equality(Oid opno, List
*** 3463,3476 ****
static Expr *
simplify_function(Expr *oldexpr, Oid funcid,
Oid result_type, int32 result_typmod, Oid result_collid,
! Oid input_collid, List **args,
! bool has_named_args,
bool allow_inline,
! eval_const_expressions_context *context)
{
HeapTuple func_tuple;
Expr *newexpr;
Oid transform;
/*
* We have three strategies for simplification: execute the function to
--- 3736,3752 ----
static Expr *
simplify_function(Expr *oldexpr, Oid funcid,
Oid result_type, int32 result_typmod, Oid result_collid,
! Oid input_collid, List **old_args,
bool allow_inline,
! eval_const_expressions_context *context,
! bool *cachable)
{
HeapTuple func_tuple;
Expr *newexpr;
Oid transform;
+ ListCell *lc;
+ List *args = NIL;
+ List *cachable_args = NIL;
/*
* We have three strategies for simplification: execute the function to
*************** simplify_function(Expr *oldexpr, Oid fun
*** 3484,3502 ****
if (!HeapTupleIsValid(func_tuple))
elog(ERROR, "cache lookup failed for function %u", funcid);
/*
! * While we have the tuple, reorder named arguments and add default
! * arguments if needed.
*/
- if (has_named_args)
- *args = reorder_function_arguments(*args, result_type, func_tuple,
- context);
- else if (((Form_pg_proc) GETSTRUCT(func_tuple))->pronargs > list_length(*args))
- *args = add_function_defaults(*args, result_type, func_tuple, context);
-
newexpr = evaluate_function(funcid, result_type, result_typmod,
! result_collid, input_collid, *args,
! func_tuple, context);
/*
* Some functions calls can be simplified at plan time based on properties
--- 3760,3812 ----
if (!HeapTupleIsValid(func_tuple))
elog(ERROR, "cache lookup failed for function %u", funcid);
+ if (oldexpr && IsA(oldexpr, FuncExpr))
+
+ /*
+ * Reorder named arguments and add defaults if needed. Returns a
+ * copied list, so we can mutate it later.
+ */
+ args = simplify_copy_function_arguments(*old_args, result_type, func_tuple);
+ else
+ /* Copy argument list before we start mutating it */
+ args = list_copy(*old_args);
+
+ /* Reduce constants in the FuncExpr's arguments */
+ foreach(lc, args)
+ {
+ Node *arg = (Node *) lfirst(lc);
+ bool isCachable = true;
+
+ arg = const_expressions_mutator(arg, context, &isCachable);
+ lfirst(lc) = arg;
+
+ /*
+ * We're stuck in a catch-22 here. If all arguments and the call
+ * itself is cachable, we don't want to insert cache nodes for
+ * arguments. But we don't know that until we walk through all the
+ * arguments.
+ *
+ * So we accumulate cachable arguments in a list of ListCell pointers,
+ * which we will update later if necessary.
+ *
+ * Note: The args list may not be mutated from here on this until we
+ * handle cachable_args below.
+ */
+ if (isCachable)
+ {
+ if (is_cache_useful((Expr *) arg))
+ cachable_args = lappend(cachable_args, &lfirst(lc));
+ }
+ else
+ *cachable = false; /* One bad arg spoils the whole cache */
+ }
+
/*
! * evaluate_function tells us about the cachability of the function call
*/
newexpr = evaluate_function(funcid, result_type, result_typmod,
! result_collid, input_collid, args,
! func_tuple, context, cachable);
/*
* Some functions calls can be simplified at plan time based on properties
*************** simplify_function(Expr *oldexpr, Oid fun
*** 3533,3562 ****
PointerGetDatum(oldexpr)));
if (!newexpr && allow_inline)
newexpr = inline_function(funcid, result_type, result_collid,
! input_collid, *args,
! func_tuple, context);
ReleaseSysCache(func_tuple);
return newexpr;
}
/*
* reorder_function_arguments: convert named-notation args to positional args
*
* This function also inserts default argument values as needed, since it's
* impossible to form a truly valid positional call without that.
*/
static List *
! reorder_function_arguments(List *args, Oid result_type, HeapTuple func_tuple,
! eval_const_expressions_context *context)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
int pronargs = funcform->pronargs;
int nargsprovided = list_length(args);
Node *argarray[FUNC_MAX_ARGS];
- Bitmapset *defargnumbers;
ListCell *lc;
int i;
--- 3843,3953 ----
PointerGetDatum(oldexpr)));
if (!newexpr && allow_inline)
+ {
+ /*
+ * The inlined expression may be cachable regardless of the above, if
+ * the function's volatility was mis-labeled or if volatile parts are
+ * removed (possible due to constant folding of conditionals).
+ *
+ * inline_function() also takes care of caching all cachable subtrees
+ */
+ bool isCachable = true;
+
newexpr = inline_function(funcid, result_type, result_collid,
! input_collid, args,
! func_tuple, context, &isCachable);
!
! if (newexpr)
! *cachable = isCachable;
! }
ReleaseSysCache(func_tuple);
+ /*
+ * If function call can't be cached/inlined, update all cachable arguments
+ */
+ if (!newexpr && !(*cachable))
+ {
+ foreach(lc, cachable_args)
+ {
+ Node **arg = (Node **) lfirst(lc);
+
+ *arg = (Node *) makeCacheExpr((Expr *) *arg);
+ }
+ }
+
+ /* Argument processing done, give it back to the caller */
+ *old_args = args;
+
return newexpr;
}
/*
+ * This function prepares a function's argument list -- converting
+ * named-notation argument list into positional notation while adding any
+ * needed default argument expressions.
+ *
+ * Always returns a copy of the argument list, the original list is not
+ * modified.
+ */
+ static List *
+ simplify_copy_function_arguments(List *old_args, Oid result_type,
+ HeapTuple func_tuple)
+ {
+ List *args = NIL;
+ ListCell *lc;
+ bool has_named_args = false;
+ int nargs_before;
+
+ /* Do we need to reorder named arguments? */
+ foreach(lc, old_args)
+ {
+ Node *arg = (Node *) lfirst(lc);
+
+ if (IsA(arg, NamedArgExpr))
+ {
+ has_named_args = true;
+ break;
+ }
+ }
+
+ nargs_before = list_length(old_args);
+
+ /*
+ * Reorder named arguments and add default arguments if needed.
+ */
+ if (has_named_args)
+ args = reorder_function_arguments(old_args, result_type, func_tuple);
+
+ else
+ {
+ args = list_copy(old_args);
+
+ /* Append missing default arguments to the list */
+ if (((Form_pg_proc) GETSTRUCT(func_tuple))->pronargs > list_length(args))
+ args = add_function_defaults(args, result_type, func_tuple);
+ }
+
+ if (list_length(args) != nargs_before)
+ /* Added defaults may need casts */
+ recheck_cast_function_args(args, result_type, func_tuple);
+
+ return args;
+ }
+
+ /*
* reorder_function_arguments: convert named-notation args to positional args
*
* This function also inserts default argument values as needed, since it's
* impossible to form a truly valid positional call without that.
*/
static List *
! reorder_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
int pronargs = funcform->pronargs;
int nargsprovided = list_length(args);
Node *argarray[FUNC_MAX_ARGS];
ListCell *lc;
int i;
*************** reorder_function_arguments(List *args, O
*** 3590,3596 ****
* Fetch default expressions, if needed, and insert into array at proper
* locations (they aren't necessarily consecutive or all used)
*/
- defargnumbers = NULL;
if (nargsprovided < pronargs)
{
List *defaults = fetch_function_defaults(func_tuple);
--- 3981,3986 ----
*************** reorder_function_arguments(List *args, O
*** 3599,3608 ****
foreach(lc, defaults)
{
if (argarray[i] == NULL)
- {
argarray[i] = (Node *) lfirst(lc);
- defargnumbers = bms_add_member(defargnumbers, i);
- }
i++;
}
}
--- 3989,3995 ----
*************** reorder_function_arguments(List *args, O
*** 3615,3646 ****
args = lappend(args, argarray[i]);
}
- /* Recheck argument types and add casts if needed */
- recheck_cast_function_args(args, result_type, func_tuple);
-
- /*
- * Lastly, we have to recursively simplify the defaults we just added (but
- * don't recurse on the args passed in, as we already did those). This
- * isn't merely an optimization, it's *necessary* since there could be
- * functions with named or defaulted arguments down in there.
- *
- * Note that we do this last in hopes of simplifying any typecasts that
- * were added by recheck_cast_function_args --- there shouldn't be any new
- * casts added to the explicit arguments, but casts on the defaults are
- * possible.
- */
- if (defargnumbers != NULL)
- {
- i = 0;
- foreach(lc, args)
- {
- if (bms_is_member(i, defargnumbers))
- lfirst(lc) = eval_const_expressions_mutator((Node *) lfirst(lc),
- context);
- i++;
- }
- }
-
return args;
}
--- 4002,4007 ----
*************** reorder_function_arguments(List *args, O
*** 3651,3670 ****
* and so we know we just need to add defaults at the end.
*/
static List *
! add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple,
! eval_const_expressions_context *context)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
- int nargsprovided = list_length(args);
List *defaults;
int ndelete;
- ListCell *lc;
/* Get all the default expressions from the pg_proc tuple */
defaults = fetch_function_defaults(func_tuple);
/* Delete any unused defaults from the list */
! ndelete = nargsprovided + list_length(defaults) - funcform->pronargs;
if (ndelete < 0)
elog(ERROR, "not enough default arguments");
while (ndelete-- > 0)
--- 4012,4028 ----
* and so we know we just need to add defaults at the end.
*/
static List *
! add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
List *defaults;
int ndelete;
/* Get all the default expressions from the pg_proc tuple */
defaults = fetch_function_defaults(func_tuple);
/* Delete any unused defaults from the list */
! ndelete = list_length(args) + list_length(defaults) - funcform->pronargs;
if (ndelete < 0)
elog(ERROR, "not enough default arguments");
while (ndelete-- > 0)
*************** add_function_defaults(List *args, Oid re
*** 3673,3700 ****
/* And form the combined argument list */
args = list_concat(args, defaults);
- /* Recheck argument types and add casts if needed */
- recheck_cast_function_args(args, result_type, func_tuple);
-
- /*
- * Lastly, we have to recursively simplify the defaults we just added (but
- * don't recurse on the args passed in, as we already did those). This
- * isn't merely an optimization, it's *necessary* since there could be
- * functions with named or defaulted arguments down in there.
- *
- * Note that we do this last in hopes of simplifying any typecasts that
- * were added by recheck_cast_function_args --- there shouldn't be any new
- * casts added to the explicit arguments, but casts on the defaults are
- * possible.
- */
- foreach(lc, args)
- {
- if (nargsprovided-- > 0)
- continue; /* skip original arg positions */
- lfirst(lc) = eval_const_expressions_mutator((Node *) lfirst(lc),
- context);
- }
-
return args;
}
--- 4031,4036 ----
*************** static Expr *
*** 3784,3790 ****
evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
bool has_nonconst_input = false;
--- 4120,4127 ----
evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context,
! bool *cachable)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
bool has_nonconst_input = false;
*************** evaluate_function(Oid funcid, Oid result
*** 3796,3802 ****
--- 4133,4142 ----
* Can't simplify if it returns a set.
*/
if (funcform->proretset)
+ {
+ *cachable = false;
return NULL;
+ }
/*
* Can't simplify if it returns RECORD. The immediate problem is that it
*************** evaluate_function(Oid funcid, Oid result
*** 3810,3816 ****
--- 4150,4162 ----
* gotchas, seems best to leave the function call unreduced.
*/
if (funcform->prorettype == RECORDOID)
+ {
+ *cachable = false;
return NULL;
+ }
+
+ if (funcform->provolatile == PROVOLATILE_VOLATILE)
+ *cachable = false;
/*
* Check for constant inputs and especially constant-NULL inputs.
*************** static Expr *
*** 3907,3913 ****
inline_function(Oid funcid, Oid result_type, Oid result_collid,
Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
char *src;
--- 4253,4260 ----
inline_function(Oid funcid, Oid result_type, Oid result_collid,
Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context,
! bool *cachable)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
char *src;
*************** inline_function(Oid funcid, Oid result_t
*** 4186,4192 ****
* the current function to the context list of active functions.
*/
context->active_fns = lcons_oid(funcid, context->active_fns);
! newexpr = eval_const_expressions_mutator(newexpr, context);
context->active_fns = list_delete_first(context->active_fns);
error_context_stack = sqlerrcontext.previous;
--- 4533,4539 ----
* the current function to the context list of active functions.
*/
context->active_fns = lcons_oid(funcid, context->active_fns);
! newexpr = const_expressions_mutator(newexpr, context, cachable);
context->active_fns = list_delete_first(context->active_fns);
error_context_stack = sqlerrcontext.previous;
*************** sql_inline_error_callback(void *arg)
*** 4266,4271 ****
--- 4613,4646 ----
}
/*
+ * Is it useful to cache this expression? Constants and param references are
+ * always fast to access so don't insert cache in front of those.
+ *
+ * Without inline, we lose almost 10% time in some very simple queries (!)
+ */
+ static inline bool
+ is_cache_useful(Expr *expr)
+ {
+ if (IsA(expr, Const))
+ return false;
+ if (IsA(expr, Param))
+ return false;
+ return true;
+ }
+
+ static Expr *
+ insert_cache(Expr *expr)
+ {
+ /* Don't cache obviously cheap expressions */
+ if (!is_cache_useful(expr))
+ return expr;
+
+ Assert(!IsA(expr, CacheExpr));
+
+ return (Expr *) makeCacheExpr(expr);
+ }
+
+ /*
* evaluate_expr: pre-evaluate a constant expression
*
* We use the executor's routine ExecEvalExpr() to avoid duplication of
*************** evaluate_expr(Expr *expr, Oid result_typ
*** 4298,4304 ****
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
! exprstate = ExecInitExpr(expr, NULL);
/*
* And evaluate it.
--- 4673,4679 ----
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
! exprstate = ExecInitExpr(expr, NULL, false);
/*
* And evaluate it.
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
new file mode 100644
index c3161c5..f98854d
*** a/src/backend/optimizer/util/predtest.c
--- b/src/backend/optimizer/util/predtest.c
*************** btree_predicate_proof(Expr *predicate, N
*** 1486,1492 ****
fix_opfuncids((Node *) test_expr);
/* Prepare it for execution */
! test_exprstate = ExecInitExpr(test_expr, NULL);
/* And execute it. */
test_result = ExecEvalExprSwitchContext(test_exprstate,
--- 1486,1492 ----
fix_opfuncids((Node *) test_expr);
/* Prepare it for execution */
! test_exprstate = ExecInitExpr(test_expr, NULL, false);
/* And execute it. */
test_result = ExecEvalExprSwitchContext(test_exprstate,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
new file mode 100644
index 9ad54c5..d7b6ea2
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** isSimpleNode(Node *node, Node *parentNod
*** 4612,4619 ****
/* function-like: name(..) or name[..] */
return true;
! /* CASE keywords act as parentheses */
case T_CaseExpr:
return true;
case T_FieldSelect:
--- 4612,4623 ----
/* function-like: name(..) or name[..] */
return true;
! case T_CacheExpr:
! /* hidden from user */
! return true;
!
case T_CaseExpr:
+ /* CASE keywords act as parentheses */
return true;
case T_FieldSelect:
*************** get_rule_expr(Node *node, deparse_contex
*** 5734,5739 ****
--- 5738,5757 ----
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+
+ #ifdef DEBUG_CACHEEXPR
+ appendStringInfo(buf, "CACHE[");
+ get_rule_expr((Node *) cache->arg, context, true);
+ appendStringInfoChar(buf, ']');
+ #else
+ get_rule_expr((Node *) cache->arg, context, true);
+ #endif
+ }
+ break;
+
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
new file mode 100644
index 9a74541..56a4bce
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
*************** extern Tuplestorestate *ExecMakeTableFun
*** 227,233 ****
bool randomAccess);
extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
! extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull);
extern int ExecTargetListLength(List *targetlist);
--- 227,233 ----
bool randomAccess);
extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
! extern ExprState *ExecInitExpr(Expr *node, PlanState *parent, bool cacheEnabled);
extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull);
extern int ExecTargetListLength(List *targetlist);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
new file mode 100644
index 5207102..a4701f2
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct FuncExprState
*** 674,679 ****
--- 674,696 ----
} FuncExprState;
/* ----------------
+ * CacheExprState node
+ *
+ * Takes care of caching execution-time constants that cannot be constant
+ * folded at plan-time.
+ * ----------------
+ */
+ typedef struct CacheExprState
+ {
+ ExprState xprstate;
+ ExprState *arg; /* state of sub-expression */
+
+ bool enabled; /* is cache enabled? */
+ Datum result; /* cached result */
+ bool isNull; /* is result NULL? */
+ } CacheExprState;
+
+ /* ----------------
* ScalarArrayOpExprState node
*
* This is a FuncExprState plus some additional data.
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
new file mode 100644
index a6dffe6..fa5d8e7
*** a/src/include/nodes/makefuncs.h
--- b/src/include/nodes/makefuncs.h
*************** extern TypeName *makeTypeNameFromOid(Oid
*** 74,79 ****
--- 74,80 ----
extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
Oid funccollid, Oid inputcollid, CoercionForm fformat);
+ extern CacheExpr *makeCacheExpr(Expr *arg);
extern DefElem *makeDefElem(char *name, Node *arg);
extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index 0e7d184..016a63c
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 172,177 ****
--- 172,178 ----
T_JoinExpr,
T_FromExpr,
T_IntoClause,
+ T_CacheExpr,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
*************** typedef enum NodeTag
*** 205,210 ****
--- 206,212 ----
T_NullTestState,
T_CoerceToDomainState,
T_DomainConstraintState,
+ T_CacheExprState,
/*
* TAGS FOR PLANNER NODES (relation.h)
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
new file mode 100644
index 261e7a0..56e1db5
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct BooleanTest
*** 1029,1034 ****
--- 1029,1049 ----
} BooleanTest;
/*
+ * CacheExpr
+ *
+ * CacheExpr is a constant expression to be cached at execution time. The
+ * eval_const_expressions() function inserts CacheExpr nodes nodes at
+ * strategic locations when it recognizes constant expressions that cannot be
+ * constant-folded at plan time, such as expressions with Param references,
+ * stable function and operator calls with constant arguments, etc
+ */
+ typedef struct CacheExpr
+ {
+ Expr xpr;
+ Expr *arg;
+ } CacheExpr;
+
+ /*
* CoerceToDomain
*
* CoerceToDomain represents the operation of coercing a value to a domain
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
new file mode 100644
index bf952b6..faa6408
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 4935,4941 ****
if (expr->expr_simple_lxid != curlxid)
{
oldcontext = MemoryContextSwitchTo(simple_eval_estate->es_query_cxt);
! expr->expr_simple_state = ExecInitExpr(expr->expr_simple_expr, NULL);
expr->expr_simple_in_use = false;
expr->expr_simple_lxid = curlxid;
MemoryContextSwitchTo(oldcontext);
--- 4935,4943 ----
if (expr->expr_simple_lxid != curlxid)
{
oldcontext = MemoryContextSwitchTo(simple_eval_estate->es_query_cxt);
! expr->expr_simple_state = ExecInitExpr(expr->expr_simple_expr,
! NULL,
! false);
expr->expr_simple_in_use = false;
expr->expr_simple_lxid = curlxid;
MemoryContextSwitchTo(oldcontext);
*************** exec_simple_check_node(Node *node)
*** 5446,5451 ****
--- 5448,5458 ----
return TRUE;
case T_Param:
+ /*
+ * If we have other kinds of params here, then earlier tests
+ * should have ruled out this as simple expression
+ */
+ Assert(((Param *) node)->paramkind == PARAM_EXTERN);
return TRUE;
case T_ArrayRef:
*************** exec_simple_check_node(Node *node)
*** 5678,5683 ****
--- 5685,5694 ----
return TRUE;
}
+ case T_CacheExpr:
+ /* Caching is disabled for simple expressions */
+ return TRUE;
+
default:
return FALSE;
}
diff --git a/src/test/regress/expected/cache.out b/src/test/regress/expected/cache.out
new file mode 100644
index ...592263d
*** a/src/test/regress/expected/cache.out
--- b/src/test/regress/expected/cache.out
***************
*** 0 ****
--- 1,685 ----
+ --
+ -- Test cachable expressions
+ --
+ -- If the NOTICE outputs of these functions change, you've probably broken
+ -- something with the CacheExpr optimization
+ --
+ create function stable_true() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE TRUE'; return true; end$$;
+ create function volatile_true() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE TRUE'; return true; end$$;
+ create function stable_false() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE FALSE'; return false; end$$;
+ create function volatile_false() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE FALSE'; return false; end$$;
+ -- Table with two rows
+ create table two (i int);
+ insert into two values (1), (2);
+ -- Boolean expressions
+ select stable_false() or volatile_true() or stable_true() as b from two;
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select stable_true() or volatile_false() or stable_false() as b from two;
+ NOTICE: STABLE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select stable_false() or volatile_true() as b from two;
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select stable_false() or stable_false() or volatile_true() as b from two;
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select volatile_true() or volatile_false() or stable_false() as b from two;
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select volatile_false() or volatile_true() or stable_false() as b from two;
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select stable_true() and volatile_false() and stable_false() as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select stable_false() and volatile_true() and stable_true() as b from two;
+ NOTICE: STABLE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select stable_true() and volatile_false() as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select stable_true() and stable_true() and volatile_false() as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select volatile_true() and volatile_false() and stable_true() as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select volatile_false() and volatile_true() and stable_true() as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select not stable_true() as b from two;
+ NOTICE: STABLE TRUE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select not volatile_true() as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ -- Bind params
+ prepare param_test(bool) as select $1 or stable_false() or volatile_true() as b from two;
+ execute param_test(true);
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ execute param_test(false);
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- Function calls
+ create function stable(bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%)', $1; return $1; end$$;
+ create function volatile(bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE(%)', $1; return $1; end$$;
+ select volatile(volatile_true()) from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE(t)
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE(t)
+ volatile
+ ----------
+ t
+ t
+ (2 rows)
+
+ select stable(stable_true()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE(t)
+ stable
+ --------
+ t
+ t
+ (2 rows)
+
+ select stable(volatile_true()) from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(t)
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(t)
+ stable
+ --------
+ t
+ t
+ (2 rows)
+
+ select volatile(stable_true()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE(t)
+ NOTICE: VOLATILE(t)
+ volatile
+ ----------
+ t
+ t
+ (2 rows)
+
+ create function stable(bool, bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+ create function volatile(bool, bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE(%, %)', $1, $2; return $1; end$$;
+ select stable(volatile_true(), volatile_false()) from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE(t, f)
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE(t, f)
+ stable
+ --------
+ t
+ t
+ (2 rows)
+
+ select stable(stable_true(), volatile_false()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE(t, f)
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE(t, f)
+ stable
+ --------
+ t
+ t
+ (2 rows)
+
+ select stable(stable_true(), stable_false()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE(t, f)
+ stable
+ --------
+ t
+ t
+ (2 rows)
+
+ select volatile(volatile_true(), volatile_false()) from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE(t, f)
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE(t, f)
+ volatile
+ ----------
+ t
+ t
+ (2 rows)
+
+ select volatile(stable_true(), volatile_false()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE(t, f)
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE(t, f)
+ volatile
+ ----------
+ t
+ t
+ (2 rows)
+
+ select volatile(stable_true(), stable_false()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE(t, f)
+ NOTICE: VOLATILE(t, f)
+ volatile
+ ----------
+ t
+ t
+ (2 rows)
+
+ -- Default arguments
+ create function stable_def(a bool = stable_false(), b bool = volatile_true())
+ returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+ select stable_def() from two;
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(f, t)
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(f, t)
+ stable_def
+ ------------
+ f
+ f
+ (2 rows)
+
+ select stable_def(b := stable_true()) from two;
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE(f, t)
+ stable_def
+ ------------
+ f
+ f
+ (2 rows)
+
+ select stable_def(volatile_false()) from two;
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(f, t)
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(f, t)
+ stable_def
+ ------------
+ f
+ f
+ (2 rows)
+
+ -- Operators
+ create function stable_eq(bool, bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE % == %', $1, $2; return $1 = $2; end$$;
+ create function volatile_eq(bool, bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE % =%%= %', $1, $2; return $1 = $2; end$$;
+ create operator == (procedure = stable_eq, leftarg=bool, rightarg=bool);
+ create operator =%= (procedure = volatile_eq, leftarg=bool, rightarg=bool);
+ select volatile_true() == volatile_false() from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE t == f
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE t == f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select stable_true() == volatile_false() from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE t == f
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE t == f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select stable_true() == stable_false() from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE t == f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select volatile_true() =%= volatile_false() from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE t =%= f
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE t =%= f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select stable_true() =%= volatile_false() from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE t =%= f
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE t =%= f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select stable_true() =%= stable_false() from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE t =%= f
+ NOTICE: VOLATILE t =%= f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select (volatile_true() or stable_true()) == true as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE t == t
+ NOTICE: STABLE t == t
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- Coalesce
+ create function stable_null() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE NULL'; return null; end$$;
+ create function volatile_null() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE NULL'; return null; end$$;
+ select coalesce(stable_null(), stable_true()) from two;
+ NOTICE: STABLE NULL
+ NOTICE: STABLE TRUE
+ coalesce
+ ----------
+ t
+ t
+ (2 rows)
+
+ select coalesce(stable_true(), volatile_null()) from two;
+ NOTICE: STABLE TRUE
+ coalesce
+ ----------
+ t
+ t
+ (2 rows)
+
+ select coalesce(volatile_null(), stable_null(), volatile_true()) from two;
+ NOTICE: VOLATILE NULL
+ NOTICE: STABLE NULL
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE NULL
+ NOTICE: VOLATILE TRUE
+ coalesce
+ ----------
+ t
+ t
+ (2 rows)
+
+ -- Case/when
+ select case when stable_true() then 't' else volatile_false() end as b from two;
+ NOTICE: STABLE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select case when volatile_true() then stable_true() else stable_false() end as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select case when i=1 then stable_true() else stable_false() end as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ b
+ ---
+ t
+ f
+ (2 rows)
+
+ select case when i=1 then volatile_true() else volatile_false() end as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ t
+ f
+ (2 rows)
+
+ select case when 't' then 't' else volatile_false() end == true as b from two;
+ NOTICE: STABLE t == t
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- Coerce via I/O
+ select stable_true()::text::bool == true as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE t == t
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select volatile_true()::text::bool == true as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE t == t
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE t == t
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- IS DISTINCT FROM
+ select (stable_true() is not distinct from volatile_false()) as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select (stable_true() is distinct from stable_false()) == false as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE t == f
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select (volatile_true() is distinct from null) as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- IS NULL
+ select volatile_true() is null == false as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE f == f
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE f == f
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select stable_null() is not null == true as b from two;
+ NOTICE: STABLE NULL
+ NOTICE: STABLE f == t
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ -- Boolean tests
+ select volatile_false() is true == true as b from two;
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE f == t
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE f == t
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select stable_null() is not unknown == false as b from two;
+ NOTICE: STABLE NULL
+ NOTICE: STABLE f == f
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- Field select -- not currently cached
+ create function stable_row(a out int, b out int) STABLE language plpgsql as
+ $$begin raise notice 'STABLE ROW'; a = 1; b = 2; end$$;
+ select (stable_row()).a from two;
+ NOTICE: STABLE ROW
+ NOTICE: STABLE ROW
+ a
+ ---
+ 1
+ 1
+ (2 rows)
+
+ -- WHERE clause
+ begin;
+ -- stable_true is evaluated twice due to planning estimates
+ declare stable_where cursor for select * from two where i > stable_true()::int;
+ NOTICE: STABLE TRUE
+ fetch all from stable_where;
+ NOTICE: STABLE TRUE
+ i
+ ---
+ 2
+ (1 row)
+
+ declare volatile_where cursor for select * from two where i = volatile_false()::int;
+ fetch all from volatile_where;
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ i
+ ---
+ (0 rows)
+
+ rollback;
+ -- INSERT column default expressions
+ create table defaults (
+ dummy int,
+ a bool default stable_true(),
+ b bool default volatile_true()
+ );
+ insert into defaults (dummy) values(0), (1);
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ -- ALTER COLUMN TYPE USING
+ alter table defaults alter column a type bool using stable_false();
+ NOTICE: STABLE FALSE
+ alter table defaults alter column a type bool using volatile_false();
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ -- COPY FROM with default expressions
+ copy defaults (dummy) from stdin;
+ NOTICE: STABLE TRUE
+ CONTEXT: COPY defaults, line 1: "2"
+ NOTICE: VOLATILE TRUE
+ CONTEXT: COPY defaults, line 1: "2"
+ NOTICE: VOLATILE TRUE
+ CONTEXT: COPY defaults, line 2: "3"
+ -- VALUES list expressions
+ -- The fact that there are be 3 lines of 'VOLATILE TRUE' output is a quirk of
+ -- the current set-returning function execution code
+ insert into defaults (dummy, a, b)
+ values (generate_series(4, 5), stable_true(), volatile_true());
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ -- PL/pgSQL Simple expressions
+ -- Make sure we don't cache simple expressions -- these expressions are only
+ -- initialized once per transaction and then executed multiple times
+ create function stable_max() returns int STABLE language plpgsql as
+ $$begin return (select max(i) from two); end$$;
+ create function simple() returns int STABLE language plpgsql as
+ $$begin return stable_max(); end$$;
+ begin;
+ select simple();
+ simple
+ --------
+ 2
+ (1 row)
+
+ insert into two values(3);
+ select simple();
+ simple
+ --------
+ 3
+ (1 row)
+
+ rollback;
+ -- The end
+ drop table defaults;
+ drop table two;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
new file mode 100644
index d6105f8..13deb89
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
*************** test: select_into select_distinct select
*** 83,89 ****
# ----------
# Another group of parallel tests
# ----------
! test: privileges security_label collate
test: misc
# rules cannot run concurrently with any test that creates a view
--- 83,89 ----
# ----------
# Another group of parallel tests
# ----------
! test: privileges security_label collate cache
test: misc
# rules cannot run concurrently with any test that creates a view
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
new file mode 100644
index 90726ce..8ae4ac9
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
*************** test: prepared_xacts
*** 92,97 ****
--- 92,98 ----
test: privileges
test: security_label
test: collate
+ test: cache
test: misc
test: rules
test: select_views
diff --git a/src/test/regress/sql/cache.sql b/src/test/regress/sql/cache.sql
new file mode 100644
index ...9b68670
*** a/src/test/regress/sql/cache.sql
--- b/src/test/regress/sql/cache.sql
***************
*** 0 ****
--- 1,185 ----
+ --
+ -- Test cachable expressions
+ --
+ -- If the NOTICE outputs of these functions change, you've probably broken
+ -- something with the CacheExpr optimization
+ --
+
+ create function stable_true() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE TRUE'; return true; end$$;
+ create function volatile_true() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE TRUE'; return true; end$$;
+ create function stable_false() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE FALSE'; return false; end$$;
+ create function volatile_false() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE FALSE'; return false; end$$;
+
+ -- Table with two rows
+ create table two (i int);
+ insert into two values (1), (2);
+
+ -- Boolean expressions
+ select stable_false() or volatile_true() or stable_true() as b from two;
+ select stable_true() or volatile_false() or stable_false() as b from two;
+ select stable_false() or volatile_true() as b from two;
+ select stable_false() or stable_false() or volatile_true() as b from two;
+ select volatile_true() or volatile_false() or stable_false() as b from two;
+ select volatile_false() or volatile_true() or stable_false() as b from two;
+
+ select stable_true() and volatile_false() and stable_false() as b from two;
+ select stable_false() and volatile_true() and stable_true() as b from two;
+ select stable_true() and volatile_false() as b from two;
+ select stable_true() and stable_true() and volatile_false() as b from two;
+ select volatile_true() and volatile_false() and stable_true() as b from two;
+ select volatile_false() and volatile_true() and stable_true() as b from two;
+
+ select not stable_true() as b from two;
+ select not volatile_true() as b from two;
+
+ -- Bind params
+ prepare param_test(bool) as select $1 or stable_false() or volatile_true() as b from two;
+ execute param_test(true);
+ execute param_test(false);
+
+ -- Function calls
+ create function stable(bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%)', $1; return $1; end$$;
+ create function volatile(bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE(%)', $1; return $1; end$$;
+
+ select volatile(volatile_true()) from two;
+ select stable(stable_true()) from two;
+ select stable(volatile_true()) from two;
+ select volatile(stable_true()) from two;
+
+ create function stable(bool, bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+ create function volatile(bool, bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE(%, %)', $1, $2; return $1; end$$;
+
+ select stable(volatile_true(), volatile_false()) from two;
+ select stable(stable_true(), volatile_false()) from two;
+ select stable(stable_true(), stable_false()) from two;
+ select volatile(volatile_true(), volatile_false()) from two;
+ select volatile(stable_true(), volatile_false()) from two;
+ select volatile(stable_true(), stable_false()) from two;
+
+ -- Default arguments
+ create function stable_def(a bool = stable_false(), b bool = volatile_true())
+ returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+
+ select stable_def() from two;
+ select stable_def(b := stable_true()) from two;
+ select stable_def(volatile_false()) from two;
+
+ -- Operators
+ create function stable_eq(bool, bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE % == %', $1, $2; return $1 = $2; end$$;
+ create function volatile_eq(bool, bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE % =%%= %', $1, $2; return $1 = $2; end$$;
+
+ create operator == (procedure = stable_eq, leftarg=bool, rightarg=bool);
+ create operator =%= (procedure = volatile_eq, leftarg=bool, rightarg=bool);
+
+ select volatile_true() == volatile_false() from two;
+ select stable_true() == volatile_false() from two;
+ select stable_true() == stable_false() from two;
+ select volatile_true() =%= volatile_false() from two;
+ select stable_true() =%= volatile_false() from two;
+ select stable_true() =%= stable_false() from two;
+
+ select (volatile_true() or stable_true()) == true as b from two;
+
+ -- Coalesce
+ create function stable_null() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE NULL'; return null; end$$;
+ create function volatile_null() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE NULL'; return null; end$$;
+
+ select coalesce(stable_null(), stable_true()) from two;
+ select coalesce(stable_true(), volatile_null()) from two;
+ select coalesce(volatile_null(), stable_null(), volatile_true()) from two;
+
+ -- Case/when
+ select case when stable_true() then 't' else volatile_false() end as b from two;
+ select case when volatile_true() then stable_true() else stable_false() end as b from two;
+ select case when i=1 then stable_true() else stable_false() end as b from two;
+ select case when i=1 then volatile_true() else volatile_false() end as b from two;
+
+ select case when 't' then 't' else volatile_false() end == true as b from two;
+
+ -- Coerce via I/O
+ select stable_true()::text::bool == true as b from two;
+ select volatile_true()::text::bool == true as b from two;
+
+ -- IS DISTINCT FROM
+ select (stable_true() is not distinct from volatile_false()) as b from two;
+ select (stable_true() is distinct from stable_false()) == false as b from two;
+ select (volatile_true() is distinct from null) as b from two;
+
+ -- IS NULL
+ select volatile_true() is null == false as b from two;
+ select stable_null() is not null == true as b from two;
+
+ -- Boolean tests
+ select volatile_false() is true == true as b from two;
+ select stable_null() is not unknown == false as b from two;
+
+ -- Field select -- not currently cached
+ create function stable_row(a out int, b out int) STABLE language plpgsql as
+ $$begin raise notice 'STABLE ROW'; a = 1; b = 2; end$$;
+
+ select (stable_row()).a from two;
+
+ -- WHERE clause
+ begin;
+ -- stable_true is evaluated twice due to planning estimates
+ declare stable_where cursor for select * from two where i > stable_true()::int;
+ fetch all from stable_where;
+ declare volatile_where cursor for select * from two where i = volatile_false()::int;
+ fetch all from volatile_where;
+ rollback;
+
+ -- INSERT column default expressions
+ create table defaults (
+ dummy int,
+ a bool default stable_true(),
+ b bool default volatile_true()
+ );
+ insert into defaults (dummy) values(0), (1);
+
+ -- ALTER COLUMN TYPE USING
+ alter table defaults alter column a type bool using stable_false();
+ alter table defaults alter column a type bool using volatile_false();
+
+ -- COPY FROM with default expressions
+ copy defaults (dummy) from stdin;
+ 2
+ 3
+ \.
+
+ -- VALUES list expressions
+ -- The fact that there are be 3 lines of 'VOLATILE TRUE' output is a quirk of
+ -- the current set-returning function execution code
+ insert into defaults (dummy, a, b)
+ values (generate_series(4, 5), stable_true(), volatile_true());
+
+ -- PL/pgSQL Simple expressions
+ -- Make sure we don't cache simple expressions -- these expressions are only
+ -- initialized once per transaction and then executed multiple times
+ create function stable_max() returns int STABLE language plpgsql as
+ $$begin return (select max(i) from two); end$$;
+
+ create function simple() returns int STABLE language plpgsql as
+ $$begin return stable_max(); end$$;
+
+ begin;
+ select simple();
+ insert into two values(3);
+ select simple();
+ rollback;
+
+ -- The end
+ drop table defaults;
+ drop table two;
On Mon, Jan 16, 2012 at 12:06 PM, Marti Raudsepp <marti@juffo.org> wrote:
Here's v6 of my expression caching patch.
i little review...
first, i notice a change of behaviour... i'm not sure if i can say
this is good or not.
with this function:
"""
create or replace function cached_random() returns numeric as $$
begin
raise notice 'cached';
return random();
end;
$$ language plpgsql stable;
"""
if you execute: select *, cached_random() from (select
generate_series(1, 10) ) i;
on head you get 10 random numbers, with your patch you get 10 times
the same random number... wich means your patch make stable promise a
hard one.
personally i think that's good but i know there are people using,
mistakenly, volatile functions inside stable ones
---
seems you are moving code in simplify_function(), do you think is
useful to do that independently? at least if it provides some clarity
to the code
---
benchmark. i run a few tests in my laptop (which is not very performant but...)
from what i see there is no too much gain for the amount of complexity
added... i can see there should be cases which a lot more gain (for
example if you use a function to hide a select and you use such a
function several times in the select... but i guess it should work the
same with a CTE)
configuration:
name | setting
----------------------------------+--------------------
shared_buffers | 4096
synchronous_commit | off
filesystem: xfs
-- This is from the bench_cache.sh but with -T 150
select * from ts where ts between to_timestamp('2005-01-01',
'YYYY-MM-DD') and to_timestamp('2005-01-01', 'YYYY-MM-DD');
head 0.423855
cache 1.949242
select * from ts where ts>now();
head 2.420200
cache 2.580885
/*uncachable*/ select * from one where ts >=
to_date(clock_timestamp()::date::text, 'YYYY-MM-DD') and ts <
(to_date(clock_timestamp()::date::text, 'YYYY-MM-DD') + interval '1
year')
head 955.007129
cache 846.917163
/*cachable*/ select * from one where ts >= to_date(now()::date::text,
'YYYY-MM-DD') and ts < (to_date(now()::date::text, 'YYYY-MM-DD') +
interval '1 year')
head 827.484067
cache 801.743863
a benchmark with pgbench scale 1 (average of 3 runs, -T 300 clients
=1, except on second run)
-scale 1
== simple ==
head 261.833794
cache 250.22167
== simple (10 clients) ==
head 244.075592
cache 233.815389
== extended ==
head 194.676093
cache 202.778580
== prepared ==
head 300.460328
cache 302.061739
== select only ==
head 886.207252
cache 909.832986
a benchmark with pgbench scale 20 (average of 3 runs, -T 300 clients
=1, except on second run)
-scale 20
== simple ==
head 19.890278
cache 19.536342
== simple (10 clients) ==
head 40.864455
cache 44.457357
== extended ==
head 21.372751
cache 19.992955
== prepared ==
head 19.543434
cache 20.226981
== select only ==
head 31.780529
cache 36.410658
--
Jaime Casanova www.2ndQuadrant.com
Professional PostgreSQL: Soporte 24x7 y capacitación
On Sat, Feb 4, 2012 at 09:49, Jaime Casanova <jaime@2ndquadrant.com> wrote:
i little review...
Thanks! By the way, you should update to the v7 patch.
first, i notice a change of behaviour... i'm not sure if i can say
this is good or not.
if you execute: select *, cached_random() from (select
generate_series(1, 10) ) i;
Yeah, this is pretty much expected.
seems you are moving code in simplify_function(), do you think is
useful to do that independently? at least if it provides some clarity
to the code
I think so, yeah, but separating it from the patch would be quite a bit of work.
shared_buffers | 4096
Note that the "ts" table is 36MB so it doesn't fit in your
shared_buffers now. If you increase shared_buffers, you will see
better gains for tests #1 and #2
from what i see there is no too much gain for the amount of complexity
added... i can see there should be cases which a lot more gain
Yeah, the goal of this test script wasn't to demonstrate the best
cases of the patch, but to display how it behaves in different
scenarios. Here's what they do:
Test case #1 is a typical "real world" query that benefits from this
patch. With a higher shared_buffers you should see a 6-7x improvement
there, which I think is a compelling speedup for a whole class of
queries.
Test #2 doesn't benefit much from the patch since now() is already a
very fast function, but the point is that even saving the function
call overhead helps noticeably.
Tests #3 and #4 show the possible *worst* case of the patch -- it's
processing a complex expression, but there's just one row in the
table, so no opportunity for caching.
----
Besides stable functions, this patch also improves the performance of
expressions involving placeholders parameters, such as variable
references from PL/pgSQL, since these are not constant-folded. E.g:
DECLARE
foo timestamptz = '2012-02-04';
BEGIN
SELECT * FROM ts WHERE ts>(foo - interval '1 day');
Before this patch, the interval subtraction was executed once per row;
now it's once per execution.
a benchmark with pgbench scale 20 (average of 3 runs, -T 300 clients
=1, except on second run)
-scale 20
I think the differences here are mostly noise because the queries
pgbench generates aren't affected by caching.
Regards,
Marti
On Sat, Feb 4, 2012 at 5:40 AM, Marti Raudsepp <marti@juffo.org> wrote:
On Sat, Feb 4, 2012 at 09:49, Jaime Casanova <jaime@2ndquadrant.com> wrote:
i little review...
Thanks! By the way, you should update to the v7 patch.
just tried it and it fail when initializing on make check
"""
creating information schema ... TRAP: FailedAssertion("!(*cachable ==
((bool) 1))", File: "clauses.c", Line: 2295)
Aborted (core dumped)
child process exited with exit code 134
"""
--
Jaime Casanova www.2ndQuadrant.com
Professional PostgreSQL: Soporte 24x7 y capacitación
On Fri, Mar 2, 2012 at 00:34, Jaime Casanova <jaime@2ndquadrant.com> wrote:
just tried it and it fail when initializing on make check
creating information schema ... TRAP: FailedAssertion("!(*cachable ==
((bool) 1))", File: "clauses.c", Line: 2295)
Sorry, my bad, here's a fixed patch (also rebased to current git master).
Regards,
Marti
Attachments:
cacheexpr-v8.patchtext/x-patch; charset=US-ASCII; name=cacheexpr-v8.patchDownload
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
new file mode 100644
index 110480f..a9dbb87
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
*************** BeginCopyFrom(Relation rel,
*** 2296,2302 ****
{
/* Initialize expressions in copycontext. */
defexprs[num_defaults] = ExecInitExpr(
! expression_planner((Expr *) defexpr), NULL);
defmap[num_defaults] = attnum - 1;
num_defaults++;
--- 2296,2304 ----
{
/* Initialize expressions in copycontext. */
defexprs[num_defaults] = ExecInitExpr(
! expression_planner((Expr *) defexpr),
! NULL,
! true);
defmap[num_defaults] = attnum - 1;
num_defaults++;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
new file mode 100644
index cd4490a..aa6e141
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** ATRewriteTable(AlteredTableInfo *tab, Oi
*** 3575,3581 ****
NewColumnValue *ex = lfirst(l);
/* expr already planned */
! ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
}
notnull_attrs = NIL;
--- 3575,3581 ----
NewColumnValue *ex = lfirst(l);
/* expr already planned */
! ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL, true);
}
notnull_attrs = NIL;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
new file mode 100644
index 22c1132..85fd08a
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
*************** GetDomainConstraints(Oid typeOid)
*** 3049,3055 ****
r = makeNode(DomainConstraintState);
r->constrainttype = DOM_CONSTRAINT_CHECK;
r->name = pstrdup(NameStr(c->conname));
! r->check_expr = ExecInitExpr(check_expr, NULL);
/*
* use lcons() here because constraints of lower domains should be
--- 3049,3055 ----
r = makeNode(DomainConstraintState);
r->constrainttype = DOM_CONSTRAINT_CHECK;
r->name = pstrdup(NameStr(c->conname));
! r->check_expr = ExecInitExpr(check_expr, NULL, false);
/*
* use lcons() here because constraints of lower domains should be
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
new file mode 100644
index a1193a8..dd7abeb
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 51,62 ****
--- 51,70 ----
#include "pgstat.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+ #include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/typcache.h"
#include "utils/xml.h"
+ typedef struct
+ {
+ PlanState *parent;
+ bool useCache;
+ } ExecInitExprContext;
+
+
/* static function decls */
static Datum ExecEvalArrayRef(ArrayRefExprState *astate,
ExprContext *econtext,
*************** static Datum ExecEvalCoerceToDomain(Coer
*** 157,162 ****
--- 165,176 ----
static Datum ExecEvalCoerceToDomainValue(ExprState *exprstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
+ static Datum ExecEvalCacheExpr(CacheExprState *cstate,
+ ExprContext *econtext,
+ bool *isNull,ExprDoneCond *isDone);
+ static Datum ExecEvalCacheExprResult(CacheExprState *cstate,
+ ExprContext *econtext,
+ bool *isNull,ExprDoneCond *isDone);
static Datum ExecEvalFieldSelect(FieldSelectState *fstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
*************** static Datum ExecEvalArrayCoerceExpr(Arr
*** 174,179 ****
--- 188,195 ----
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
+ static ExprState *ExecInitExprMutator(Expr *node,
+ const ExecInitExprContext *context);
/* ----------------------------------------------------------------
*************** ExecEvalBooleanTest(GenericExprState *bs
*** 3754,3759 ****
--- 3770,3832 ----
}
}
+ /* ----------------------------------------------------------------
+ * ExecEvalCacheExpr
+ *
+ * Evaluates a cachable expression for the first time and updates
+ * xprstate.evalfunc to return cached result next time
+ * ----------------------------------------------------------------
+ */
+ static Datum
+ ExecEvalCacheExpr(CacheExprState *cstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+ {
+ MemoryContext oldcontext;
+ Datum result;
+ bool resultTypByVal;
+ int16 resultTypLen;
+ Oid resultType;
+
+ result = ExecEvalExpr(cstate->arg, econtext, isNull, isDone);
+
+ if (!cstate->enabled)
+ return result; /* Cache disabled, pass thru the result */
+
+ /* Set-returning expressions can't be cached */
+ Assert(isDone == NULL || *isDone == ExprSingleResult);
+
+ /* Figure out type and size for copy */
+ resultType = exprType((Node *) ((CacheExpr *) cstate->xprstate.expr)->arg);
+ get_typlenbyval(resultType, &resultTypLen, &resultTypByVal);
+
+ /* This cached datum has to persist for the whole query */
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ cstate->result = datumCopy(result, resultTypByVal, resultTypLen);
+ cstate->isNull = *isNull;
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Subsequent calls will return the cached result */
+ cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCacheExprResult;
+
+ return cstate->result;
+ }
+
+ /* ----------------------------------------------------------------
+ * ExecEvalCacheExprResult
+ *
+ * Return the already-cached result, computed in ExecEvalCacheExpr
+ * ----------------------------------------------------------------
+ */
+ static Datum
+ ExecEvalCacheExprResult(CacheExprState *cstate, ExprContext *econtext,
+ bool *isNull, ExprDoneCond *isDone)
+ {
+ if (isDone)
+ *isDone = ExprSingleResult;
+ *isNull = cstate->isNull;
+ return cstate->result;
+ }
+
/*
* ExecEvalCoerceToDomain
*
*************** ExecEvalExprSwitchContext(ExprState *exp
*** 4217,4229 ****
*
* 'node' is the root of the expression tree to examine
* 'parent' is the PlanState node that owns the expression.
*
* 'parent' may be NULL if we are preparing an expression that is not
* associated with a plan tree. (If so, it can't have aggs or subplans.)
* This case should usually come through ExecPrepareExpr, not directly here.
*/
ExprState *
! ExecInitExpr(Expr *node, PlanState *parent)
{
ExprState *state;
--- 4290,4319 ----
*
* 'node' is the root of the expression tree to examine
* 'parent' is the PlanState node that owns the expression.
+ * 'useCache' enables caching for stable/parameterized expressions.
*
* 'parent' may be NULL if we are preparing an expression that is not
* associated with a plan tree. (If so, it can't have aggs or subplans.)
* This case should usually come through ExecPrepareExpr, not directly here.
+ *
+ * 'useCache' may only be true if it's guaranteed that all executions of the
+ * expression use the same snapshot and same external params. It should also
+ * be false if the expression is only executed once.
*/
ExprState *
! ExecInitExpr(Expr *node, PlanState *parent, bool useCache)
! {
! ExecInitExprContext context;
!
! context.parent = parent;
! context.useCache = useCache;
!
! return ExecInitExprMutator(node, &context);
! }
!
! static ExprState *
! ExecInitExprMutator(Expr *node,
! const ExecInitExprContext *context)
{
ExprState *state;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4273,4288 ****
AggrefExprState *astate = makeNode(AggrefExprState);
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAggref;
! if (parent && IsA(parent, AggState))
{
! AggState *aggstate = (AggState *) parent;
int naggs;
aggstate->aggs = lcons(astate, aggstate->aggs);
naggs = ++aggstate->numaggs;
! astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
! parent);
/*
* Complain if the aggregate's arguments contain any
--- 4363,4378 ----
AggrefExprState *astate = makeNode(AggrefExprState);
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAggref;
! if (context->parent && IsA(context->parent, AggState))
{
! AggState *aggstate = (AggState *) context->parent;
int naggs;
aggstate->aggs = lcons(astate, aggstate->aggs);
naggs = ++aggstate->numaggs;
! astate->args = (List *)
! ExecInitExprMutator((Expr *) aggref->args, context);
/*
* Complain if the aggregate's arguments contain any
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4309,4317 ****
WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc;
! if (parent && IsA(parent, WindowAggState))
{
! WindowAggState *winstate = (WindowAggState *) parent;
int nfuncs;
winstate->funcs = lcons(wfstate, winstate->funcs);
--- 4399,4407 ----
WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc;
! if (context->parent && IsA(context->parent, WindowAggState))
{
! WindowAggState *winstate = (WindowAggState *) context->parent;
int nfuncs;
winstate->funcs = lcons(wfstate, winstate->funcs);
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4319,4326 ****
if (wfunc->winagg)
winstate->numaggs++;
! wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
! parent);
/*
* Complain if the windowfunc's arguments contain any
--- 4409,4416 ----
if (wfunc->winagg)
winstate->numaggs++;
! wfstate->args = (List *)
! ExecInitExprMutator((Expr *) wfunc->args, context);
/*
* Complain if the windowfunc's arguments contain any
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4348,4359 ****
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayRef;
astate->refupperindexpr = (List *)
! ExecInitExpr((Expr *) aref->refupperindexpr, parent);
astate->reflowerindexpr = (List *)
! ExecInitExpr((Expr *) aref->reflowerindexpr, parent);
! astate->refexpr = ExecInitExpr(aref->refexpr, parent);
! astate->refassgnexpr = ExecInitExpr(aref->refassgnexpr,
! parent);
/* do one-time catalog lookups for type info */
astate->refattrlength = get_typlen(aref->refarraytype);
get_typlenbyvalalign(aref->refelemtype,
--- 4438,4449 ----
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayRef;
astate->refupperindexpr = (List *)
! ExecInitExprMutator((Expr *) aref->refupperindexpr, context);
astate->reflowerindexpr = (List *)
! ExecInitExprMutator((Expr *) aref->reflowerindexpr, context);
! astate->refexpr = ExecInitExprMutator(aref->refexpr, context);
! astate->refassgnexpr =
! ExecInitExprMutator(aref->refassgnexpr, context);
/* do one-time catalog lookups for type info */
astate->refattrlength = get_typlen(aref->refarraytype);
get_typlenbyvalalign(aref->refelemtype,
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4370,4376 ****
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFunc;
fstate->args = (List *)
! ExecInitExpr((Expr *) funcexpr->args, parent);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
--- 4460,4466 ----
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFunc;
fstate->args = (List *)
! ExecInitExprMutator((Expr *) funcexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4382,4388 ****
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOper;
fstate->args = (List *)
! ExecInitExpr((Expr *) opexpr->args, parent);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
--- 4472,4478 ----
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOper;
fstate->args = (List *)
! ExecInitExprMutator((Expr *) opexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4394,4400 ****
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalDistinct;
fstate->args = (List *)
! ExecInitExpr((Expr *) distinctexpr->args, parent);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
--- 4484,4490 ----
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalDistinct;
fstate->args = (List *)
! ExecInitExprMutator((Expr *) distinctexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4406,4412 ****
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullIf;
fstate->args = (List *)
! ExecInitExpr((Expr *) nullifexpr->args, parent);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
--- 4496,4502 ----
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullIf;
fstate->args = (List *)
! ExecInitExprMutator((Expr *) nullifexpr->args, context);
fstate->func.fn_oid = InvalidOid; /* not initialized */
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4418,4424 ****
sstate->fxprstate.xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalScalarArrayOp;
sstate->fxprstate.args = (List *)
! ExecInitExpr((Expr *) opexpr->args, parent);
sstate->fxprstate.func.fn_oid = InvalidOid; /* not initialized */
sstate->element_type = InvalidOid; /* ditto */
state = (ExprState *) sstate;
--- 4508,4514 ----
sstate->fxprstate.xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalScalarArrayOp;
sstate->fxprstate.args = (List *)
! ExecInitExprMutator((Expr *) opexpr->args, context);
sstate->fxprstate.func.fn_oid = InvalidOid; /* not initialized */
sstate->element_type = InvalidOid; /* ditto */
state = (ExprState *) sstate;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4446,4452 ****
break;
}
bstate->args = (List *)
! ExecInitExpr((Expr *) boolexpr->args, parent);
state = (ExprState *) bstate;
}
break;
--- 4536,4542 ----
break;
}
bstate->args = (List *)
! ExecInitExprMutator((Expr *) boolexpr->args, context);
state = (ExprState *) bstate;
}
break;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4455,4467 ****
SubPlan *subplan = (SubPlan *) node;
SubPlanState *sstate;
! if (!parent)
elog(ERROR, "SubPlan found with no parent plan");
! sstate = ExecInitSubPlan(subplan, parent);
/* Add SubPlanState nodes to parent->subPlan */
! parent->subPlan = lappend(parent->subPlan, sstate);
state = (ExprState *) sstate;
}
--- 4545,4558 ----
SubPlan *subplan = (SubPlan *) node;
SubPlanState *sstate;
! if (!context->parent)
elog(ERROR, "SubPlan found with no parent plan");
! sstate = ExecInitSubPlan(subplan, context->parent);
/* Add SubPlanState nodes to parent->subPlan */
! context->parent->subPlan = lappend(context->parent->subPlan,
! sstate);
state = (ExprState *) sstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4471,4480 ****
AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
AlternativeSubPlanState *asstate;
! if (!parent)
elog(ERROR, "AlternativeSubPlan found with no parent plan");
! asstate = ExecInitAlternativeSubPlan(asplan, parent);
state = (ExprState *) asstate;
}
--- 4562,4571 ----
AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
AlternativeSubPlanState *asstate;
! if (!context->parent)
elog(ERROR, "AlternativeSubPlan found with no parent plan");
! asstate = ExecInitAlternativeSubPlan(asplan, context->parent);
state = (ExprState *) asstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4485,4491 ****
FieldSelectState *fstate = makeNode(FieldSelectState);
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldSelect;
! fstate->arg = ExecInitExpr(fselect->arg, parent);
fstate->argdesc = NULL;
state = (ExprState *) fstate;
}
--- 4576,4582 ----
FieldSelectState *fstate = makeNode(FieldSelectState);
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldSelect;
! fstate->arg = ExecInitExprMutator(fselect->arg, context);
fstate->argdesc = NULL;
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4496,4503 ****
FieldStoreState *fstate = makeNode(FieldStoreState);
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldStore;
! fstate->arg = ExecInitExpr(fstore->arg, parent);
! fstate->newvals = (List *) ExecInitExpr((Expr *) fstore->newvals, parent);
fstate->argdesc = NULL;
state = (ExprState *) fstate;
}
--- 4587,4595 ----
FieldStoreState *fstate = makeNode(FieldStoreState);
fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldStore;
! fstate->arg = ExecInitExprMutator(fstore->arg, context);
! fstate->newvals = (List *)
! ExecInitExprMutator((Expr *) fstore->newvals, context);
fstate->argdesc = NULL;
state = (ExprState *) fstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4508,4514 ****
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRelabelType;
! gstate->arg = ExecInitExpr(relabel->arg, parent);
state = (ExprState *) gstate;
}
break;
--- 4600,4606 ----
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRelabelType;
! gstate->arg = ExecInitExprMutator(relabel->arg, context);
state = (ExprState *) gstate;
}
break;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4520,4526 ****
bool typisvarlena;
iostate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceViaIO;
! iostate->arg = ExecInitExpr(iocoerce->arg, parent);
/* lookup the result type's input function */
getTypeInputInfo(iocoerce->resulttype, &iofunc,
&iostate->intypioparam);
--- 4612,4618 ----
bool typisvarlena;
iostate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceViaIO;
! iostate->arg = ExecInitExprMutator(iocoerce->arg, context);
/* lookup the result type's input function */
getTypeInputInfo(iocoerce->resulttype, &iofunc,
&iostate->intypioparam);
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4538,4544 ****
ArrayCoerceExprState *astate = makeNode(ArrayCoerceExprState);
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayCoerceExpr;
! astate->arg = ExecInitExpr(acoerce->arg, parent);
astate->resultelemtype = get_element_type(acoerce->resulttype);
if (astate->resultelemtype == InvalidOid)
ereport(ERROR,
--- 4630,4636 ----
ArrayCoerceExprState *astate = makeNode(ArrayCoerceExprState);
astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayCoerceExpr;
! astate->arg = ExecInitExprMutator(acoerce->arg, context);
astate->resultelemtype = get_element_type(acoerce->resulttype);
if (astate->resultelemtype == InvalidOid)
ereport(ERROR,
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4558,4564 ****
ConvertRowtypeExprState *cstate = makeNode(ConvertRowtypeExprState);
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype;
! cstate->arg = ExecInitExpr(convert->arg, parent);
state = (ExprState *) cstate;
}
break;
--- 4650,4656 ----
ConvertRowtypeExprState *cstate = makeNode(ConvertRowtypeExprState);
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype;
! cstate->arg = ExecInitExprMutator(convert->arg, context);
state = (ExprState *) cstate;
}
break;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4570,4576 ****
ListCell *l;
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase;
! cstate->arg = ExecInitExpr(caseexpr->arg, parent);
foreach(l, caseexpr->args)
{
CaseWhen *when = (CaseWhen *) lfirst(l);
--- 4662,4668 ----
ListCell *l;
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase;
! cstate->arg = ExecInitExprMutator(caseexpr->arg, context);
foreach(l, caseexpr->args)
{
CaseWhen *when = (CaseWhen *) lfirst(l);
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4579,4590 ****
Assert(IsA(when, CaseWhen));
wstate->xprstate.evalfunc = NULL; /* not used */
wstate->xprstate.expr = (Expr *) when;
! wstate->expr = ExecInitExpr(when->expr, parent);
! wstate->result = ExecInitExpr(when->result, parent);
outlist = lappend(outlist, wstate);
}
cstate->args = outlist;
! cstate->defresult = ExecInitExpr(caseexpr->defresult, parent);
state = (ExprState *) cstate;
}
break;
--- 4671,4683 ----
Assert(IsA(when, CaseWhen));
wstate->xprstate.evalfunc = NULL; /* not used */
wstate->xprstate.expr = (Expr *) when;
! wstate->expr = ExecInitExprMutator(when->expr, context);
! wstate->result = ExecInitExprMutator(when->result, context);
outlist = lappend(outlist, wstate);
}
cstate->args = outlist;
! cstate->defresult =
! ExecInitExprMutator(caseexpr->defresult, context);
state = (ExprState *) cstate;
}
break;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4601,4607 ****
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
astate->elements = outlist;
--- 4694,4700 ----
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
astate->elements = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4670,4676 ****
*/
e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid);
}
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
i++;
}
--- 4763,4769 ----
*/
e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid);
}
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
i++;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4697,4703 ****
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
rstate->largs = outlist;
--- 4790,4796 ----
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
rstate->largs = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4708,4714 ****
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
rstate->rargs = outlist;
--- 4801,4807 ----
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
rstate->rargs = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4761,4767 ****
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
cstate->args = outlist;
--- 4854,4860 ----
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
cstate->args = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4782,4788 ****
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
mstate->args = outlist;
--- 4875,4881 ----
Expr *e = (Expr *) lfirst(l);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
mstate->args = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4819,4825 ****
Expr *e = (Expr *) lfirst(arg);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
xstate->named_args = outlist;
--- 4912,4918 ----
Expr *e = (Expr *) lfirst(arg);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
xstate->named_args = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4830,4836 ****
Expr *e = (Expr *) lfirst(arg);
ExprState *estate;
! estate = ExecInitExpr(e, parent);
outlist = lappend(outlist, estate);
}
xstate->args = outlist;
--- 4923,4929 ----
Expr *e = (Expr *) lfirst(arg);
ExprState *estate;
! estate = ExecInitExprMutator(e, context);
outlist = lappend(outlist, estate);
}
xstate->args = outlist;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4844,4850 ****
NullTestState *nstate = makeNode(NullTestState);
nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest;
! nstate->arg = ExecInitExpr(ntest->arg, parent);
nstate->argdesc = NULL;
state = (ExprState *) nstate;
}
--- 4937,4943 ----
NullTestState *nstate = makeNode(NullTestState);
nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest;
! nstate->arg = ExecInitExprMutator(ntest->arg, context);
nstate->argdesc = NULL;
state = (ExprState *) nstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4855,4871 ****
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalBooleanTest;
! gstate->arg = ExecInitExpr(btest->arg, parent);
state = (ExprState *) gstate;
}
break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
CoerceToDomainState *cstate = makeNode(CoerceToDomainState);
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceToDomain;
! cstate->arg = ExecInitExpr(ctest->arg, parent);
cstate->constraints = GetDomainConstraints(ctest->resulttype);
state = (ExprState *) cstate;
}
--- 4948,4981 ----
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalBooleanTest;
! gstate->arg = ExecInitExprMutator(btest->arg, context);
state = (ExprState *) gstate;
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+ CacheExprState *cstate = makeNode(CacheExprState);
+
+ cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCacheExpr;
+ cstate->arg = ExecInitExprMutator(cache->arg, context);
+ /*
+ * If useCache=false, in theory we could simply skip creating
+ * the CacheExprState node in the first place, but that might
+ * be surprising to future developers.
+ */
+ cstate->enabled = context->useCache;
+
+ state = (ExprState *) cstate;
+ }
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
CoerceToDomainState *cstate = makeNode(CoerceToDomainState);
cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceToDomain;
! cstate->arg = ExecInitExprMutator(ctest->arg, context);
cstate->constraints = GetDomainConstraints(ctest->resulttype);
state = (ExprState *) cstate;
}
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4880,4886 ****
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = NULL; /* not used */
! gstate->arg = ExecInitExpr(tle->expr, parent);
state = (ExprState *) gstate;
}
break;
--- 4990,4996 ----
GenericExprState *gstate = makeNode(GenericExprState);
gstate->xprstate.evalfunc = NULL; /* not used */
! gstate->arg = ExecInitExprMutator(tle->expr, context);
state = (ExprState *) gstate;
}
break;
*************** ExecInitExpr(Expr *node, PlanState *pare
*** 4892,4899 ****
foreach(l, (List *) node)
{
outlist = lappend(outlist,
! ExecInitExpr((Expr *) lfirst(l),
! parent));
}
/* Don't fall through to the "common" code below */
return (ExprState *) outlist;
--- 5002,5009 ----
foreach(l, (List *) node)
{
outlist = lappend(outlist,
! ExecInitExprMutator((Expr *) lfirst(l),
! context));
}
/* Don't fall through to the "common" code below */
return (ExprState *) outlist;
*************** ExecPrepareExpr(Expr *node, EState *esta
*** 4932,4938 ****
node = expression_planner(node);
! result = ExecInitExpr(node, NULL);
MemoryContextSwitchTo(oldcontext);
--- 5042,5048 ----
node = expression_planner(node);
! result = ExecInitExpr(node, NULL, false);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
new file mode 100644
index c9aa921..618a485
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** ExecInitAgg(Agg *node, EState *estate, i
*** 1444,1453 ****
*/
aggstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) aggstate);
aggstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) aggstate);
/*
* initialize child nodes
--- 1444,1455 ----
*/
aggstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) aggstate,
! true);
aggstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) aggstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
new file mode 100644
index 849665d..f7e2d1c
*** a/src/backend/executor/nodeBitmapHeapscan.c
--- b/src/backend/executor/nodeBitmapHeapscan.c
*************** ExecInitBitmapHeapScan(BitmapHeapScan *n
*** 569,581 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
scanstate->bitmapqualorig = (List *)
ExecInitExpr((Expr *) node->bitmapqualorig,
! (PlanState *) scanstate);
/*
* tuple table initialization
--- 569,584 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
scanstate->bitmapqualorig = (List *)
ExecInitExpr((Expr *) node->bitmapqualorig,
! (PlanState *) scanstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c
new file mode 100644
index 7ab15ac..93c2c04
*** a/src/backend/executor/nodeCtescan.c
--- b/src/backend/executor/nodeCtescan.c
*************** ExecInitCteScan(CteScan *node, EState *e
*** 241,250 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
/*
* tuple table initialization
--- 241,252 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
new file mode 100644
index 9204859..1602290
*** a/src/backend/executor/nodeForeignscan.c
--- b/src/backend/executor/nodeForeignscan.c
*************** ExecInitForeignScan(ForeignScan *node, E
*** 129,138 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
/*
* tuple table initialization
--- 129,140 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
new file mode 100644
index a700762..955c4ee
*** a/src/backend/executor/nodeFunctionscan.c
--- b/src/backend/executor/nodeFunctionscan.c
*************** ExecInitFunctionScan(FunctionScan *node,
*** 153,162 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
/*
* Now determine if the function returns a simple or composite type, and
--- 153,164 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
/*
* Now determine if the function returns a simple or composite type, and
*************** ExecInitFunctionScan(FunctionScan *node,
*** 217,223 ****
*/
scanstate->tuplestorestate = NULL;
scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
! (PlanState *) scanstate);
scanstate->ss.ps.ps_TupFromTlist = false;
--- 219,226 ----
*/
scanstate->tuplestorestate = NULL;
scanstate->funcexpr = ExecInitExpr((Expr *) node->funcexpr,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.ps_TupFromTlist = false;
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
new file mode 100644
index 80e282b..881e8d7
*** a/src/backend/executor/nodeGroup.c
--- b/src/backend/executor/nodeGroup.c
*************** ExecInitGroup(Group *node, EState *estat
*** 228,237 ****
*/
grpstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) grpstate);
grpstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) grpstate);
/*
* initialize child nodes
--- 228,239 ----
*/
grpstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) grpstate,
! true);
grpstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) grpstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
new file mode 100644
index cabe663..7d65e6d
*** a/src/backend/executor/nodeHash.c
--- b/src/backend/executor/nodeHash.c
*************** ExecInitHash(Hash *node, EState *estate,
*** 179,188 ****
*/
hashstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) hashstate);
hashstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) hashstate);
/*
* initialize child nodes
--- 179,190 ----
*/
hashstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) hashstate,
! true);
hashstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) hashstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
new file mode 100644
index 0ea490b..b42ad3f
*** a/src/backend/executor/nodeHashjoin.c
--- b/src/backend/executor/nodeHashjoin.c
*************** ExecInitHashJoin(HashJoin *node, EState
*** 463,479 ****
*/
hjstate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) hjstate);
hjstate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) hjstate);
hjstate->js.jointype = node->join.jointype;
hjstate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) hjstate);
hjstate->hashclauses = (List *)
ExecInitExpr((Expr *) node->hashclauses,
! (PlanState *) hjstate);
/*
* initialize child nodes
--- 463,483 ----
*/
hjstate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) hjstate,
! true);
hjstate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) hjstate,
! true);
hjstate->js.jointype = node->join.jointype;
hjstate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) hjstate,
! true);
hjstate->hashclauses = (List *)
ExecInitExpr((Expr *) node->hashclauses,
! (PlanState *) hjstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
new file mode 100644
index 4abd805..f20f92f
*** a/src/backend/executor/nodeIndexonlyscan.c
--- b/src/backend/executor/nodeIndexonlyscan.c
*************** ExecInitIndexOnlyScan(IndexOnlyScan *nod
*** 366,378 ****
*/
indexstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) indexstate);
indexstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) indexstate);
indexstate->indexqual = (List *)
ExecInitExpr((Expr *) node->indexqual,
! (PlanState *) indexstate);
/*
* tuple table initialization
--- 366,381 ----
*/
indexstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) indexstate,
! true);
indexstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) indexstate,
! true);
indexstate->indexqual = (List *)
ExecInitExpr((Expr *) node->indexqual,
! (PlanState *) indexstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
new file mode 100644
index e577bac..a2c979d
*** a/src/backend/executor/nodeIndexscan.c
--- b/src/backend/executor/nodeIndexscan.c
*************** ExecInitIndexScan(IndexScan *node, EStat
*** 494,506 ****
*/
indexstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) indexstate);
indexstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) indexstate);
indexstate->indexqualorig = (List *)
ExecInitExpr((Expr *) node->indexqualorig,
! (PlanState *) indexstate);
/*
* tuple table initialization
--- 494,509 ----
*/
indexstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) indexstate,
! true);
indexstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) indexstate,
! true);
indexstate->indexqualorig = (List *)
ExecInitExpr((Expr *) node->indexqualorig,
! (PlanState *) indexstate,
! true);
/*
* tuple table initialization
*************** ExecIndexBuildScanKeys(PlanState *planst
*** 817,823 ****
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
--- 820,826 ----
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate, true);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
*************** ExecIndexBuildScanKeys(PlanState *planst
*** 944,950 ****
}
runtime_keys[n_runtime_keys].scan_key = this_sub_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
--- 947,953 ----
}
runtime_keys[n_runtime_keys].scan_key = this_sub_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate, true);
runtime_keys[n_runtime_keys].key_toastable =
TypeIsToastable(op_righttype);
n_runtime_keys++;
*************** ExecIndexBuildScanKeys(PlanState *planst
*** 1062,1068 ****
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate);
/*
* Careful here: the runtime expression is not of
--- 1065,1071 ----
}
runtime_keys[n_runtime_keys].scan_key = this_scan_key;
runtime_keys[n_runtime_keys].key_expr =
! ExecInitExpr(rightop, planstate, true);
/*
* Careful here: the runtime expression is not of
*************** ExecIndexBuildScanKeys(PlanState *planst
*** 1080,1086 ****
/* Executor has to expand the array value */
array_keys[n_array_keys].scan_key = this_scan_key;
array_keys[n_array_keys].array_expr =
! ExecInitExpr(rightop, planstate);
/* the remaining fields were zeroed by palloc0 */
n_array_keys++;
scanvalue = (Datum) 0;
--- 1083,1089 ----
/* Executor has to expand the array value */
array_keys[n_array_keys].scan_key = this_scan_key;
array_keys[n_array_keys].array_expr =
! ExecInitExpr(rightop, planstate, true);
/* the remaining fields were zeroed by palloc0 */
n_array_keys++;
scanvalue = (Datum) 0;
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
new file mode 100644
index 8936187..3e2b643
*** a/src/backend/executor/nodeLimit.c
--- b/src/backend/executor/nodeLimit.c
*************** ExecInitLimit(Limit *node, EState *estat
*** 399,407 ****
* initialize child expressions
*/
limitstate->limitOffset = ExecInitExpr((Expr *) node->limitOffset,
! (PlanState *) limitstate);
limitstate->limitCount = ExecInitExpr((Expr *) node->limitCount,
! (PlanState *) limitstate);
/*
* Tuple table initialization (XXX not actually used...)
--- 399,409 ----
* initialize child expressions
*/
limitstate->limitOffset = ExecInitExpr((Expr *) node->limitOffset,
! (PlanState *) limitstate,
! true);
limitstate->limitCount = ExecInitExpr((Expr *) node->limitCount,
! (PlanState *) limitstate,
! true);
/*
* Tuple table initialization (XXX not actually used...)
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
new file mode 100644
index f921847..be45c3e
*** a/src/backend/executor/nodeMergejoin.c
--- b/src/backend/executor/nodeMergejoin.c
*************** MJExamineQuals(List *mergeclauses,
*** 206,213 ****
/*
* Prepare the input expressions for execution.
*/
! clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent);
! clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent);
/* Set up sort support data */
clause->ssup.ssup_cxt = CurrentMemoryContext;
--- 206,213 ----
/*
* Prepare the input expressions for execution.
*/
! clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent, true);
! clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent, true);
/* Set up sort support data */
clause->ssup.ssup_cxt = CurrentMemoryContext;
*************** ExecInitMergeJoin(MergeJoin *node, EStat
*** 1498,1511 ****
*/
mergestate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) mergestate);
mergestate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) mergestate);
mergestate->js.jointype = node->join.jointype;
mergestate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) mergestate);
mergestate->mj_ConstFalseJoin = false;
/* mergeclauses are handled below */
--- 1498,1514 ----
*/
mergestate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) mergestate,
! true);
mergestate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) mergestate,
! true);
mergestate->js.jointype = node->join.jointype;
mergestate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) mergestate,
! true);
mergestate->mj_ConstFalseJoin = false;
/* mergeclauses are handled below */
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
new file mode 100644
index dfdcb20..3e2810b
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
*************** ExecInitModifyTable(ModifyTable *node, E
*** 1003,1009 ****
List *rlist = (List *) lfirst(l);
List *rliststate;
! rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rliststate, econtext, slot,
resultRelInfo->ri_RelationDesc->rd_att);
--- 1003,1009 ----
List *rlist = (List *) lfirst(l);
List *rliststate;
! rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps, true);
resultRelInfo->ri_projectReturning =
ExecBuildProjectionInfo(rliststate, econtext, slot,
resultRelInfo->ri_RelationDesc->rd_att);
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
new file mode 100644
index 11d027b..0452478
*** a/src/backend/executor/nodeNestloop.c
--- b/src/backend/executor/nodeNestloop.c
*************** ExecInitNestLoop(NestLoop *node, EState
*** 322,335 ****
*/
nlstate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) nlstate);
nlstate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) nlstate);
nlstate->js.jointype = node->join.jointype;
nlstate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) nlstate);
/*
* initialize child nodes
--- 322,338 ----
*/
nlstate->js.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->join.plan.targetlist,
! (PlanState *) nlstate,
! true);
nlstate->js.ps.qual = (List *)
ExecInitExpr((Expr *) node->join.plan.qual,
! (PlanState *) nlstate,
! true);
nlstate->js.jointype = node->join.jointype;
nlstate->js.joinqual = (List *)
ExecInitExpr((Expr *) node->join.joinqual,
! (PlanState *) nlstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
new file mode 100644
index b51efd8..5e0869c
*** a/src/backend/executor/nodeResult.c
--- b/src/backend/executor/nodeResult.c
*************** ExecInitResult(Result *node, EState *est
*** 240,251 ****
*/
resstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) resstate);
resstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) resstate);
resstate->resconstantqual = ExecInitExpr((Expr *) node->resconstantqual,
! (PlanState *) resstate);
/*
* initialize child nodes
--- 240,254 ----
*/
resstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) resstate,
! true);
resstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) resstate,
! true);
resstate->resconstantqual = ExecInitExpr((Expr *) node->resconstantqual,
! (PlanState *) resstate,
! true);
/*
* initialize child nodes
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
new file mode 100644
index 9fcd332..17589fa
*** a/src/backend/executor/nodeSeqscan.c
--- b/src/backend/executor/nodeSeqscan.c
*************** ExecInitSeqScan(SeqScan *node, EState *e
*** 182,191 ****
*/
scanstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) scanstate);
scanstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) scanstate);
/*
* tuple table initialization
--- 182,193 ----
*/
scanstate->ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ps.qual = (List *)
ExecInitExpr((Expr *) node->plan.qual,
! (PlanState *) scanstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
new file mode 100644
index da31820..b24a372
*** a/src/backend/executor/nodeSubplan.c
--- b/src/backend/executor/nodeSubplan.c
*************** ExecInitSubPlan(SubPlan *subplan, PlanSt
*** 661,668 ****
subplan->plan_id - 1);
/* Initialize subexpressions */
! sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
! sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
/*
* initialize my state
--- 661,668 ----
subplan->plan_id - 1);
/* Initialize subexpressions */
! sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent, true);
! sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent, true);
/*
* initialize my state
*************** ExecInitAlternativeSubPlan(AlternativeSu
*** 1102,1108 ****
* we're going to use?)
*/
asstate->subplans = (List *) ExecInitExpr((Expr *) asplan->subplans,
! parent);
/*
* Select the one to be used. For this, we need an estimate of the number
--- 1102,1109 ----
* we're going to use?)
*/
asstate->subplans = (List *) ExecInitExpr((Expr *) asplan->subplans,
! parent,
! true);
/*
* Select the one to be used. For this, we need an estimate of the number
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
new file mode 100644
index b5ef152..040ba5c
*** a/src/backend/executor/nodeSubqueryscan.c
--- b/src/backend/executor/nodeSubqueryscan.c
*************** ExecInitSubqueryScan(SubqueryScan *node,
*** 122,131 ****
*/
subquerystate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) subquerystate);
subquerystate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) subquerystate);
/*
* tuple table initialization
--- 122,133 ----
*/
subquerystate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) subquerystate,
! true);
subquerystate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) subquerystate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
new file mode 100644
index 32b8abf..3842911
*** a/src/backend/executor/nodeTidscan.c
--- b/src/backend/executor/nodeTidscan.c
*************** ExecInitTidScan(TidScan *node, EState *e
*** 506,519 ****
*/
tidstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) tidstate);
tidstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) tidstate);
tidstate->tss_tidquals = (List *)
ExecInitExpr((Expr *) node->tidquals,
! (PlanState *) tidstate);
/*
* tuple table initialization
--- 506,522 ----
*/
tidstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) tidstate,
! true);
tidstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) tidstate,
! true);
tidstate->tss_tidquals = (List *)
ExecInitExpr((Expr *) node->tidquals,
! (PlanState *) tidstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
new file mode 100644
index a6c1b70..6aa99e9
*** a/src/backend/executor/nodeValuesscan.c
--- b/src/backend/executor/nodeValuesscan.c
*************** ValuesNext(ValuesScanState *node)
*** 119,125 ****
* is a SubPlan, and there shouldn't be any (any subselects in the
* VALUES list should be InitPlans).
*/
! exprstatelist = (List *) ExecInitExpr((Expr *) exprlist, NULL);
/* parser should have checked all sublists are the same length */
Assert(list_length(exprstatelist) == slot->tts_tupleDescriptor->natts);
--- 119,125 ----
* is a SubPlan, and there shouldn't be any (any subselects in the
* VALUES list should be InitPlans).
*/
! exprstatelist = (List *) ExecInitExpr((Expr *) exprlist, NULL, true);
/* parser should have checked all sublists are the same length */
Assert(list_length(exprstatelist) == slot->tts_tupleDescriptor->natts);
*************** ExecInitValuesScan(ValuesScan *node, ESt
*** 234,243 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
/*
* get info about values list
--- 234,245 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
/*
* get info about values list
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
new file mode 100644
index ce89ff2..2d2c28b
*** a/src/backend/executor/nodeWindowAgg.c
--- b/src/backend/executor/nodeWindowAgg.c
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1455,1461 ****
winstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) winstate);
/*
* WindowAgg nodes never have quals, since they can only occur at the
--- 1455,1462 ----
winstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->plan.targetlist,
! (PlanState *) winstate,
! true);
/*
* WindowAgg nodes never have quals, since they can only occur at the
*************** ExecInitWindowAgg(WindowAgg *node, EStat
*** 1622,1630 ****
/* initialize frame bound offset expressions */
winstate->startOffset = ExecInitExpr((Expr *) node->startOffset,
! (PlanState *) winstate);
winstate->endOffset = ExecInitExpr((Expr *) node->endOffset,
! (PlanState *) winstate);
winstate->all_first = true;
winstate->partition_spooled = false;
--- 1623,1633 ----
/* initialize frame bound offset expressions */
winstate->startOffset = ExecInitExpr((Expr *) node->startOffset,
! (PlanState *) winstate,
! true);
winstate->endOffset = ExecInitExpr((Expr *) node->endOffset,
! (PlanState *) winstate,
! true);
winstate->all_first = true;
winstate->partition_spooled = false;
diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c
new file mode 100644
index e2f3dd4..e68d268
*** a/src/backend/executor/nodeWorktablescan.c
--- b/src/backend/executor/nodeWorktablescan.c
*************** ExecInitWorkTableScan(WorkTableScan *nod
*** 160,169 ****
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate);
/*
* tuple table initialization
--- 160,171 ----
*/
scanstate->ss.ps.targetlist = (List *)
ExecInitExpr((Expr *) node->scan.plan.targetlist,
! (PlanState *) scanstate,
! true);
scanstate->ss.ps.qual = (List *)
ExecInitExpr((Expr *) node->scan.plan.qual,
! (PlanState *) scanstate,
! true);
/*
* tuple table initialization
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 7fec4db..9d1b45a
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyBooleanTest(const BooleanTest *from
*** 1684,1689 ****
--- 1684,1702 ----
}
/*
+ * _copyCacheExpr
+ */
+ static CacheExpr *
+ _copyCacheExpr(const CacheExpr *from)
+ {
+ CacheExpr *newnode = makeNode(CacheExpr);
+
+ COPY_NODE_FIELD(arg);
+
+ return newnode;
+ }
+
+ /*
* _copyCoerceToDomain
*/
static CoerceToDomain *
*************** copyObject(const void *from)
*** 4011,4016 ****
--- 4024,4032 ----
case T_BooleanTest:
retval = _copyBooleanTest(from);
break;
+ case T_CacheExpr:
+ retval = _copyCacheExpr(from);
+ break;
case T_CoerceToDomain:
retval = _copyCoerceToDomain(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index d2a79eb..710f526
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalBooleanTest(const BooleanTest *a,
*** 678,683 ****
--- 678,691 ----
}
static bool
+ _equalCacheExpr(const CacheExpr *a, const CacheExpr *b)
+ {
+ COMPARE_NODE_FIELD(arg);
+
+ return true;
+ }
+
+ static bool
_equalCoerceToDomain(const CoerceToDomain *a, const CoerceToDomain *b)
{
COMPARE_NODE_FIELD(arg);
*************** equal(const void *a, const void *b)
*** 2567,2572 ****
--- 2575,2583 ----
case T_BooleanTest:
retval = _equalBooleanTest(a, b);
break;
+ case T_CacheExpr:
+ retval = _equalCacheExpr(a, b);
+ break;
case T_CoerceToDomain:
retval = _equalCoerceToDomain(a, b);
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
new file mode 100644
index bb5cdae..c8549eb
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
*************** makeFuncExpr(Oid funcid, Oid rettype, Li
*** 471,476 ****
--- 471,491 ----
}
/*
+ * makeCacheExpr -
+ * build an expression node for a cachable expression.
+ */
+ CacheExpr *
+ makeCacheExpr(Expr *arg)
+ {
+ CacheExpr *cacheexpr;
+
+ cacheexpr = makeNode(CacheExpr);
+ cacheexpr->arg = arg;
+
+ return cacheexpr;
+ }
+
+ /*
* makeDefElem -
* build a DefElem node
*
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index 51459c4..fab2104
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** exprType(const Node *expr)
*** 212,217 ****
--- 212,220 ----
case T_BooleanTest:
type = BOOLOID;
break;
+ case T_CacheExpr:
+ type = exprType((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
type = ((const CoerceToDomain *) expr)->resulttype;
break;
*************** exprCollation(const Node *expr)
*** 792,797 ****
--- 795,803 ----
case T_BooleanTest:
coll = InvalidOid; /* result is always boolean */
break;
+ case T_CacheExpr:
+ coll = exprCollation((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
coll = ((const CoerceToDomain *) expr)->resultcollid;
break;
*************** exprLocation(const Node *expr)
*** 1263,1268 ****
--- 1269,1278 ----
/* just use argument's location */
loc = exprLocation((Node *) ((const BooleanTest *) expr)->arg);
break;
+ case T_CacheExpr:
+ /* original expression location */
+ loc = exprLocation((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
{
const CoerceToDomain *cexpr = (const CoerceToDomain *) expr;
*************** expression_tree_walker(Node *node,
*** 1722,1727 ****
--- 1732,1739 ----
return walker(((NullTest *) node)->arg, context);
case T_BooleanTest:
return walker(((BooleanTest *) node)->arg, context);
+ case T_CacheExpr:
+ return walker(((CacheExpr *) node)->arg, context);
case T_CoerceToDomain:
return walker(((CoerceToDomain *) node)->arg, context);
case T_TargetEntry:
*************** expression_tree_mutator(Node *node,
*** 2374,2379 ****
--- 2386,2401 ----
return (Node *) newnode;
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+ CacheExpr *newnode;
+
+ FLATCOPY(newnode, cache, CacheExpr);
+ MUTATE(newnode->arg, cache->arg, Expr *);
+ return (Node *) newnode;
+ }
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
*************** bool
*** 2781,2786 ****
--- 2803,2810 ----
return walker(((NullTest *) node)->arg, context);
case T_BooleanTest:
return walker(((BooleanTest *) node)->arg, context);
+ case T_CacheExpr:
+ return walker(((CacheExpr *) node)->arg, context);
case T_JoinExpr:
{
JoinExpr *join = (JoinExpr *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 25a215e..4d59f83
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outBooleanTest(StringInfo str, const Bo
*** 1367,1372 ****
--- 1367,1380 ----
}
static void
+ _outCacheExpr(StringInfo str, const CacheExpr *node)
+ {
+ WRITE_NODE_TYPE("CACHEEXPR");
+
+ WRITE_NODE_FIELD(arg);
+ }
+
+ static void
_outCoerceToDomain(StringInfo str, const CoerceToDomain *node)
{
WRITE_NODE_TYPE("COERCETODOMAIN");
*************** _outNode(StringInfo str, const void *obj
*** 2910,2915 ****
--- 2918,2926 ----
case T_BooleanTest:
_outBooleanTest(str, obj);
break;
+ case T_CacheExpr:
+ _outCacheExpr(str, obj);
+ break;
case T_CoerceToDomain:
_outCoerceToDomain(str, obj);
break;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
new file mode 100644
index 885d855..59514b8
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** cost_qual_eval_walker(Node *node, cost_q
*** 3036,3041 ****
--- 3036,3059 ----
*/
return false; /* don't recurse into children */
}
+ else if (IsA(node, CacheExpr))
+ {
+ cost_qual_eval_context locContext;
+ CacheExpr *cache = (CacheExpr *) node;
+
+ locContext.root = context->root;
+ locContext.total.startup = 0;
+ locContext.total.per_tuple = 0;
+
+ cost_qual_eval_walker((Node *) cache->arg, &locContext);
+
+ /* Account all cached per-tuple costs once in the startup cost. */
+ context->total.startup +=
+ locContext.total.startup + locContext.total.per_tuple;
+
+ /* do NOT recurse into children */
+ return false;
+ }
else if (IsA(node, CoerceViaIO))
{
CoerceViaIO *iocoerce = (CoerceViaIO *) node;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
new file mode 100644
index cd3da46..cbac4ba
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
*************** static Relids find_nonnullable_rels_walk
*** 98,140 ****
static List *find_nonnullable_vars_walker(Node *node, bool top_level);
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
static bool set_coercionform_dontcare_walker(Node *node, void *context);
! static Node *eval_const_expressions_mutator(Node *node,
! eval_const_expressions_context *context);
static List *simplify_or_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceTrue);
static List *simplify_and_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceFalse);
static Node *simplify_boolean_equality(Oid opno, List *args);
static Expr *simplify_function(Expr *oldexpr, Oid funcid,
Oid result_type, int32 result_typmod, Oid result_collid,
Oid input_collid, List **args,
- bool has_named_args,
bool allow_inline,
! eval_const_expressions_context *context);
static List *reorder_function_arguments(List *args, Oid result_type,
! HeapTuple func_tuple,
! eval_const_expressions_context *context);
static List *add_function_defaults(List *args, Oid result_type,
! HeapTuple func_tuple,
! eval_const_expressions_context *context);
static List *fetch_function_defaults(HeapTuple func_tuple);
static void recheck_cast_function_args(List *args, Oid result_type,
HeapTuple func_tuple);
static Expr *evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context);
static Expr *inline_function(Oid funcid, Oid result_type, Oid result_collid,
Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context);
static Node *substitute_actual_parameters(Node *expr, int nargs, List *args,
int *usecounts);
static Node *substitute_actual_parameters_mutator(Node *node,
substitute_actual_parameters_context *context);
static void sql_inline_error_callback(void *arg);
static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
static Query *substitute_actual_srf_parameters(Query *expr,
--- 98,147 ----
static List *find_nonnullable_vars_walker(Node *node, bool top_level);
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
static bool set_coercionform_dontcare_walker(Node *node, void *context);
! static Node *caching_const_expressions_mutator(Node *node,
! eval_const_expressions_context *context);
! static Node *const_expressions_mutator(Node *node,
! eval_const_expressions_context *context,
! bool *cachable);
static List *simplify_or_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceTrue, bool *cachable);
static List *simplify_and_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceFalse, bool *cachable);
static Node *simplify_boolean_equality(Oid opno, List *args);
static Expr *simplify_function(Expr *oldexpr, Oid funcid,
Oid result_type, int32 result_typmod, Oid result_collid,
Oid input_collid, List **args,
bool allow_inline,
! eval_const_expressions_context *context,
! bool *cachable);
! static List *simplify_copy_function_arguments(List *old_args, Oid result_type,
! HeapTuple func_tuple);
static List *reorder_function_arguments(List *args, Oid result_type,
! HeapTuple func_tuple);
static List *add_function_defaults(List *args, Oid result_type,
! HeapTuple func_tuple);
static List *fetch_function_defaults(HeapTuple func_tuple);
static void recheck_cast_function_args(List *args, Oid result_type,
HeapTuple func_tuple);
static Expr *evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context,
! bool *cachable);
static Expr *inline_function(Oid funcid, Oid result_type, Oid result_collid,
Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context,
! bool *cachable);
static Node *substitute_actual_parameters(Node *expr, int nargs, List *args,
int *usecounts);
static Node *substitute_actual_parameters_mutator(Node *node,
substitute_actual_parameters_context *context);
static void sql_inline_error_callback(void *arg);
+ static inline bool is_cache_useful(Expr *expr);
+ static Expr *insert_cache(Expr *expr);
static Expr *evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
Oid result_collation);
static Query *substitute_actual_srf_parameters(Query *expr,
*************** rowtype_field_matches(Oid rowtypeid, int
*** 2168,2173 ****
--- 2175,2185 ----
* assumption in the presence of user-defined functions; do we need a
* pg_proc flag that prevents discarding the execution of a function?)
*
+ * We also insert CacheExpr nodes above expressions that cannot be
+ * evaluated at planning time, but are constant at execution time.
+ * This includes expressions that contain stable function calls and
+ * Param references.
+ *
* We do understand that certain functions may deliver non-constant
* results even with constant inputs, "nextval()" being the classic
* example. Functions that are not marked "immutable" in pg_proc
*************** eval_const_expressions(PlannerInfo *root
*** 2206,2212 ****
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = false; /* safe transformations only */
! return eval_const_expressions_mutator(node, &context);
}
/*--------------------
--- 2218,2225 ----
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = false; /* safe transformations only */
!
! return caching_const_expressions_mutator(node, &context);
}
/*--------------------
*************** eval_const_expressions(PlannerInfo *root
*** 2224,2235 ****
--- 2237,2255 ----
* value of the Param.
* 2. Fold stable, as well as immutable, functions to constants.
* 3. Reduce PlaceHolderVar nodes to their contained expressions.
+ * 4. Strip CacheExpr nodes, as selectivity estimation functions aren't
+ * prepared to deal with CacheExpr nodes.
+ *
+ * NOTE: This function is not indempotent. Calling it twice over an expression
+ * tree causes CacheExpr nodes to be removed in the first pass, then re-added
+ * in the 2nd pass. Make sure it only gets called once.
*--------------------
*/
Node *
estimate_expression_value(PlannerInfo *root, Node *node)
{
eval_const_expressions_context context;
+ bool isCachable = true;
context.boundParams = root->glob->boundParams; /* bound Params */
/* we do not need to mark the plan as depending on inlined functions */
*************** estimate_expression_value(PlannerInfo *r
*** 2237,2249 ****
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = true; /* unsafe transformations OK */
! return eval_const_expressions_mutator(node, &context);
}
static Node *
! eval_const_expressions_mutator(Node *node,
! eval_const_expressions_context *context)
{
if (node == NULL)
return NULL;
switch (nodeTag(node))
--- 2257,2299 ----
context.active_fns = NIL; /* nothing being recursively simplified */
context.case_val = NULL; /* no CASE being examined */
context.estimate = true; /* unsafe transformations OK */
!
! return const_expressions_mutator(node, &context, &isCachable);
}
+ /*
+ * Calls const_expressions_mutator on the expression tree and automatically
+ * adds a CacheExpr node if the expression is cachable.
+ */
static Node *
! caching_const_expressions_mutator(Node *node,
! eval_const_expressions_context *context)
{
+ bool isCachable = true;
+
+ if (node == NULL)
+ return NULL;
+
+ node = const_expressions_mutator(node, context, &isCachable);
+ if (isCachable)
+ node = (Node *) insert_cache((Expr *) node);
+
+ return node;
+ }
+
+ /*
+ * Returns a mutated node tree and determines its cachability.
+ *
+ * The caller must make sure that cachable points to a boolean value that's
+ * initialized to TRUE.
+ */
+ static Node *
+ const_expressions_mutator(Node *node,
+ eval_const_expressions_context *context,
+ bool *cachable)
+ {
+ Assert(*cachable == true);
+
if (node == NULL)
return NULL;
switch (nodeTag(node))
*************** eval_const_expressions_mutator(Node *nod
*** 2252,2257 ****
--- 2302,2315 ----
{
Param *param = (Param *) node;
+ /*
+ * Only externally-supplied parameters are stable. Other
+ * params are used for passing changing values within the
+ * executor
+ */
+ if (param->paramkind != PARAM_EXTERN)
+ *cachable = false;
+
/* Look to see if we've been given a value for this Param */
if (param->paramkind == PARAM_EXTERN &&
context->boundParams != NULL &&
*************** eval_const_expressions_mutator(Node *nod
*** 2303,2329 ****
case T_FuncExpr:
{
FuncExpr *expr = (FuncExpr *) node;
! List *args;
! bool has_named_args;
Expr *simple;
FuncExpr *newexpr;
- ListCell *lc;
-
- /*
- * Reduce constants in the FuncExpr's arguments, and check to
- * see if there are any named args.
- */
- args = NIL;
- has_named_args = false;
- foreach(lc, expr->args)
- {
- Node *arg = (Node *) lfirst(lc);
-
- arg = eval_const_expressions_mutator(arg, context);
- if (IsA(arg, NamedArgExpr))
- has_named_args = true;
- args = lappend(args, arg);
- }
/*
* Code for op/func reduction is pretty bulky, so split it out
--- 2361,2369 ----
case T_FuncExpr:
{
FuncExpr *expr = (FuncExpr *) node;
! List *args = expr->args;
Expr *simple;
FuncExpr *newexpr;
/*
* Code for op/func reduction is pretty bulky, so split it out
*************** eval_const_expressions_mutator(Node *nod
*** 2339,2347 ****
expr->funccollid,
expr->inputcollid,
&args,
- has_named_args,
true,
! context);
if (simple) /* successfully simplified it */
return (Node *) simple;
--- 2379,2387 ----
expr->funccollid,
expr->inputcollid,
&args,
true,
! context,
! cachable);
if (simple) /* successfully simplified it */
return (Node *) simple;
*************** eval_const_expressions_mutator(Node *nod
*** 2365,2385 ****
case T_OpExpr:
{
OpExpr *expr = (OpExpr *) node;
! List *args;
Expr *simple;
OpExpr *newexpr;
/*
- * Reduce constants in the OpExpr's arguments. We know args
- * is either NIL or a List node, so we can call
- * expression_tree_mutator directly rather than recursing to
- * self.
- */
- args = (List *) expression_tree_mutator((Node *) expr->args,
- eval_const_expressions_mutator,
- (void *) context);
-
- /*
* Need to get OID of underlying function. Okay to scribble
* on input to this extent.
*/
--- 2405,2415 ----
case T_OpExpr:
{
OpExpr *expr = (OpExpr *) node;
! List *args = expr->args;
Expr *simple;
OpExpr *newexpr;
/*
* Need to get OID of underlying function. Okay to scribble
* on input to this extent.
*/
*************** eval_const_expressions_mutator(Node *nod
*** 2395,2401 ****
expr->opcollid,
expr->inputcollid,
&args,
! false, true, context);
if (simple) /* successfully simplified it */
return (Node *) simple;
--- 2425,2432 ----
expr->opcollid,
expr->inputcollid,
&args,
! true, context,
! cachable);
if (simple) /* successfully simplified it */
return (Node *) simple;
*************** eval_const_expressions_mutator(Node *nod
*** 2432,2466 ****
case T_DistinctExpr:
{
DistinctExpr *expr = (DistinctExpr *) node;
! List *args;
! ListCell *arg;
bool has_null_input = false;
bool all_null_input = true;
bool has_nonconst_input = false;
Expr *simple;
DistinctExpr *newexpr;
/*
! * Reduce constants in the DistinctExpr's arguments. We know
! * args is either NIL or a List node, so we can call
! * expression_tree_mutator directly rather than recursing to
! * self.
*/
! args = (List *) expression_tree_mutator((Node *) expr->args,
! eval_const_expressions_mutator,
! (void *) context);
/*
* We must do our own check for NULLs because DistinctExpr has
* different results for NULL input than the underlying
* operator does.
*/
! foreach(arg, args)
{
! if (IsA(lfirst(arg), Const))
{
! has_null_input |= ((Const *) lfirst(arg))->constisnull;
! all_null_input &= ((Const *) lfirst(arg))->constisnull;
}
else
has_nonconst_input = true;
--- 2463,2511 ----
case T_DistinctExpr:
{
DistinctExpr *expr = (DistinctExpr *) node;
! List *args = NIL;
! ListCell *lc;
! Node *arg;
bool has_null_input = false;
bool all_null_input = true;
bool has_nonconst_input = false;
Expr *simple;
DistinctExpr *newexpr;
+ bool leftCachable = true;
+ bool rightCachable = true;
/*
! * Reduce constants in the DistinctExpr's arguments
! *
! * Note that simplify_function() might call the mutator
! * function on arguments for a second time. However, this is
! * harmless because it's only called when arguments are
! * constant.
*/
! Assert(list_length(expr->args) == 2);
!
! arg = const_expressions_mutator(linitial(expr->args),
! context,
! &leftCachable);
! args = lappend(args, arg);
!
! arg = const_expressions_mutator(lsecond(expr->args),
! context,
! &rightCachable);
! args = lappend(args, arg);
/*
* We must do our own check for NULLs because DistinctExpr has
* different results for NULL input than the underlying
* operator does.
*/
! foreach(lc, args)
{
! arg = lfirst(lc);
! if (IsA(arg, Const))
{
! has_null_input |= ((Const *) arg)->constisnull;
! all_null_input &= ((Const *) arg)->constisnull;
}
else
has_nonconst_input = true;
*************** eval_const_expressions_mutator(Node *nod
*** 2497,2503 ****
expr->opcollid,
expr->inputcollid,
&args,
! false, false, context);
if (simple) /* successfully simplified it */
{
/*
--- 2542,2550 ----
expr->opcollid,
expr->inputcollid,
&args,
! false, context,
! cachable);
!
if (simple) /* successfully simplified it */
{
/*
*************** eval_const_expressions_mutator(Node *nod
*** 2512,2517 ****
--- 2559,2590 ----
return (Node *) csimple;
}
}
+ else if (!leftCachable || !rightCachable)
+ {
+ *cachable = false;
+ }
+ else
+ {
+ /*
+ * This expression is only cachable if the equality
+ * operator is not volatile.
+ */
+ HeapTuple func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(expr->opfuncid));
+ Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
+
+ if (funcform->provolatile == PROVOLATILE_VOLATILE)
+ *cachable = false;
+
+ ReleaseSysCache(func_tuple);
+ }
+
+ if (!(*cachable))
+ {
+ if (leftCachable)
+ linitial(args) = insert_cache((Expr *) linitial(args));
+ if (rightCachable)
+ lsecond(args) = insert_cache((Expr *) lsecond(args));
+ }
/*
* The expression cannot be simplified any further, so build
*************** eval_const_expressions_mutator(Node *nod
*** 2544,2550 ****
newargs = simplify_or_arguments(expr->args,
context,
&haveNull,
! &forceTrue);
if (forceTrue)
return makeBoolConst(true, false);
if (haveNull)
--- 2617,2624 ----
newargs = simplify_or_arguments(expr->args,
context,
&haveNull,
! &forceTrue,
! cachable);
if (forceTrue)
return makeBoolConst(true, false);
if (haveNull)
*************** eval_const_expressions_mutator(Node *nod
*** 2572,2578 ****
newargs = simplify_and_arguments(expr->args,
context,
&haveNull,
! &forceFalse);
if (forceFalse)
return makeBoolConst(false, false);
if (haveNull)
--- 2646,2653 ----
newargs = simplify_and_arguments(expr->args,
context,
&haveNull,
! &forceFalse,
! cachable);
if (forceFalse)
return makeBoolConst(false, false);
if (haveNull)
*************** eval_const_expressions_mutator(Node *nod
*** 2596,2603 ****
Node *arg;
Assert(list_length(expr->args) == 1);
! arg = eval_const_expressions_mutator(linitial(expr->args),
! context);
/*
* Use negate_clause() to see if we can simplify
--- 2671,2679 ----
Node *arg;
Assert(list_length(expr->args) == 1);
! arg = const_expressions_mutator(linitial(expr->args),
! context,
! cachable);
/*
* Use negate_clause() to see if we can simplify
*************** eval_const_expressions_mutator(Node *nod
*** 2621,2626 ****
--- 2697,2703 ----
* XXX should we ereport() here instead? Probably this routine
* should never be invoked after SubPlan creation.
*/
+ *cachable = false;
return node;
case T_RelabelType:
{
*************** eval_const_expressions_mutator(Node *nod
*** 2633,2640 ****
RelabelType *relabel = (RelabelType *) node;
Node *arg;
! arg = eval_const_expressions_mutator((Node *) relabel->arg,
! context);
/*
* If we find stacked RelabelTypes (eg, from foo :: int ::
--- 2710,2718 ----
RelabelType *relabel = (RelabelType *) node;
Node *arg;
! arg = const_expressions_mutator((Node *) relabel->arg,
! context,
! cachable);
/*
* If we find stacked RelabelTypes (eg, from foo :: int ::
*************** eval_const_expressions_mutator(Node *nod
*** 2668,2674 ****
case T_CoerceViaIO:
{
CoerceViaIO *expr = (CoerceViaIO *) node;
- Expr *arg;
List *args;
Oid outfunc;
bool outtypisvarlena;
--- 2746,2751 ----
*************** eval_const_expressions_mutator(Node *nod
*** 2680,2688 ****
/*
* Reduce constants in the CoerceViaIO's argument.
*/
! arg = (Expr *) eval_const_expressions_mutator((Node *) expr->arg,
! context);
! args = list_make1(arg);
/*
* CoerceViaIO represents calling the source type's output
--- 2757,2763 ----
/*
* Reduce constants in the CoerceViaIO's argument.
*/
! args = list_make1(expr->arg);
/*
* CoerceViaIO represents calling the source type's output
*************** eval_const_expressions_mutator(Node *nod
*** 2693,2699 ****
* Note that the coercion functions are assumed not to care
* about input collation, so we just pass InvalidOid for that.
*/
! getTypeOutputInfo(exprType((Node *) arg),
&outfunc, &outtypisvarlena);
getTypeInputInfo(expr->resulttype,
&infunc, &intypioparam);
--- 2768,2774 ----
* Note that the coercion functions are assumed not to care
* about input collation, so we just pass InvalidOid for that.
*/
! getTypeOutputInfo(exprType((Node *) expr->arg),
&outfunc, &outtypisvarlena);
getTypeInputInfo(expr->resulttype,
&infunc, &intypioparam);
*************** eval_const_expressions_mutator(Node *nod
*** 2704,2710 ****
InvalidOid,
InvalidOid,
&args,
! false, true, context);
if (simple) /* successfully simplified output fn */
{
/*
--- 2779,2786 ----
InvalidOid,
InvalidOid,
&args,
! true, context,
! cachable);
if (simple) /* successfully simplified output fn */
{
/*
*************** eval_const_expressions_mutator(Node *nod
*** 2734,2740 ****
expr->resultcollid,
InvalidOid,
&args,
! false, true, context);
if (simple) /* successfully simplified input fn */
return (Node *) simple;
}
--- 2810,2817 ----
expr->resultcollid,
InvalidOid,
&args,
! true, context,
! cachable);
if (simple) /* successfully simplified input fn */
return (Node *) simple;
}
*************** eval_const_expressions_mutator(Node *nod
*** 2745,2751 ****
* possibly-simplified argument.
*/
newexpr = makeNode(CoerceViaIO);
! newexpr->arg = arg;
newexpr->resulttype = expr->resulttype;
newexpr->resultcollid = expr->resultcollid;
newexpr->coerceformat = expr->coerceformat;
--- 2822,2828 ----
* possibly-simplified argument.
*/
newexpr = makeNode(CoerceViaIO);
! newexpr->arg = (Expr *) linitial(args);
newexpr->resulttype = expr->resulttype;
newexpr->resultcollid = expr->resultcollid;
newexpr->coerceformat = expr->coerceformat;
*************** eval_const_expressions_mutator(Node *nod
*** 2762,2769 ****
* Reduce constants in the ArrayCoerceExpr's argument, then
* build a new ArrayCoerceExpr.
*/
! arg = (Expr *) eval_const_expressions_mutator((Node *) expr->arg,
! context);
newexpr = makeNode(ArrayCoerceExpr);
newexpr->arg = arg;
--- 2839,2847 ----
* Reduce constants in the ArrayCoerceExpr's argument, then
* build a new ArrayCoerceExpr.
*/
! arg = (Expr *) const_expressions_mutator((Node *) expr->arg,
! context,
! cachable);
newexpr = makeNode(ArrayCoerceExpr);
newexpr->arg = arg;
*************** eval_const_expressions_mutator(Node *nod
*** 2786,2791 ****
--- 2864,2879 ----
newexpr->resulttype,
newexpr->resulttypmod,
newexpr->resultcollid);
+ /*
+ * If the argument is cachable, but conversion isn't, insert a
+ * CacheExpr above the argument
+ */
+ if (arg && *cachable && (OidIsValid(newexpr->elemfuncid) &&
+ func_volatile(newexpr->elemfuncid) == PROVOLATILE_VOLATILE))
+ {
+ *cachable = false;
+ newexpr->arg = insert_cache(arg);
+ }
/* Else we must return the partially-simplified node */
return (Node *) newexpr;
*************** eval_const_expressions_mutator(Node *nod
*** 2803,2810 ****
CollateExpr *collate = (CollateExpr *) node;
Node *arg;
! arg = eval_const_expressions_mutator((Node *) collate->arg,
! context);
if (arg && IsA(arg, Const))
{
--- 2891,2899 ----
CollateExpr *collate = (CollateExpr *) node;
Node *arg;
! arg = const_expressions_mutator((Node *) collate->arg,
! context,
! cachable);
if (arg && IsA(arg, Const))
{
*************** eval_const_expressions_mutator(Node *nod
*** 2873,2885 ****
Node *save_case_val;
Node *newarg;
List *newargs;
bool const_true_cond;
Node *defresult = NULL;
ListCell *arg;
/* Simplify the test expression, if any */
! newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
! context);
/* Set up for contained CaseTestExpr nodes */
save_case_val = context->case_val;
--- 2962,2975 ----
Node *save_case_val;
Node *newarg;
List *newargs;
+ List *cachable_args = NIL;
bool const_true_cond;
Node *defresult = NULL;
ListCell *arg;
/* Simplify the test expression, if any */
! newarg = caching_const_expressions_mutator((Node *) caseexpr->arg,
! context);
/* Set up for contained CaseTestExpr nodes */
save_case_val = context->case_val;
*************** eval_const_expressions_mutator(Node *nod
*** 2899,2910 ****
CaseWhen *oldcasewhen = (CaseWhen *) lfirst(arg);
Node *casecond;
Node *caseresult;
Assert(IsA(oldcasewhen, CaseWhen));
/* Simplify this alternative's test condition */
! casecond = eval_const_expressions_mutator((Node *) oldcasewhen->expr,
! context);
/*
* If the test condition is constant FALSE (or NULL), then
--- 2989,3003 ----
CaseWhen *oldcasewhen = (CaseWhen *) lfirst(arg);
Node *casecond;
Node *caseresult;
+ bool condCachable = true;
+ bool resultCachable = true;
Assert(IsA(oldcasewhen, CaseWhen));
/* Simplify this alternative's test condition */
! casecond = const_expressions_mutator((Node *) oldcasewhen->expr,
! context,
! &condCachable);
/*
* If the test condition is constant FALSE (or NULL), then
*************** eval_const_expressions_mutator(Node *nod
*** 2923,2930 ****
}
/* Simplify this alternative's result value */
! caseresult = eval_const_expressions_mutator((Node *) oldcasewhen->result,
! context);
/* If non-constant test condition, emit a new WHEN node */
if (!const_true_cond)
--- 3016,3024 ----
}
/* Simplify this alternative's result value */
! caseresult = const_expressions_mutator((Node *) oldcasewhen->result,
! context,
! &resultCachable);
/* If non-constant test condition, emit a new WHEN node */
if (!const_true_cond)
*************** eval_const_expressions_mutator(Node *nod
*** 2935,2940 ****
--- 3029,3051 ----
newcasewhen->result = (Expr *) caseresult;
newcasewhen->location = oldcasewhen->location;
newargs = lappend(newargs, newcasewhen);
+
+ if (condCachable)
+ {
+ if (is_cache_useful((Expr *) casecond))
+ cachable_args = lappend(cachable_args, &newcasewhen->expr);
+ }
+ else
+ *cachable = false;
+
+ if (resultCachable)
+ {
+ if (is_cache_useful((Expr *) caseresult))
+ cachable_args = lappend(cachable_args, &newcasewhen->result);
+ }
+ else
+ *cachable = false;
+
continue;
}
*************** eval_const_expressions_mutator(Node *nod
*** 2949,2956 ****
/* Simplify the default result, unless we replaced it above */
if (!const_true_cond)
! defresult = eval_const_expressions_mutator((Node *) caseexpr->defresult,
! context);
context->case_val = save_case_val;
--- 3060,3080 ----
/* Simplify the default result, unless we replaced it above */
if (!const_true_cond)
! {
! bool isCachable = true;
!
! defresult = const_expressions_mutator((Node *) caseexpr->defresult,
! context,
! &isCachable);
!
! if (isCachable)
! {
! if (is_cache_useful((Expr *) defresult))
! cachable_args = lappend(cachable_args, &defresult);
! }
! else
! *cachable = false;
! }
context->case_val = save_case_val;
*************** eval_const_expressions_mutator(Node *nod
*** 2960,2965 ****
--- 3084,3102 ----
*/
if (newargs == NIL)
return defresult;
+
+ if (!(*cachable))
+ {
+ ListCell *lc;
+
+ foreach(lc, cachable_args)
+ {
+ Expr **arg = (Expr **) lfirst(lc);
+
+ *arg = (Expr *) makeCacheExpr(*arg);
+ }
+ }
+
/* Otherwise we need a new CASE node */
newcase = makeNode(CaseExpr);
newcase->casetype = caseexpr->casetype;
*************** eval_const_expressions_mutator(Node *nod
*** 2980,2986 ****
--- 3117,3126 ----
if (context->case_val)
return copyObject(context->case_val);
else
+ {
+ *cachable = false;
return copyObject(node);
+ }
}
case T_ArrayExpr:
{
*************** eval_const_expressions_mutator(Node *nod
*** 2990,3002 ****
List *newelems;
ListCell *element;
newelems = NIL;
foreach(element, arrayexpr->elements)
{
Node *e;
! e = eval_const_expressions_mutator((Node *) lfirst(element),
! context);
if (!IsA(e, Const))
all_const = false;
newelems = lappend(newelems, e);
--- 3130,3144 ----
List *newelems;
ListCell *element;
+ *cachable = false; /* Not implemented */
+
newelems = NIL;
foreach(element, arrayexpr->elements)
{
Node *e;
! e = caching_const_expressions_mutator((Node *) lfirst(element),
! context);
if (!IsA(e, Const))
all_const = false;
newelems = lappend(newelems, e);
*************** eval_const_expressions_mutator(Node *nod
*** 3023,3037 ****
CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
CoalesceExpr *newcoalesce;
List *newargs;
! ListCell *arg;
newargs = NIL;
! foreach(arg, coalesceexpr->args)
{
! Node *e;
! e = eval_const_expressions_mutator((Node *) lfirst(arg),
! context);
/*
* We can remove null constants from the list. For a
--- 3165,3182 ----
CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
CoalesceExpr *newcoalesce;
List *newargs;
! List *cachable_args = NIL;
! ListCell *lc;
newargs = NIL;
! foreach(lc, coalesceexpr->args)
{
! Node *arg = lfirst(lc);
! bool isCachable = true;
! arg = const_expressions_mutator((Node *) arg,
! context,
! &isCachable);
/*
* We can remove null constants from the list. For a
*************** eval_const_expressions_mutator(Node *nod
*** 3041,3056 ****
* drop following arguments since they will never be
* reached.
*/
! if (IsA(e, Const))
{
! if (((Const *) e)->constisnull)
continue; /* drop null constant */
if (newargs == NIL)
! return e; /* first expr */
! newargs = lappend(newargs, e);
break;
}
! newargs = lappend(newargs, e);
}
/*
--- 3186,3210 ----
* drop following arguments since they will never be
* reached.
*/
! if (IsA(arg, Const))
{
! if (((Const *) arg)->constisnull)
continue; /* drop null constant */
if (newargs == NIL)
! return arg; /* first expr */
! newargs = lappend(newargs, arg);
break;
}
!
! newargs = lappend(newargs, arg);
!
! if (isCachable)
! {
! if (is_cache_useful((Expr *) arg))
! cachable_args = lappend(cachable_args, &llast(newargs));
! }
! else
! *cachable = false;
}
/*
*************** eval_const_expressions_mutator(Node *nod
*** 3061,3066 ****
--- 3215,3229 ----
return (Node *) makeNullConst(coalesceexpr->coalescetype,
-1,
coalesceexpr->coalescecollid);
+ if (!(*cachable))
+ {
+ foreach(lc, cachable_args)
+ {
+ Expr **arg = (Expr **) lfirst(lc);
+
+ *arg = (Expr *) makeCacheExpr(*arg);
+ }
+ }
newcoalesce = makeNode(CoalesceExpr);
newcoalesce->coalescetype = coalesceexpr->coalescetype;
*************** eval_const_expressions_mutator(Node *nod
*** 3082,3094 ****
* is still the same as when the FieldSelect was created ---
* this can change if someone did ALTER COLUMN TYPE on the
* rowtype.
*/
FieldSelect *fselect = (FieldSelect *) node;
FieldSelect *newfselect;
Node *arg;
! arg = eval_const_expressions_mutator((Node *) fselect->arg,
! context);
if (arg && IsA(arg, Var) &&
((Var *) arg)->varattno == InvalidAttrNumber)
{
--- 3245,3262 ----
* is still the same as when the FieldSelect was created ---
* this can change if someone did ALTER COLUMN TYPE on the
* rowtype.
+ *
+ * This is never cachable because Var references aren't
+ * constants. simplify_function() also refuses caching of
+ * row-returning functions
*/
FieldSelect *fselect = (FieldSelect *) node;
FieldSelect *newfselect;
Node *arg;
! arg = const_expressions_mutator((Node *) fselect->arg,
! context,
! cachable);
if (arg && IsA(arg, Var) &&
((Var *) arg)->varattno == InvalidAttrNumber)
{
*************** eval_const_expressions_mutator(Node *nod
*** 3139,3146 ****
NullTest *newntest;
Node *arg;
! arg = eval_const_expressions_mutator((Node *) ntest->arg,
! context);
if (arg && IsA(arg, RowExpr))
{
/*
--- 3307,3315 ----
NullTest *newntest;
Node *arg;
! arg = const_expressions_mutator((Node *) ntest->arg,
! context,
! cachable);
if (arg && IsA(arg, RowExpr))
{
/*
*************** eval_const_expressions_mutator(Node *nod
*** 3223,3230 ****
BooleanTest *newbtest;
Node *arg;
! arg = eval_const_expressions_mutator((Node *) btest->arg,
! context);
if (arg && IsA(arg, Const))
{
Const *carg = (Const *) arg;
--- 3392,3400 ----
BooleanTest *newbtest;
Node *arg;
! arg = const_expressions_mutator((Node *) btest->arg,
! context,
! cachable);
if (arg && IsA(arg, Const))
{
Const *carg = (Const *) arg;
*************** eval_const_expressions_mutator(Node *nod
*** 3270,3276 ****
return (Node *) newbtest;
}
case T_PlaceHolderVar:
-
/*
* In estimation mode, just strip the PlaceHolderVar node
* altogether; this amounts to estimating that the contained value
--- 3440,3445 ----
*************** eval_const_expressions_mutator(Node *nod
*** 3278,3292 ****
* just use the default behavior (ie, simplify the expression but
* leave the PlaceHolderVar node intact).
*/
if (context->estimate)
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
! return eval_const_expressions_mutator((Node *) phv->phexpr,
! context);
}
break;
default:
break;
}
--- 3447,3483 ----
* just use the default behavior (ie, simplify the expression but
* leave the PlaceHolderVar node intact).
*/
+ *cachable = false;
+
if (context->estimate)
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
+ bool isCachable = true; /* ignored */
! return const_expressions_mutator((Node *) phv->phexpr,
! context,
! &isCachable);
! }
! break;
! case T_Const:
! /* Keep *cachable=true */
! break;
! case T_CacheExpr:
! /* We already have CacheExpr in the appropriate place.
! * In estimation mode, strip CacheExpr nodes here to simplify
! * selectivity estimation functions.
! */
! *cachable = false;
! if (context->estimate)
! {
! CacheExpr *cache = (CacheExpr *) node;
! bool isCachable = true;
! return const_expressions_mutator((Node *) cache->arg, context, &isCachable);
}
break;
default:
+ /* Everything else is not cachable */
+ *cachable = false;
break;
}
*************** eval_const_expressions_mutator(Node *nod
*** 3294,3303 ****
* For any node type not handled above, we recurse using
* expression_tree_mutator, which will copy the node unchanged but try to
* simplify its arguments (if any) using this routine. For example: we
! * cannot eliminate an ArrayRef node, but we might be able to simplify
! * constant expressions in its subscripts.
*/
! return expression_tree_mutator(node, eval_const_expressions_mutator,
(void *) context);
}
--- 3485,3495 ----
* For any node type not handled above, we recurse using
* expression_tree_mutator, which will copy the node unchanged but try to
* simplify its arguments (if any) using this routine. For example: we
! * cannot eliminate an ArrayRef node, but we might be able to simplify or
! * cache constant expressions in its subscripts.
*/
! return expression_tree_mutator(node,
! caching_const_expressions_mutator,
(void *) context);
}
*************** eval_const_expressions_mutator(Node *nod
*** 3319,3331 ****
* The output arguments *haveNull and *forceTrue must be initialized FALSE
* by the caller. They will be set TRUE if a null constant or true constant,
* respectively, is detected anywhere in the argument list.
*/
static List *
simplify_or_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceTrue)
{
! List *newargs = NIL;
List *unprocessed_args;
/*
--- 3511,3533 ----
* The output arguments *haveNull and *forceTrue must be initialized FALSE
* by the caller. They will be set TRUE if a null constant or true constant,
* respectively, is detected anywhere in the argument list.
+ *
+ * We divide elements into two separate lists, one for cachable items and one
+ * for non-cachable items. Upon returning, the cachable sub-list is turned
+ * into a new BoolExpr, cached and prepended. This is done in hopes that the
+ * cachable sub-list is faster to evaluate and short-cicruits the rest of the
+ * expression.
+ *
+ * Input: (cachable OR uncachable OR cachable OR uncachable)
+ * Output: (CACHE(cachable OR cachable) OR uncachable OR uncachable)
*/
static List *
simplify_or_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceTrue, bool *cachable)
{
! List *nocache_args = NIL;
! List *cachable_args = NIL;
List *unprocessed_args;
/*
*************** simplify_or_arguments(List *args,
*** 3340,3345 ****
--- 3542,3548 ----
while (unprocessed_args)
{
Node *arg = (Node *) linitial(unprocessed_args);
+ bool isCachable = true;
unprocessed_args = list_delete_first(unprocessed_args);
*************** simplify_or_arguments(List *args,
*** 3362,3368 ****
}
/* If it's not an OR, simplify it */
! arg = eval_const_expressions_mutator(arg, context);
/*
* It is unlikely but not impossible for simplification of a non-OR
--- 3565,3571 ----
}
/* If it's not an OR, simplify it */
! arg = const_expressions_mutator(arg, context, &isCachable);
/*
* It is unlikely but not impossible for simplification of a non-OR
*************** simplify_or_arguments(List *args,
*** 3404,3413 ****
}
/* else emit the simplified arg into the result list */
! newargs = lappend(newargs, arg);
}
! return newargs;
}
/*
--- 3607,3646 ----
}
/* else emit the simplified arg into the result list */
! if (isCachable)
! cachable_args = lappend(cachable_args, arg);
! else
! nocache_args = lappend(nocache_args, arg);
}
! if (cachable_args && nocache_args)
! {
! Expr *arg;
!
! /* Build a new expression for cachable sub-list */
! if (list_length(cachable_args) == 1)
! arg = linitial(cachable_args);
! else
! arg = makeBoolExpr(OR_EXPR, cachable_args, -1);
!
! arg = insert_cache(arg);
!
! /*
! * Assume that the cachable expression is cheaper to evaluate, so put
! * it first
! */
! nocache_args = lcons(arg, nocache_args);
!
! *cachable = false;
! return nocache_args;
! }
! else if (nocache_args)
! {
! *cachable = false;
! return nocache_args;
! }
! else
! return cachable_args;
}
/*
*************** simplify_or_arguments(List *args,
*** 3428,3440 ****
* The output arguments *haveNull and *forceFalse must be initialized FALSE
* by the caller. They will be set TRUE if a null constant or false constant,
* respectively, is detected anywhere in the argument list.
*/
static List *
simplify_and_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceFalse)
{
! List *newargs = NIL;
List *unprocessed_args;
/* See comments in simplify_or_arguments */
--- 3661,3683 ----
* The output arguments *haveNull and *forceFalse must be initialized FALSE
* by the caller. They will be set TRUE if a null constant or false constant,
* respectively, is detected anywhere in the argument list.
+ *
+ * We divide elements into two separate lists, one for cachable items and one
+ * for non-cachable items. Upon returning, the cachable sub-list is turned
+ * into a new BoolExpr, cached and prepended. This is done in hopes that the
+ * cachable sub-list is faster to evaluate and short-cicruits the rest of the
+ * expression.
+ *
+ * Input: (cachable OR uncachable OR cachable OR uncachable)
+ * Output: (CACHE(cachable OR cachable) OR uncachable OR uncachable)
*/
static List *
simplify_and_arguments(List *args,
eval_const_expressions_context *context,
! bool *haveNull, bool *forceFalse, bool *cachable)
{
! List *nocache_args = NIL;
! List *cachable_args = NIL;
List *unprocessed_args;
/* See comments in simplify_or_arguments */
*************** simplify_and_arguments(List *args,
*** 3442,3447 ****
--- 3685,3691 ----
while (unprocessed_args)
{
Node *arg = (Node *) linitial(unprocessed_args);
+ bool isCachable = true;
unprocessed_args = list_delete_first(unprocessed_args);
*************** simplify_and_arguments(List *args,
*** 3464,3470 ****
}
/* If it's not an AND, simplify it */
! arg = eval_const_expressions_mutator(arg, context);
/*
* It is unlikely but not impossible for simplification of a non-AND
--- 3708,3714 ----
}
/* If it's not an AND, simplify it */
! arg = const_expressions_mutator(arg, context, &isCachable);
/*
* It is unlikely but not impossible for simplification of a non-AND
*************** simplify_and_arguments(List *args,
*** 3506,3515 ****
}
/* else emit the simplified arg into the result list */
! newargs = lappend(newargs, arg);
}
! return newargs;
}
/*
--- 3750,3789 ----
}
/* else emit the simplified arg into the result list */
! if (isCachable)
! cachable_args = lappend(cachable_args, arg);
! else
! nocache_args = lappend(nocache_args, arg);
}
! if (cachable_args && nocache_args)
! {
! Expr *arg;
!
! /* Build a new expression for cachable sub-list */
! if (list_length(cachable_args) == 1)
! arg = linitial(cachable_args);
! else
! arg = makeBoolExpr(AND_EXPR, cachable_args, -1);
!
! arg = insert_cache(arg);
!
! /*
! * Assume that the cachable expression is cheaper to evaluate, so put
! * it first
! */
! nocache_args = lcons(arg, nocache_args);
!
! *cachable = false;
! return nocache_args;
! }
! else if (nocache_args)
! {
! *cachable = false;
! return nocache_args;
! }
! else
! return cachable_args;
}
/*
*************** simplify_boolean_equality(Oid opno, List
*** 3584,3590 ****
* Inputs are the original expression (can be NULL), function OID, actual
* result type OID (which is needed for polymorphic functions), result typmod,
* result collation, the input collation to use for the function, the
! * pre-simplified argument list, and some flags; also the context data for
* eval_const_expressions. In common cases, several of the arguments could be
* derived from the original expression. Sending them separately avoids
* duplicating NodeTag-specific knowledge, and it's necessary for CoerceViaIO.
--- 3858,3864 ----
* Inputs are the original expression (can be NULL), function OID, actual
* result type OID (which is needed for polymorphic functions), result typmod,
* result collation, the input collation to use for the function, the
! * un-simplified argument list, and some flags; also the context data for
* eval_const_expressions. In common cases, several of the arguments could be
* derived from the original expression. Sending them separately avoids
* duplicating NodeTag-specific knowledge, and it's necessary for CoerceViaIO.
*************** simplify_boolean_equality(Oid opno, List
*** 3603,3616 ****
static Expr *
simplify_function(Expr *oldexpr, Oid funcid,
Oid result_type, int32 result_typmod, Oid result_collid,
! Oid input_collid, List **args,
! bool has_named_args,
bool allow_inline,
! eval_const_expressions_context *context)
{
HeapTuple func_tuple;
Expr *newexpr;
Oid transform;
/*
* We have three strategies for simplification: execute the function to
--- 3877,3893 ----
static Expr *
simplify_function(Expr *oldexpr, Oid funcid,
Oid result_type, int32 result_typmod, Oid result_collid,
! Oid input_collid, List **old_args,
bool allow_inline,
! eval_const_expressions_context *context,
! bool *cachable)
{
HeapTuple func_tuple;
Expr *newexpr;
Oid transform;
+ ListCell *lc;
+ List *args = NIL;
+ List *cachable_args = NIL;
/*
* We have three strategies for simplification: execute the function to
*************** simplify_function(Expr *oldexpr, Oid fun
*** 3624,3642 ****
if (!HeapTupleIsValid(func_tuple))
elog(ERROR, "cache lookup failed for function %u", funcid);
/*
! * While we have the tuple, reorder named arguments and add default
! * arguments if needed.
*/
- if (has_named_args)
- *args = reorder_function_arguments(*args, result_type, func_tuple,
- context);
- else if (((Form_pg_proc) GETSTRUCT(func_tuple))->pronargs > list_length(*args))
- *args = add_function_defaults(*args, result_type, func_tuple, context);
-
newexpr = evaluate_function(funcid, result_type, result_typmod,
! result_collid, input_collid, *args,
! func_tuple, context);
/*
* Some functions calls can be simplified at plan time based on properties
--- 3901,3953 ----
if (!HeapTupleIsValid(func_tuple))
elog(ERROR, "cache lookup failed for function %u", funcid);
+ if (oldexpr && IsA(oldexpr, FuncExpr))
+
+ /*
+ * Reorder named arguments and add defaults if needed. Returns a
+ * copied list, so we can mutate it later.
+ */
+ args = simplify_copy_function_arguments(*old_args, result_type, func_tuple);
+ else
+ /* Copy argument list before we start mutating it */
+ args = list_copy(*old_args);
+
+ /* Reduce constants in the FuncExpr's arguments */
+ foreach(lc, args)
+ {
+ Node *arg = (Node *) lfirst(lc);
+ bool isCachable = true;
+
+ arg = const_expressions_mutator(arg, context, &isCachable);
+ lfirst(lc) = arg;
+
+ /*
+ * We're stuck in a catch-22 here. If all arguments and the call
+ * itself is cachable, we don't want to insert cache nodes for
+ * arguments. But we don't know that until we walk through all the
+ * arguments.
+ *
+ * So we accumulate cachable arguments in a list of ListCell pointers,
+ * which we will update later if necessary.
+ *
+ * Note: The args list may not be mutated from here on this until we
+ * handle cachable_args below.
+ */
+ if (isCachable)
+ {
+ if (is_cache_useful((Expr *) arg))
+ cachable_args = lappend(cachable_args, &lfirst(lc));
+ }
+ else
+ *cachable = false; /* One bad arg spoils the whole cache */
+ }
+
/*
! * evaluate_function tells us about the cachability of the function call
*/
newexpr = evaluate_function(funcid, result_type, result_typmod,
! result_collid, input_collid, args,
! func_tuple, context, cachable);
/*
* Some functions calls can be simplified at plan time based on properties
*************** simplify_function(Expr *oldexpr, Oid fun
*** 3673,3702 ****
PointerGetDatum(oldexpr)));
if (!newexpr && allow_inline)
newexpr = inline_function(funcid, result_type, result_collid,
! input_collid, *args,
! func_tuple, context);
ReleaseSysCache(func_tuple);
return newexpr;
}
/*
* reorder_function_arguments: convert named-notation args to positional args
*
* This function also inserts default argument values as needed, since it's
* impossible to form a truly valid positional call without that.
*/
static List *
! reorder_function_arguments(List *args, Oid result_type, HeapTuple func_tuple,
! eval_const_expressions_context *context)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
int pronargs = funcform->pronargs;
int nargsprovided = list_length(args);
Node *argarray[FUNC_MAX_ARGS];
- Bitmapset *defargnumbers;
ListCell *lc;
int i;
--- 3984,4094 ----
PointerGetDatum(oldexpr)));
if (!newexpr && allow_inline)
+ {
+ /*
+ * The inlined expression may be cachable regardless of the above, if
+ * the function's volatility was mis-labeled or if volatile parts are
+ * removed (possible due to constant folding of conditionals).
+ *
+ * inline_function() also takes care of caching all cachable subtrees
+ */
+ bool isCachable = true;
+
newexpr = inline_function(funcid, result_type, result_collid,
! input_collid, args,
! func_tuple, context, &isCachable);
!
! if (newexpr)
! *cachable = isCachable;
! }
ReleaseSysCache(func_tuple);
+ /*
+ * If function call can't be cached/inlined, update all cachable arguments
+ */
+ if (!newexpr && !(*cachable))
+ {
+ foreach(lc, cachable_args)
+ {
+ Node **arg = (Node **) lfirst(lc);
+
+ *arg = (Node *) makeCacheExpr((Expr *) *arg);
+ }
+ }
+
+ /* Argument processing done, give it back to the caller */
+ *old_args = args;
+
return newexpr;
}
/*
+ * This function prepares a function's argument list -- converting
+ * named-notation argument list into positional notation while adding any
+ * needed default argument expressions.
+ *
+ * Always returns a copy of the argument list, the original list is not
+ * modified.
+ */
+ static List *
+ simplify_copy_function_arguments(List *old_args, Oid result_type,
+ HeapTuple func_tuple)
+ {
+ List *args = NIL;
+ ListCell *lc;
+ bool has_named_args = false;
+ int nargs_before;
+
+ /* Do we need to reorder named arguments? */
+ foreach(lc, old_args)
+ {
+ Node *arg = (Node *) lfirst(lc);
+
+ if (IsA(arg, NamedArgExpr))
+ {
+ has_named_args = true;
+ break;
+ }
+ }
+
+ nargs_before = list_length(old_args);
+
+ /*
+ * Reorder named arguments and add default arguments if needed.
+ */
+ if (has_named_args)
+ args = reorder_function_arguments(old_args, result_type, func_tuple);
+
+ else
+ {
+ args = list_copy(old_args);
+
+ /* Append missing default arguments to the list */
+ if (((Form_pg_proc) GETSTRUCT(func_tuple))->pronargs > list_length(args))
+ args = add_function_defaults(args, result_type, func_tuple);
+ }
+
+ if (list_length(args) != nargs_before)
+ /* Added defaults may need casts */
+ recheck_cast_function_args(args, result_type, func_tuple);
+
+ return args;
+ }
+
+ /*
* reorder_function_arguments: convert named-notation args to positional args
*
* This function also inserts default argument values as needed, since it's
* impossible to form a truly valid positional call without that.
*/
static List *
! reorder_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
int pronargs = funcform->pronargs;
int nargsprovided = list_length(args);
Node *argarray[FUNC_MAX_ARGS];
ListCell *lc;
int i;
*************** reorder_function_arguments(List *args, O
*** 3730,3736 ****
* Fetch default expressions, if needed, and insert into array at proper
* locations (they aren't necessarily consecutive or all used)
*/
- defargnumbers = NULL;
if (nargsprovided < pronargs)
{
List *defaults = fetch_function_defaults(func_tuple);
--- 4122,4127 ----
*************** reorder_function_arguments(List *args, O
*** 3739,3748 ****
foreach(lc, defaults)
{
if (argarray[i] == NULL)
- {
argarray[i] = (Node *) lfirst(lc);
- defargnumbers = bms_add_member(defargnumbers, i);
- }
i++;
}
}
--- 4130,4136 ----
*************** reorder_function_arguments(List *args, O
*** 3755,3786 ****
args = lappend(args, argarray[i]);
}
- /* Recheck argument types and add casts if needed */
- recheck_cast_function_args(args, result_type, func_tuple);
-
- /*
- * Lastly, we have to recursively simplify the defaults we just added (but
- * don't recurse on the args passed in, as we already did those). This
- * isn't merely an optimization, it's *necessary* since there could be
- * functions with named or defaulted arguments down in there.
- *
- * Note that we do this last in hopes of simplifying any typecasts that
- * were added by recheck_cast_function_args --- there shouldn't be any new
- * casts added to the explicit arguments, but casts on the defaults are
- * possible.
- */
- if (defargnumbers != NULL)
- {
- i = 0;
- foreach(lc, args)
- {
- if (bms_is_member(i, defargnumbers))
- lfirst(lc) = eval_const_expressions_mutator((Node *) lfirst(lc),
- context);
- i++;
- }
- }
-
return args;
}
--- 4143,4148 ----
*************** reorder_function_arguments(List *args, O
*** 3791,3810 ****
* and so we know we just need to add defaults at the end.
*/
static List *
! add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple,
! eval_const_expressions_context *context)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
- int nargsprovided = list_length(args);
List *defaults;
int ndelete;
- ListCell *lc;
/* Get all the default expressions from the pg_proc tuple */
defaults = fetch_function_defaults(func_tuple);
/* Delete any unused defaults from the list */
! ndelete = nargsprovided + list_length(defaults) - funcform->pronargs;
if (ndelete < 0)
elog(ERROR, "not enough default arguments");
while (ndelete-- > 0)
--- 4153,4169 ----
* and so we know we just need to add defaults at the end.
*/
static List *
! add_function_defaults(List *args, Oid result_type, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
List *defaults;
int ndelete;
/* Get all the default expressions from the pg_proc tuple */
defaults = fetch_function_defaults(func_tuple);
/* Delete any unused defaults from the list */
! ndelete = list_length(args) + list_length(defaults) - funcform->pronargs;
if (ndelete < 0)
elog(ERROR, "not enough default arguments");
while (ndelete-- > 0)
*************** add_function_defaults(List *args, Oid re
*** 3813,3840 ****
/* And form the combined argument list */
args = list_concat(args, defaults);
- /* Recheck argument types and add casts if needed */
- recheck_cast_function_args(args, result_type, func_tuple);
-
- /*
- * Lastly, we have to recursively simplify the defaults we just added (but
- * don't recurse on the args passed in, as we already did those). This
- * isn't merely an optimization, it's *necessary* since there could be
- * functions with named or defaulted arguments down in there.
- *
- * Note that we do this last in hopes of simplifying any typecasts that
- * were added by recheck_cast_function_args --- there shouldn't be any new
- * casts added to the explicit arguments, but casts on the defaults are
- * possible.
- */
- foreach(lc, args)
- {
- if (nargsprovided-- > 0)
- continue; /* skip original arg positions */
- lfirst(lc) = eval_const_expressions_mutator((Node *) lfirst(lc),
- context);
- }
-
return args;
}
--- 4172,4177 ----
*************** static Expr *
*** 3924,3930 ****
evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
bool has_nonconst_input = false;
--- 4261,4268 ----
evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context,
! bool *cachable)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
bool has_nonconst_input = false;
*************** evaluate_function(Oid funcid, Oid result
*** 3936,3942 ****
--- 4274,4283 ----
* Can't simplify if it returns a set.
*/
if (funcform->proretset)
+ {
+ *cachable = false;
return NULL;
+ }
/*
* Can't simplify if it returns RECORD. The immediate problem is that it
*************** evaluate_function(Oid funcid, Oid result
*** 3950,3956 ****
--- 4291,4303 ----
* gotchas, seems best to leave the function call unreduced.
*/
if (funcform->prorettype == RECORDOID)
+ {
+ *cachable = false;
return NULL;
+ }
+
+ if (funcform->provolatile == PROVOLATILE_VOLATILE)
+ *cachable = false;
/*
* Check for constant inputs and especially constant-NULL inputs.
*************** static Expr *
*** 4047,4053 ****
inline_function(Oid funcid, Oid result_type, Oid result_collid,
Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
char *src;
--- 4394,4401 ----
inline_function(Oid funcid, Oid result_type, Oid result_collid,
Oid input_collid, List *args,
HeapTuple func_tuple,
! eval_const_expressions_context *context,
! bool *cachable)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
char *src;
*************** inline_function(Oid funcid, Oid result_t
*** 4326,4332 ****
* the current function to the context list of active functions.
*/
context->active_fns = lcons_oid(funcid, context->active_fns);
! newexpr = eval_const_expressions_mutator(newexpr, context);
context->active_fns = list_delete_first(context->active_fns);
error_context_stack = sqlerrcontext.previous;
--- 4674,4680 ----
* the current function to the context list of active functions.
*/
context->active_fns = lcons_oid(funcid, context->active_fns);
! newexpr = const_expressions_mutator(newexpr, context, cachable);
context->active_fns = list_delete_first(context->active_fns);
error_context_stack = sqlerrcontext.previous;
*************** sql_inline_error_callback(void *arg)
*** 4406,4411 ****
--- 4754,4787 ----
}
/*
+ * Is it useful to cache this expression? Constants and param references are
+ * always fast to access so don't insert cache in front of those.
+ *
+ * Without inline, we lose almost 10% time in some very simple queries (!)
+ */
+ static inline bool
+ is_cache_useful(Expr *expr)
+ {
+ if (IsA(expr, Const))
+ return false;
+ if (IsA(expr, Param))
+ return false;
+ return true;
+ }
+
+ static Expr *
+ insert_cache(Expr *expr)
+ {
+ /* Don't cache obviously cheap expressions */
+ if (!is_cache_useful(expr))
+ return expr;
+
+ Assert(!IsA(expr, CacheExpr));
+
+ return (Expr *) makeCacheExpr(expr);
+ }
+
+ /*
* evaluate_expr: pre-evaluate a constant expression
*
* We use the executor's routine ExecEvalExpr() to avoid duplication of
*************** evaluate_expr(Expr *expr, Oid result_typ
*** 4438,4444 ****
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
! exprstate = ExecInitExpr(expr, NULL);
/*
* And evaluate it.
--- 4814,4820 ----
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
* because it'd result in recursively invoking eval_const_expressions.)
*/
! exprstate = ExecInitExpr(expr, NULL, false);
/*
* And evaluate it.
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
new file mode 100644
index c3161c5..f98854d
*** a/src/backend/optimizer/util/predtest.c
--- b/src/backend/optimizer/util/predtest.c
*************** btree_predicate_proof(Expr *predicate, N
*** 1486,1492 ****
fix_opfuncids((Node *) test_expr);
/* Prepare it for execution */
! test_exprstate = ExecInitExpr(test_expr, NULL);
/* And execute it. */
test_result = ExecEvalExprSwitchContext(test_exprstate,
--- 1486,1492 ----
fix_opfuncids((Node *) test_expr);
/* Prepare it for execution */
! test_exprstate = ExecInitExpr(test_expr, NULL, false);
/* And execute it. */
test_result = ExecEvalExprSwitchContext(test_exprstate,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
new file mode 100644
index 64ba8ec..e97d39f
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** isSimpleNode(Node *node, Node *parentNod
*** 4704,4711 ****
/* function-like: name(..) or name[..] */
return true;
! /* CASE keywords act as parentheses */
case T_CaseExpr:
return true;
case T_FieldSelect:
--- 4704,4715 ----
/* function-like: name(..) or name[..] */
return true;
! case T_CacheExpr:
! /* hidden from user */
! return true;
!
case T_CaseExpr:
+ /* CASE keywords act as parentheses */
return true;
case T_FieldSelect:
*************** get_rule_expr(Node *node, deparse_contex
*** 5826,5831 ****
--- 5830,5849 ----
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+
+ #ifdef DEBUG_CACHEEXPR
+ appendStringInfo(buf, "CACHE[");
+ get_rule_expr((Node *) cache->arg, context, true);
+ appendStringInfoChar(buf, ']');
+ #else
+ get_rule_expr((Node *) cache->arg, context, true);
+ #endif
+ }
+ break;
+
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
new file mode 100644
index 7f27669..fa08344
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
*************** extern Tuplestorestate *ExecMakeTableFun
*** 227,233 ****
bool randomAccess);
extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
! extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull);
extern int ExecTargetListLength(List *targetlist);
--- 227,233 ----
bool randomAccess);
extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
! extern ExprState *ExecInitExpr(Expr *node, PlanState *parent, bool cacheEnabled);
extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull);
extern int ExecTargetListLength(List *targetlist);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
new file mode 100644
index 5207102..a4701f2
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct FuncExprState
*** 674,679 ****
--- 674,696 ----
} FuncExprState;
/* ----------------
+ * CacheExprState node
+ *
+ * Takes care of caching execution-time constants that cannot be constant
+ * folded at plan-time.
+ * ----------------
+ */
+ typedef struct CacheExprState
+ {
+ ExprState xprstate;
+ ExprState *arg; /* state of sub-expression */
+
+ bool enabled; /* is cache enabled? */
+ Datum result; /* cached result */
+ bool isNull; /* is result NULL? */
+ } CacheExprState;
+
+ /* ----------------
* ScalarArrayOpExprState node
*
* This is a FuncExprState plus some additional data.
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
new file mode 100644
index a6dffe6..fa5d8e7
*** a/src/include/nodes/makefuncs.h
--- b/src/include/nodes/makefuncs.h
*************** extern TypeName *makeTypeNameFromOid(Oid
*** 74,79 ****
--- 74,80 ----
extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
Oid funccollid, Oid inputcollid, CoercionForm fformat);
+ extern CacheExpr *makeCacheExpr(Expr *arg);
extern DefElem *makeDefElem(char *name, Node *arg);
extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index 0e7d184..016a63c
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 172,177 ****
--- 172,178 ----
T_JoinExpr,
T_FromExpr,
T_IntoClause,
+ T_CacheExpr,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
*************** typedef enum NodeTag
*** 205,210 ****
--- 206,212 ----
T_NullTestState,
T_CoerceToDomainState,
T_DomainConstraintState,
+ T_CacheExprState,
/*
* TAGS FOR PLANNER NODES (relation.h)
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
new file mode 100644
index 50831ee..95908c5
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct BooleanTest
*** 1033,1038 ****
--- 1033,1053 ----
} BooleanTest;
/*
+ * CacheExpr
+ *
+ * CacheExpr is a constant expression to be cached at execution time. The
+ * eval_const_expressions() function inserts CacheExpr nodes nodes at
+ * strategic locations when it recognizes constant expressions that cannot be
+ * constant-folded at plan time, such as expressions with Param references,
+ * stable function and operator calls with constant arguments, etc
+ */
+ typedef struct CacheExpr
+ {
+ Expr xpr;
+ Expr *arg;
+ } CacheExpr;
+
+ /*
* CoerceToDomain
*
* CoerceToDomain represents the operation of coercing a value to a domain
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
new file mode 100644
index 0b126a3..58e4c15
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 4938,4944 ****
if (expr->expr_simple_lxid != curlxid)
{
oldcontext = MemoryContextSwitchTo(simple_eval_estate->es_query_cxt);
! expr->expr_simple_state = ExecInitExpr(expr->expr_simple_expr, NULL);
expr->expr_simple_in_use = false;
expr->expr_simple_lxid = curlxid;
MemoryContextSwitchTo(oldcontext);
--- 4938,4946 ----
if (expr->expr_simple_lxid != curlxid)
{
oldcontext = MemoryContextSwitchTo(simple_eval_estate->es_query_cxt);
! expr->expr_simple_state = ExecInitExpr(expr->expr_simple_expr,
! NULL,
! false);
expr->expr_simple_in_use = false;
expr->expr_simple_lxid = curlxid;
MemoryContextSwitchTo(oldcontext);
*************** exec_simple_check_node(Node *node)
*** 5472,5477 ****
--- 5474,5484 ----
return TRUE;
case T_Param:
+ /*
+ * If we have other kinds of params here, then earlier tests
+ * should have ruled out this as simple expression
+ */
+ Assert(((Param *) node)->paramkind == PARAM_EXTERN);
return TRUE;
case T_ArrayRef:
*************** exec_simple_check_node(Node *node)
*** 5704,5709 ****
--- 5711,5720 ----
return TRUE;
}
+ case T_CacheExpr:
+ /* Caching is disabled for simple expressions */
+ return TRUE;
+
default:
return FALSE;
}
diff --git a/src/test/regress/expected/cache.out b/src/test/regress/expected/cache.out
new file mode 100644
index ...592263d
*** a/src/test/regress/expected/cache.out
--- b/src/test/regress/expected/cache.out
***************
*** 0 ****
--- 1,685 ----
+ --
+ -- Test cachable expressions
+ --
+ -- If the NOTICE outputs of these functions change, you've probably broken
+ -- something with the CacheExpr optimization
+ --
+ create function stable_true() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE TRUE'; return true; end$$;
+ create function volatile_true() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE TRUE'; return true; end$$;
+ create function stable_false() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE FALSE'; return false; end$$;
+ create function volatile_false() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE FALSE'; return false; end$$;
+ -- Table with two rows
+ create table two (i int);
+ insert into two values (1), (2);
+ -- Boolean expressions
+ select stable_false() or volatile_true() or stable_true() as b from two;
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select stable_true() or volatile_false() or stable_false() as b from two;
+ NOTICE: STABLE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select stable_false() or volatile_true() as b from two;
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select stable_false() or stable_false() or volatile_true() as b from two;
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select volatile_true() or volatile_false() or stable_false() as b from two;
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select volatile_false() or volatile_true() or stable_false() as b from two;
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select stable_true() and volatile_false() and stable_false() as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select stable_false() and volatile_true() and stable_true() as b from two;
+ NOTICE: STABLE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select stable_true() and volatile_false() as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select stable_true() and stable_true() and volatile_false() as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select volatile_true() and volatile_false() and stable_true() as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select volatile_false() and volatile_true() and stable_true() as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select not stable_true() as b from two;
+ NOTICE: STABLE TRUE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select not volatile_true() as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ -- Bind params
+ prepare param_test(bool) as select $1 or stable_false() or volatile_true() as b from two;
+ execute param_test(true);
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ execute param_test(false);
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- Function calls
+ create function stable(bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%)', $1; return $1; end$$;
+ create function volatile(bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE(%)', $1; return $1; end$$;
+ select volatile(volatile_true()) from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE(t)
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE(t)
+ volatile
+ ----------
+ t
+ t
+ (2 rows)
+
+ select stable(stable_true()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE(t)
+ stable
+ --------
+ t
+ t
+ (2 rows)
+
+ select stable(volatile_true()) from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(t)
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(t)
+ stable
+ --------
+ t
+ t
+ (2 rows)
+
+ select volatile(stable_true()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE(t)
+ NOTICE: VOLATILE(t)
+ volatile
+ ----------
+ t
+ t
+ (2 rows)
+
+ create function stable(bool, bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+ create function volatile(bool, bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE(%, %)', $1, $2; return $1; end$$;
+ select stable(volatile_true(), volatile_false()) from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE(t, f)
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE(t, f)
+ stable
+ --------
+ t
+ t
+ (2 rows)
+
+ select stable(stable_true(), volatile_false()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE(t, f)
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE(t, f)
+ stable
+ --------
+ t
+ t
+ (2 rows)
+
+ select stable(stable_true(), stable_false()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE(t, f)
+ stable
+ --------
+ t
+ t
+ (2 rows)
+
+ select volatile(volatile_true(), volatile_false()) from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE(t, f)
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE(t, f)
+ volatile
+ ----------
+ t
+ t
+ (2 rows)
+
+ select volatile(stable_true(), volatile_false()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE(t, f)
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE(t, f)
+ volatile
+ ----------
+ t
+ t
+ (2 rows)
+
+ select volatile(stable_true(), stable_false()) from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE(t, f)
+ NOTICE: VOLATILE(t, f)
+ volatile
+ ----------
+ t
+ t
+ (2 rows)
+
+ -- Default arguments
+ create function stable_def(a bool = stable_false(), b bool = volatile_true())
+ returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+ select stable_def() from two;
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(f, t)
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(f, t)
+ stable_def
+ ------------
+ f
+ f
+ (2 rows)
+
+ select stable_def(b := stable_true()) from two;
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE(f, t)
+ stable_def
+ ------------
+ f
+ f
+ (2 rows)
+
+ select stable_def(volatile_false()) from two;
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(f, t)
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE(f, t)
+ stable_def
+ ------------
+ f
+ f
+ (2 rows)
+
+ -- Operators
+ create function stable_eq(bool, bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE % == %', $1, $2; return $1 = $2; end$$;
+ create function volatile_eq(bool, bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE % =%%= %', $1, $2; return $1 = $2; end$$;
+ create operator == (procedure = stable_eq, leftarg=bool, rightarg=bool);
+ create operator =%= (procedure = volatile_eq, leftarg=bool, rightarg=bool);
+ select volatile_true() == volatile_false() from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE t == f
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE t == f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select stable_true() == volatile_false() from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE t == f
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE t == f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select stable_true() == stable_false() from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE t == f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select volatile_true() =%= volatile_false() from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE t =%= f
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE t =%= f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select stable_true() =%= volatile_false() from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE t =%= f
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE t =%= f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select stable_true() =%= stable_false() from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ NOTICE: VOLATILE t =%= f
+ NOTICE: VOLATILE t =%= f
+ ?column?
+ ----------
+ f
+ f
+ (2 rows)
+
+ select (volatile_true() or stable_true()) == true as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE t == t
+ NOTICE: STABLE t == t
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- Coalesce
+ create function stable_null() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE NULL'; return null; end$$;
+ create function volatile_null() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE NULL'; return null; end$$;
+ select coalesce(stable_null(), stable_true()) from two;
+ NOTICE: STABLE NULL
+ NOTICE: STABLE TRUE
+ coalesce
+ ----------
+ t
+ t
+ (2 rows)
+
+ select coalesce(stable_true(), volatile_null()) from two;
+ NOTICE: STABLE TRUE
+ coalesce
+ ----------
+ t
+ t
+ (2 rows)
+
+ select coalesce(volatile_null(), stable_null(), volatile_true()) from two;
+ NOTICE: VOLATILE NULL
+ NOTICE: STABLE NULL
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE NULL
+ NOTICE: VOLATILE TRUE
+ coalesce
+ ----------
+ t
+ t
+ (2 rows)
+
+ -- Case/when
+ select case when stable_true() then 't' else volatile_false() end as b from two;
+ NOTICE: STABLE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select case when volatile_true() then stable_true() else stable_false() end as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select case when i=1 then stable_true() else stable_false() end as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ b
+ ---
+ t
+ f
+ (2 rows)
+
+ select case when i=1 then volatile_true() else volatile_false() end as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ t
+ f
+ (2 rows)
+
+ select case when 't' then 't' else volatile_false() end == true as b from two;
+ NOTICE: STABLE t == t
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- Coerce via I/O
+ select stable_true()::text::bool == true as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE t == t
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select volatile_true()::text::bool == true as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE t == t
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE t == t
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- IS DISTINCT FROM
+ select (stable_true() is not distinct from volatile_false()) as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select (stable_true() is distinct from stable_false()) == false as b from two;
+ NOTICE: STABLE TRUE
+ NOTICE: STABLE FALSE
+ NOTICE: STABLE t == f
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select (volatile_true() is distinct from null) as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- IS NULL
+ select volatile_true() is null == false as b from two;
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE f == f
+ NOTICE: VOLATILE TRUE
+ NOTICE: STABLE f == f
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ select stable_null() is not null == true as b from two;
+ NOTICE: STABLE NULL
+ NOTICE: STABLE f == t
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ -- Boolean tests
+ select volatile_false() is true == true as b from two;
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE f == t
+ NOTICE: VOLATILE FALSE
+ NOTICE: STABLE f == t
+ b
+ ---
+ f
+ f
+ (2 rows)
+
+ select stable_null() is not unknown == false as b from two;
+ NOTICE: STABLE NULL
+ NOTICE: STABLE f == f
+ b
+ ---
+ t
+ t
+ (2 rows)
+
+ -- Field select -- not currently cached
+ create function stable_row(a out int, b out int) STABLE language plpgsql as
+ $$begin raise notice 'STABLE ROW'; a = 1; b = 2; end$$;
+ select (stable_row()).a from two;
+ NOTICE: STABLE ROW
+ NOTICE: STABLE ROW
+ a
+ ---
+ 1
+ 1
+ (2 rows)
+
+ -- WHERE clause
+ begin;
+ -- stable_true is evaluated twice due to planning estimates
+ declare stable_where cursor for select * from two where i > stable_true()::int;
+ NOTICE: STABLE TRUE
+ fetch all from stable_where;
+ NOTICE: STABLE TRUE
+ i
+ ---
+ 2
+ (1 row)
+
+ declare volatile_where cursor for select * from two where i = volatile_false()::int;
+ fetch all from volatile_where;
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ i
+ ---
+ (0 rows)
+
+ rollback;
+ -- INSERT column default expressions
+ create table defaults (
+ dummy int,
+ a bool default stable_true(),
+ b bool default volatile_true()
+ );
+ insert into defaults (dummy) values(0), (1);
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ -- ALTER COLUMN TYPE USING
+ alter table defaults alter column a type bool using stable_false();
+ NOTICE: STABLE FALSE
+ alter table defaults alter column a type bool using volatile_false();
+ NOTICE: VOLATILE FALSE
+ NOTICE: VOLATILE FALSE
+ -- COPY FROM with default expressions
+ copy defaults (dummy) from stdin;
+ NOTICE: STABLE TRUE
+ CONTEXT: COPY defaults, line 1: "2"
+ NOTICE: VOLATILE TRUE
+ CONTEXT: COPY defaults, line 1: "2"
+ NOTICE: VOLATILE TRUE
+ CONTEXT: COPY defaults, line 2: "3"
+ -- VALUES list expressions
+ -- The fact that there are be 3 lines of 'VOLATILE TRUE' output is a quirk of
+ -- the current set-returning function execution code
+ insert into defaults (dummy, a, b)
+ values (generate_series(4, 5), stable_true(), volatile_true());
+ NOTICE: STABLE TRUE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ NOTICE: VOLATILE TRUE
+ -- PL/pgSQL Simple expressions
+ -- Make sure we don't cache simple expressions -- these expressions are only
+ -- initialized once per transaction and then executed multiple times
+ create function stable_max() returns int STABLE language plpgsql as
+ $$begin return (select max(i) from two); end$$;
+ create function simple() returns int STABLE language plpgsql as
+ $$begin return stable_max(); end$$;
+ begin;
+ select simple();
+ simple
+ --------
+ 2
+ (1 row)
+
+ insert into two values(3);
+ select simple();
+ simple
+ --------
+ 3
+ (1 row)
+
+ rollback;
+ -- The end
+ drop table defaults;
+ drop table two;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
new file mode 100644
index 8852e0a..2c15203
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
*************** test: select_into select_distinct select
*** 83,89 ****
# ----------
# Another group of parallel tests
# ----------
! test: privileges security_label collate
test: misc
# rules cannot run concurrently with any test that creates a view
--- 83,89 ----
# ----------
# Another group of parallel tests
# ----------
! test: privileges security_label collate cache
test: misc
# rules cannot run concurrently with any test that creates a view
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
new file mode 100644
index 0bc5df7..972a722
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
*************** test: prepared_xacts
*** 94,99 ****
--- 94,100 ----
test: privileges
test: security_label
test: collate
+ test: cache
test: misc
test: rules
test: select_views
diff --git a/src/test/regress/sql/cache.sql b/src/test/regress/sql/cache.sql
new file mode 100644
index ...9b68670
*** a/src/test/regress/sql/cache.sql
--- b/src/test/regress/sql/cache.sql
***************
*** 0 ****
--- 1,185 ----
+ --
+ -- Test cachable expressions
+ --
+ -- If the NOTICE outputs of these functions change, you've probably broken
+ -- something with the CacheExpr optimization
+ --
+
+ create function stable_true() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE TRUE'; return true; end$$;
+ create function volatile_true() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE TRUE'; return true; end$$;
+ create function stable_false() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE FALSE'; return false; end$$;
+ create function volatile_false() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE FALSE'; return false; end$$;
+
+ -- Table with two rows
+ create table two (i int);
+ insert into two values (1), (2);
+
+ -- Boolean expressions
+ select stable_false() or volatile_true() or stable_true() as b from two;
+ select stable_true() or volatile_false() or stable_false() as b from two;
+ select stable_false() or volatile_true() as b from two;
+ select stable_false() or stable_false() or volatile_true() as b from two;
+ select volatile_true() or volatile_false() or stable_false() as b from two;
+ select volatile_false() or volatile_true() or stable_false() as b from two;
+
+ select stable_true() and volatile_false() and stable_false() as b from two;
+ select stable_false() and volatile_true() and stable_true() as b from two;
+ select stable_true() and volatile_false() as b from two;
+ select stable_true() and stable_true() and volatile_false() as b from two;
+ select volatile_true() and volatile_false() and stable_true() as b from two;
+ select volatile_false() and volatile_true() and stable_true() as b from two;
+
+ select not stable_true() as b from two;
+ select not volatile_true() as b from two;
+
+ -- Bind params
+ prepare param_test(bool) as select $1 or stable_false() or volatile_true() as b from two;
+ execute param_test(true);
+ execute param_test(false);
+
+ -- Function calls
+ create function stable(bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%)', $1; return $1; end$$;
+ create function volatile(bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE(%)', $1; return $1; end$$;
+
+ select volatile(volatile_true()) from two;
+ select stable(stable_true()) from two;
+ select stable(volatile_true()) from two;
+ select volatile(stable_true()) from two;
+
+ create function stable(bool, bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+ create function volatile(bool, bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE(%, %)', $1, $2; return $1; end$$;
+
+ select stable(volatile_true(), volatile_false()) from two;
+ select stable(stable_true(), volatile_false()) from two;
+ select stable(stable_true(), stable_false()) from two;
+ select volatile(volatile_true(), volatile_false()) from two;
+ select volatile(stable_true(), volatile_false()) from two;
+ select volatile(stable_true(), stable_false()) from two;
+
+ -- Default arguments
+ create function stable_def(a bool = stable_false(), b bool = volatile_true())
+ returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE(%, %)', $1, $2; return $1; end$$;
+
+ select stable_def() from two;
+ select stable_def(b := stable_true()) from two;
+ select stable_def(volatile_false()) from two;
+
+ -- Operators
+ create function stable_eq(bool, bool) returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE % == %', $1, $2; return $1 = $2; end$$;
+ create function volatile_eq(bool, bool) returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE % =%%= %', $1, $2; return $1 = $2; end$$;
+
+ create operator == (procedure = stable_eq, leftarg=bool, rightarg=bool);
+ create operator =%= (procedure = volatile_eq, leftarg=bool, rightarg=bool);
+
+ select volatile_true() == volatile_false() from two;
+ select stable_true() == volatile_false() from two;
+ select stable_true() == stable_false() from two;
+ select volatile_true() =%= volatile_false() from two;
+ select stable_true() =%= volatile_false() from two;
+ select stable_true() =%= stable_false() from two;
+
+ select (volatile_true() or stable_true()) == true as b from two;
+
+ -- Coalesce
+ create function stable_null() returns bool STABLE language plpgsql as
+ $$begin raise notice 'STABLE NULL'; return null; end$$;
+ create function volatile_null() returns bool VOLATILE language plpgsql as
+ $$begin raise notice 'VOLATILE NULL'; return null; end$$;
+
+ select coalesce(stable_null(), stable_true()) from two;
+ select coalesce(stable_true(), volatile_null()) from two;
+ select coalesce(volatile_null(), stable_null(), volatile_true()) from two;
+
+ -- Case/when
+ select case when stable_true() then 't' else volatile_false() end as b from two;
+ select case when volatile_true() then stable_true() else stable_false() end as b from two;
+ select case when i=1 then stable_true() else stable_false() end as b from two;
+ select case when i=1 then volatile_true() else volatile_false() end as b from two;
+
+ select case when 't' then 't' else volatile_false() end == true as b from two;
+
+ -- Coerce via I/O
+ select stable_true()::text::bool == true as b from two;
+ select volatile_true()::text::bool == true as b from two;
+
+ -- IS DISTINCT FROM
+ select (stable_true() is not distinct from volatile_false()) as b from two;
+ select (stable_true() is distinct from stable_false()) == false as b from two;
+ select (volatile_true() is distinct from null) as b from two;
+
+ -- IS NULL
+ select volatile_true() is null == false as b from two;
+ select stable_null() is not null == true as b from two;
+
+ -- Boolean tests
+ select volatile_false() is true == true as b from two;
+ select stable_null() is not unknown == false as b from two;
+
+ -- Field select -- not currently cached
+ create function stable_row(a out int, b out int) STABLE language plpgsql as
+ $$begin raise notice 'STABLE ROW'; a = 1; b = 2; end$$;
+
+ select (stable_row()).a from two;
+
+ -- WHERE clause
+ begin;
+ -- stable_true is evaluated twice due to planning estimates
+ declare stable_where cursor for select * from two where i > stable_true()::int;
+ fetch all from stable_where;
+ declare volatile_where cursor for select * from two where i = volatile_false()::int;
+ fetch all from volatile_where;
+ rollback;
+
+ -- INSERT column default expressions
+ create table defaults (
+ dummy int,
+ a bool default stable_true(),
+ b bool default volatile_true()
+ );
+ insert into defaults (dummy) values(0), (1);
+
+ -- ALTER COLUMN TYPE USING
+ alter table defaults alter column a type bool using stable_false();
+ alter table defaults alter column a type bool using volatile_false();
+
+ -- COPY FROM with default expressions
+ copy defaults (dummy) from stdin;
+ 2
+ 3
+ \.
+
+ -- VALUES list expressions
+ -- The fact that there are be 3 lines of 'VOLATILE TRUE' output is a quirk of
+ -- the current set-returning function execution code
+ insert into defaults (dummy, a, b)
+ values (generate_series(4, 5), stable_true(), volatile_true());
+
+ -- PL/pgSQL Simple expressions
+ -- Make sure we don't cache simple expressions -- these expressions are only
+ -- initialized once per transaction and then executed multiple times
+ create function stable_max() returns int STABLE language plpgsql as
+ $$begin return (select max(i) from two); end$$;
+
+ create function simple() returns int STABLE language plpgsql as
+ $$begin return stable_max(); end$$;
+
+ begin;
+ select simple();
+ insert into two values(3);
+ select simple();
+ rollback;
+
+ -- The end
+ drop table defaults;
+ drop table two;
Marti Raudsepp <marti@juffo.org> writes:
[ cacheexpr-v8.patch ]
A few comments:
* I believe the unconditional datumCopy call in ExecEvalCacheExpr will
dump core if the value is null and the type is pass-by-reference.
Beyond just skipping it, it seems like you could skip the type
properties lookup as well if the value to be cached is null.
* I do not see any logic in here for resetting the already-cached status
in an existing ExprState tree. You're going to need that for cases
where Params are given a new value. I don't think I trust the
assumption that this is never true for EXTERN parameters, and in any
case you're leaving a whole lot of the potential benefit of the patch on
the table by ignoring the possibility of caching executor-internal
parameters. I would rather see, for example, a ReScan call on a plan
node running around and resetting caching of all expression trees
attached to the plan node if its chgParam isn't empty.
* I think you should consider getting rid of the extra argument to
ExecInitExpr, as well as the enable flag in CacheExprState, and instead
driving cache-enable off a new flag added to EState, which should
probably initialize to "caching OK" since that's what the majority of
callers seem to want. That would get rid of a fair amount of the
boilerplate changes, as well as being at least as convenient to use,
maybe more so.
* In the same vein, I think it would be better to avoid adding
the extra argument to eval_const_expressions_mutator and instead pass
that state around in the existing "context" struct argument. In
particular I am entirely unimpressed with the fact that you can't pass
the extra argument through expression_tree_mutator and thus have to dumb
the code down to fail to cache underneath any node type for which
there's not explicit coding in eval_const_expressions_mutator.
* This is pretty horrible:
+ * NOTE: This function is not indempotent. Calling it twice over an expression
+ * tree causes CacheExpr nodes to be removed in the first pass, then re-added
+ * in the 2nd pass. Make sure it only gets called once.
I don't think we can positively guarantee that. The function API should
be fixed to not need such an assumption.
* It seems like some of the ugliness here is due to thinking that
selectivity functions can't cope with CacheExprs. Wouldn't it be a lot
better to make them cope? I don't think there are that many places.
I'd suggest looking for places that strip RelabelType and fixing them to
strip CacheExpr instead (in fact, most likely any that don't are broken
anyway ...)
* There's a lot of stuff that seems wrong in detail in
eval_const_expressions_mutator, eg it'll try to wrap a plain vanilla
Const with a CacheExpr. I see you've hacked that case inside
insert_cache itself, but that seems like evidence of a poorly thought
out recursion invariant. The function should have a better notion than
that of whether caching is useful for a given subtree. In general I'd
not expect it to insert a CacheExpr unless there is a Param or stable
function call somewhere below the current point, but it seems not to be
tracking that. You probably need at least a three-way indicator
returned from each recursion level: subexpression is not cacheable
(eg it contains a Var or volatile function); subexpression is cacheable
but there is no reason to do so (default situation); or subexpression is
cacheable and should be cached (it contains a Param or stable function).
* I don't think it's a good idea for
simplify_and_arguments/simplify_or_arguments to arbitrarily reorder the
arguments based on cacheability. I know that we tell people not to
depend on order of evaluation in AND/OR lists, but that doesn't mean
they listen, and this will introduce reordering into an awful lot of
places where it never happened before. If you have a mix of
cacheability situations, just stick CacheExprs on each arm (or not).
* I'm having a hard time understanding what you did to simplify_function
and friends; that's been whacked around quite a bit, in ways that are
neither clearly correct nor clearly related to the goal of the patch.
It might be best to present this change as a series of two patches,
one that just refactors and one that adds the caching logic.
* I don't believe CacheExprs can be treated as always simple by
ruleutils' isSimpleNode, at least not unless you always print them with
parentheses (which might be a good idea anyway).
* I don't think I believe either of the changes you made to plpgsql's
exec_simple_check_node. I'm not convinced that only PARAM_EXTERN params
are possible, and even if that's true it doesn't seem to be this patch's
business to add an assert for it. Also, immediately returning TRUE for
a CacheExpr amounts to assuming that we can never wrap a CacheExpr
around something that plpgsql would consider non-simple. I'm not sure
that's true now, and even if it is true it seems a mighty fragile
assumption.
regards, tom lane
On Sat, Mar 10, 2012 at 02:05, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Marti Raudsepp <marti@juffo.org> writes:
[ cacheexpr-v8.patch ]
A few comments
Whoa, that's quite a few. Thanks for the review.
* There's a lot of stuff that seems wrong in detail in
eval_const_expressions_mutator, eg it'll try to wrap a plain vanilla
Const with a CacheExpr. I see you've hacked that case inside
insert_cache itself, but that seems like evidence of a poorly thought
out recursion invariant. The function should have a better notion than
that of whether caching is useful for a given subtree.
Well my logic was this: accessing params and consts is just as fast as
accessing the cache -- there's no evaluation involved. So there's no
point in inserting CacheExpr in front of those. All other kinds of
expressions require some sort of evaluation, so they are cached, if
they weren't already constant-folded.
In general I'd
not expect it to insert a CacheExpr unless there is a Param or stable
function call somewhere below the current point, but it seems not to be
tracking that.
I think if you continue on your "Param or stable function" train of
thought, then it boils down to "expressions that can't be
constant-folded". And that's how it already works now --
constant-foldable expressions get reduced to a Const, which isn't
cached. There's no need to track it specially since we can check
whether we've got a Const node.
You probably need at least a three-way indicator
returned from each recursion level: subexpression is not cacheable
(eg it contains a Var or volatile function); subexpression is cacheable
but there is no reason to do so (default situation); or subexpression is
cacheable and should be cached (it contains a Param or stable function).
That could be done -- give a special cachability result in code paths
that return a constant -- but that seems like complicating the logic
and doesn't tell us anything we don't already know.
----
* I'm having a hard time understanding what you did to simplify_function
and friends; that's been whacked around quite a bit, in ways that are
neither clearly correct nor clearly related to the goal of the patch.
Sure, I'll split this out as a separate patch and send it up.
* I believe the unconditional datumCopy call in ExecEvalCacheExpr will
dump core if the value is null and the type is pass-by-reference.
datumCopy already has a check for NULL pointer. But good point about
skipping type properties lookup -- will change.
* I think you should consider getting rid of the extra argument to
ExecInitExpr, as well as the enable flag in CacheExprState, and instead
driving cache-enable off a new flag added to EState
Will do.
* In the same vein, I think it would be better to avoid adding
the extra argument to eval_const_expressions_mutator and instead pass
that state around in the existing "context" struct argument.
I'll have to see how ugly it gets to save & restore the cachability
field in recursive calls.
I am entirely unimpressed with the fact that you can't pass
the extra argument through expression_tree_mutator and thus have to dumb
the code down to fail to cache underneath any node type for which
there's not explicit coding in eval_const_expressions_mutator.
Well I deliberately chose a whitelisting approach. Expression types
that aren't important enough to be constant-folded probably aren't
that important for caching either.
Do you think it's safe to assume that expression types we don't know
about are inherently cachable?
+ * NOTE: This function is not indempotent. Calling it twice over an expression
* It seems like some of the ugliness here is due to thinking that
selectivity functions can't cope with CacheExprs. Wouldn't it be a lot
better to make them cope?
I thought centralizing this CacheExpr-stripping to one place was a
better idea than spraying it all around the code base. We can skip the
copy *and* the check this way.
Hmm, if I passed the context to insert_cache then we could avoid this problem.
We could do the same for RelabelType in estimation mode, but I don't
know how safe that is.
* I don't think it's a good idea for
simplify_and_arguments/simplify_or_arguments to arbitrarily reorder the
arguments based on cacheability. I know that we tell people not to
depend on order of evaluation in AND/OR lists, but that doesn't mean
they listen
Fair enough, will change.
* I don't believe CacheExprs can be treated as always simple by
ruleutils' isSimpleNode
Ok.
* I don't think I believe either of the changes you made to plpgsql's
exec_simple_check_node.
Will change.
Regards,
Marti
Marti Raudsepp <marti@juffo.org> writes:
On Sat, Mar 10, 2012 at 02:05, Tom Lane <tgl@sss.pgh.pa.us> wrote:
* There's a lot of stuff that seems wrong in detail in
eval_const_expressions_mutator, eg it'll try to wrap a plain vanilla
Const with a CacheExpr. I see you've hacked that case inside
insert_cache itself, but that seems like evidence of a poorly thought
out recursion invariant. The function should have a better notion than
that of whether caching is useful for a given subtree.
Well my logic was this: accessing params and consts is just as fast as
accessing the cache -- there's no evaluation involved. So there's no
point in inserting CacheExpr in front of those. All other kinds of
expressions require some sort of evaluation, so they are cached, if
they weren't already constant-folded.
I think this is overkill. Inserting a CacheExpr is far from free:
it costs cycles to make that node, cycles to copy it around, cycles
to recurse through it during subsequent processing. We should not
be putting them in to save trivial amounts of execution time. So
I don't believe your argument that there is no such thing as a
non-Const, non-volatile expression that shouldn't be cached.
Examples of things that clearly are not expensive enough to deserve
a cache node are RelabelType and PlaceHolderVar (the latter won't
even be there at runtime).
More generally, though, I think that caching something like, say,
"Param int4pl 1" is probably a net loss once you consider all the
added overhead. I'm not going to argue for putting explicit cost
considerations into the first version, but I don't want the
infrastructure to be such that it's impossible to bolt that on later.
* I believe the unconditional datumCopy call in ExecEvalCacheExpr will
dump core if the value is null and the type is pass-by-reference.
datumCopy already has a check for NULL pointer.
You're assuming that a null Datum necessarily has an all-zero value,
which is not a safe assumption; in many situations the datum word
will be uninitialized. The null-pointer check in datumCopy is not
particularly useful IMO. It's probably a hangover from fifteen years
ago when the system's null-handling was a lot shakier than it is now.
Do you think it's safe to assume that expression types we don't know
about are inherently cachable?
Presumably, anything that doesn't contain a Var nor set off
contain_volatile_functions() should be safe to cache. I do not
care for the assumption that this set of expression types is identical
to the set that eval_const_expressions bothers to deal with.
* It seems like some of the ugliness here is due to thinking that
selectivity functions can't cope with CacheExprs. Wouldn't it be a lot
better to make them cope?
I thought centralizing this CacheExpr-stripping to one place was a
better idea than spraying it all around the code base.
How exactly do you figure that this avoids needing to know about them
elsewhere? Not everything in the selectivity code starts out by doing
estimate_expression_value. Perhaps more to the point, the stuff that
*does* do that is generally going to punt if it doesn't get a plain
Const back, so eliminating CacheExprs from non-Const cases isn't helping
it anyway.
regards, tom lane
Where are we on this?
---------------------------------------------------------------------------
On Mon, Jan 16, 2012 at 07:06:45PM +0200, Marti Raudsepp wrote:
Hi list,
Here's v6 of my expression caching patch. The only change in v6 is
added expression cost estimation in costsize.c. I'm setting per-tuple
cost of CacheExpr to 0 and moving sub-expression tuple costs into the
startup cost.As always, this work is also available from my Github "cache" branch:
https://github.com/intgr/postgres/commits/cacheThis patch was marked as "Returned with Feedback" from the 2011-11
commitfest. I expected to get to tweak the patch in response to
feedback before posting to the next commitfest. But said feedback
didn't arrive, and with me being on vacation, I missed the 2012-01 CF
deadline. :(I will add it to the 2012-01 commitfest now, I hope that's OK. If not,
feel free to remove it and I'll put it in 2012-Next.PS: Jaime, have you had a chance to look at the patch? Even if you're
not done with the review, I'd be glad to get some comments earlier.
And thanks for reviewing.Regards,
Marti--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ It's impossible for everything to be true. +
On Mon, Aug 27, 2012 at 4:50 PM, Bruce Momjian <bruce@momjian.us> wrote:
Where are we on this?
TL;DR: Got a review, requires substantial work, current github branch
is slightly broken, will get back to this soon.
Tom Lane sent a thorough review of the patch here:
http://archives.postgresql.org/pgsql-hackers/2012-03/msg00655.php
(very much appreciated!)
I have addressed some smaller points from that list in my github
branch, but it still requires a substantial amount of work (in
particular, the bulk of this patch which is the recursion logic in
eval_const_expressions_mutator, needs to be changed to prevent
unnecessary CacheExpr insertions and to store intermediate state in
the context struct).
I got a small fragment of this into PostgreSQL 9.2 as commit
81a646febe87964725647a36d839f6b4b405f3ae. I rebased my github branch
on top of this commit, but the rebase introduced some test failures
that I have not tracked down yet. I don't know if it applies to git
HEAD any more.
Sadly some other things intervened and I have not had the time to
return to hacking on this patch. But I am hopeful I can get it into
shape during the 9.3 cycle.
Regards,
Marti
On Mon, Aug 27, 2012 at 05:44:32PM +0300, Marti Raudsepp wrote:
On Mon, Aug 27, 2012 at 4:50 PM, Bruce Momjian <bruce@momjian.us> wrote:
Where are we on this?
TL;DR: Got a review, requires substantial work, current github branch
is slightly broken, will get back to this soon.Tom Lane sent a thorough review of the patch here:
http://archives.postgresql.org/pgsql-hackers/2012-03/msg00655.php
(very much appreciated!)I have addressed some smaller points from that list in my github
branch, but it still requires a substantial amount of work (in
particular, the bulk of this patch which is the recursion logic in
eval_const_expressions_mutator, needs to be changed to prevent
unnecessary CacheExpr insertions and to store intermediate state in
the context struct).I got a small fragment of this into PostgreSQL 9.2 as commit
81a646febe87964725647a36d839f6b4b405f3ae. I rebased my github branch
on top of this commit, but the rebase introduced some test failures
that I have not tracked down yet. I don't know if it applies to git
HEAD any more.Sadly some other things intervened and I have not had the time to
return to hacking on this patch. But I am hopeful I can get it into
shape during the 9.3 cycle.
OK, thanks for the update, and your work on this.
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ It's impossible for everything to be true. +
Marti,
Sadly some other things intervened and I have not had the time to
return to hacking on this patch. But I am hopeful I can get it into
shape during the 9.3 cycle.
Hey, are you going to work on this for 9.3? I really could use the
feature ...
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
On Wed, Oct 31, 2012 at 2:21 AM, Josh Berkus <josh@agliodbs.com> wrote:
Hey, are you going to work on this for 9.3?
Yes, I do plan to get back to it. Thanks for the push :)
I really could use the feature ...
If you're not aware already, you can work around the limitation using
a subquery.
Instead of: WHERE foo_column > expensive_expression()
Write: WHERE foo_column > (SELECT expensive_expression())
Regards,
Marti