[PATCH] Caching for stable expressions with constant arguments v3
Hi list!
This is the third version of my CacheExpr patch. The patch is now
feature-complete -- meaning that it's now capable of caching all
expression types that are currently constant-foldable. Simple queries
don't get CacheExprs added at all now. Code comments should also be
sufficient for review. New regression tests about caching behavior are
included.
I'm now at a point where I can't think of anything significant to
improve about the patch so feedback would be very welcome.
For explanation about design decisions, please read these earlier messages:
http://archives.postgresql.org/pgsql-hackers/2011-09/msg00579.php
http://archives.postgresql.org/pgsql-hackers/2011-09/msg00812.php
http://archives.postgresql.org/pgsql-hackers/2011-09/msg00833.php
Should I remove the enable_cacheexpr GUC? The overhead of this doesn't
seem big enough to warrant people turning it off.
I'm not happy with some of the duplication added to
const_expressions_mutator, particularly CaseExpr. However, moving that
code to another function or macro would probably make it harder to
understand (every parameter would only be used once). Thoughts?
Performance
-----------
There's no question that this patch improves performance for large
sequential scan filters, so I'm mostly exploring how much overhead is
added in the worst cases here.
For benchmarking, I built and ran two parallel PostgreSQL
installations on different ports, one with my latest patche and
another with the base git revision where I branched off my development
Built with GCC 4.6.1, configured with --disable-cassert --disable-debug
Default PostgreSQL configuration running on Phenom II X4. CPU
frequency scaling is disabled during test and PostgreSQL is isolated
to a certain CPU core to reduce variance.
For testing I used 'pgbench -T 10 -n -f $SCRIPT -p $PORT'
This command was ran 20 times (10 for patched, 10 for unpatched) and
the runs were interleaved.
All result differences are statistically significant according to
t-test with p<0.05
First of all, some very simple queries to get some idea of the
overhead of the patch.
----
select now()
base: avg=22088.9 stdev=180.0
patch: avg=22233.8 stdev=134.1
In this case the patch turns off CacheExpr insertion in order not to
break PL/pgSQL simple expressions. Why performance improves by 0.6%, I
don't know; it may be due to the code rearranging in simplify_function
----
select * from now()
base: avg=17097.1 stdev=48.2
patch: avg=16929.2 stdev=47.8
Caching is on here, and hurts the patch because it's only evaluated
once. Performance loss of 1.0%
----
Now we'll try some more complicated expressions on a table with two rows:
create table two (ts timestamptz);
insert into two values (timestamptz '2011-09-24 20:07:56.641322+03'),
('2011-09-24 20:07:56.642743+03');
----
two rows with cachable WHERE
select * from two where ts >= to_date(now()::date::text, 'YYYY-MM-DD')
and ts < (to_date(now()::date::text, 'YYYY-MM-DD') + interval '1
year')
base: avg=5054.2 stdev=47.1
patch: avg=4900.6 stdev=24.4
Clearly for two rows, caching the expression doesn't pay off.
Performance loss of 3.1%
----
two rows with uncachable WHERE
select * from two 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')
base: avg=6236.6 stdev=88.3
patched: avg=6118.7 stdev=50.1
No part of this expression is cachable because clock_timestamp() is a
volatile function. Performance loss is 1.9% in the patched version,
probably due to the overhead added by searching for cachable
expressions.
----
Now repeating the last two tests with a 50-row table:
create table fifty as select generate_series(timestamptz '2001-01-01',
timestamptz '2001-01-01' + '49 days', '1 day') ts;
----
50 rows with cachable WHERE
select * from fifty where ts >= to_date(now()::date::text,
'YYYY-MM-DD') and ts < (to_date(now()::date::text, 'YYYY-MM-DD') +
interval '1 year')
base: avg=3136.6 stdev=22.3
patch: avg=4397.3 stdev=35.5
As expected, performance is much better with more rows due to caching;
improvement of 28.7%
----
50 rows with uncachable WHERE
select * from fifty 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')
base: avg=3514.0 stdev=9.8
patch: avg=3500.1 stdev=18.4
Since planning overhead is less significant with more rows, the
regression is just 0.4% here.
----
As always, my latest progress can be seen in my GitHub 'cahce' branch:
https://github.com/intgr/postgres/commits/cache
Regards,
Marti
Attachments:
cacheexpr-v3.patchtext/x-patch; charset=US-ASCII; name=cacheexpr-v3.patchDownload
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 80f08d8..9c192a0 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -51,6 +51,7 @@
#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"
@@ -157,6 +158,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);
@@ -3746,6 +3753,59 @@ 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->subexpr, econtext, isNull, isDone);
+
+ /* Set-returning expressions can't be cached */
+ Assert(isDone == NULL || *isDone == ExprSingleResult);
+
+ 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
*
@@ -4850,6 +4910,17 @@ ExecInitExpr(Expr *node, PlanState *parent)
state = (ExprState *) gstate;
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+ CacheExprState *cstate = makeNode(CacheExprState);
+
+ cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCacheExpr;
+ cstate->subexpr = ExecInitExpr(cache->arg, parent);
+
+ state = (ExprState *) cstate;
+ }
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 661a516..819ed55 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1658,6 +1658,19 @@ _copyBooleanTest(BooleanTest *from)
}
/*
+ * _copyCacheExpr
+ */
+static CacheExpr *
+_copyCacheExpr(CacheExpr *from)
+{
+ CacheExpr *newnode = makeNode(CacheExpr);
+
+ COPY_NODE_FIELD(arg);
+
+ return newnode;
+}
+
+/*
* _copyCoerceToDomain
*/
static CoerceToDomain *
@@ -4066,6 +4079,9 @@ copyObject(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 4052a9a..bea8d18 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -677,6 +677,14 @@ _equalBooleanTest(BooleanTest *a, BooleanTest *b)
}
static bool
+_equalCacheExpr(CacheExpr *a, CacheExpr *b)
+{
+ COMPARE_NODE_FIELD(arg);
+
+ return true;
+}
+
+static bool
_equalCoerceToDomain(CoerceToDomain *a, CoerceToDomain *b)
{
COMPARE_NODE_FIELD(arg);
@@ -2636,6 +2644,9 @@ equal(void *a, 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 9070cd2..e3c9f03 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -470,6 +470,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 0e57f6c..0d38627 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -212,6 +212,9 @@ exprType(Node *expr)
case T_BooleanTest:
type = BOOLOID;
break;
+ case T_CacheExpr:
+ type = exprType((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
type = ((CoerceToDomain *) expr)->resulttype;
break;
@@ -792,6 +795,9 @@ exprCollation(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 = ((CoerceToDomain *) expr)->resultcollid;
break;
@@ -1263,6 +1269,10 @@ exprLocation(Node *expr)
/* just use argument's location */
loc = exprLocation((Node *) ((BooleanTest *) expr)->arg);
break;
+ case T_CacheExpr:
+ /* original expression location */
+ loc = exprLocation((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *cexpr = (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 0d0ce3c..c42da5b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1352,6 +1352,14 @@ _outBooleanTest(StringInfo str, BooleanTest *node)
}
static void
+_outCacheExpr(StringInfo str, CacheExpr *node)
+{
+ WRITE_NODE_TYPE("CACHEEXPR");
+
+ WRITE_NODE_FIELD(arg);
+}
+
+static void
_outCoerceToDomain(StringInfo str, CoerceToDomain *node)
{
WRITE_NODE_TYPE("COERCETODOMAIN");
@@ -2897,6 +2905,9 @@ _outNode(StringInfo str, 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/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 64b5eb4..2d40dfc 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -165,6 +165,26 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
glob->lastRowMarkId = 0;
glob->transientPlan = false;
+ /*
+ * glob->isSimple is a hint to eval_const_expressions() and PL/pgSQL that
+ * this statement is potentially a simple expression -- it contains no
+ * table references, no subqueries and no join clauses.
+ *
+ * We need this here because this prevents insertion of CacheExpr, which
+ * would break simple expressions in PL/pgSQL. Such queries wouldn't
+ * benefit from constant caching anyway.
+ *
+ * The actual definition of a simple statement is more strict, but we
+ * don't want to spend that checking overhead here.
+ *
+ * Caveat: Queries with set-returning functions in SELECT list could
+ * still potentially benefit from caching, but we don't check that now.
+ */
+ glob->isSimple = (parse->commandType == CMD_SELECT &&
+ parse->jointree->fromlist == NIL &&
+ parse->hasSubLinks == FALSE &&
+ parse->cteList == NIL);
+
/* Determine what fraction of the plan is likely to be scanned */
if (cursorOptions & CURSOR_OPT_FAST_PLAN)
{
@@ -241,6 +261,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
result->relationOids = glob->relationOids;
result->invalItems = glob->invalItems;
result->nParamExec = list_length(glob->paramlist);
+ result->isSimple = glob->isSimple;
return result;
}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index baa90fa..4fb490e 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -48,6 +48,9 @@
#include "utils/typcache.h"
+bool enable_cacheexpr = true;
+
+
typedef struct
{
PlannerInfo *root;
@@ -61,6 +64,7 @@ typedef struct
List *active_fns;
Node *case_val;
bool estimate;
+ bool use_cache; /* insert cache nodes where possible? */
} eval_const_expressions_context;
typedef struct
@@ -96,43 +100,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,
@@ -2027,6 +2038,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
@@ -2065,7 +2081,10 @@ 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);
+ context.use_cache = enable_cacheexpr &&
+ (root == NULL || !root->glob->isSimple);
+
+ return caching_const_expressions_mutator(node, &context);
}
/*--------------------
@@ -2083,12 +2102,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 = false; /* short-circuit some checks */
context.boundParams = root->glob->boundParams; /* bound Params */
/* we do not need to mark the plan as depending on inlined functions */
@@ -2096,19 +2117,57 @@ 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);
+ context.use_cache = false; /* no caching, planner only evaluates once */
+
+ 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 && context->use_cache)
+ 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 (if context->use_cache is enabled).
+ */
static Node *
-eval_const_expressions_mutator(Node *node,
- eval_const_expressions_context *context)
+const_expressions_mutator(Node *node,
+ eval_const_expressions_context *context,
+ bool *cachable)
{
+ Assert(!context->use_cache || *cachable == true);
+
if (node == NULL)
return NULL;
if (IsA(node, Param))
{
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 &&
@@ -2148,33 +2207,16 @@ eval_const_expressions_mutator(Node *node,
}
}
}
+
/* Not replaceable, so just copy the Param (no need to recurse) */
return (Node *) copyObject(param);
}
if (IsA(node, 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 as a
@@ -2188,7 +2230,8 @@ eval_const_expressions_mutator(Node *node,
expr->funccollid,
expr->inputcollid,
&args,
- has_named_args, true, context);
+ true, context,
+ cachable);
if (simple) /* successfully simplified it */
return (Node *) simple;
@@ -2212,20 +2255,11 @@ eval_const_expressions_mutator(Node *node,
if (IsA(node, 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.
*/
@@ -2241,7 +2275,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;
@@ -2277,33 +2312,47 @@ eval_const_expressions_mutator(Node *node,
if (IsA(node, 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;
@@ -2339,7 +2388,9 @@ eval_const_expressions_mutator(Node *node,
expr->opcollid,
expr->inputcollid,
&args,
- false, false, context);
+ false, context,
+ cachable);
+
if (simple) /* successfully simplified it */
{
/*
@@ -2354,6 +2405,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 and
@@ -2384,7 +2461,8 @@ eval_const_expressions_mutator(Node *node,
bool forceTrue = false;
newargs = simplify_or_arguments(expr->args, context,
- &haveNull, &forceTrue);
+ &haveNull, &forceTrue,
+ cachable);
if (forceTrue)
return makeBoolConst(true, false);
if (haveNull)
@@ -2405,7 +2483,8 @@ eval_const_expressions_mutator(Node *node,
bool forceFalse = false;
newargs = simplify_and_arguments(expr->args, context,
- &haveNull, &forceFalse);
+ &haveNull, &forceFalse,
+ cachable);
if (forceFalse)
return makeBoolConst(false, false);
if (haveNull)
@@ -2424,8 +2503,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 away the
@@ -2448,6 +2528,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;
}
if (IsA(node, RelabelType))
@@ -2460,8 +2541,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 :: oid) we can
@@ -2495,7 +2577,6 @@ eval_const_expressions_mutator(Node *node,
if (IsA(node, CoerceViaIO))
{
CoerceViaIO *expr = (CoerceViaIO *) node;
- Expr *arg;
List *args;
Oid outfunc;
bool outtypisvarlena;
@@ -2507,9 +2588,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 function
@@ -2520,7 +2599,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), &outfunc, &outtypisvarlena);
+ getTypeOutputInfo(exprType((Node *) expr->arg), &outfunc, &outtypisvarlena);
getTypeInputInfo(expr->resulttype, &infunc, &intypioparam);
simple = simplify_function(NULL,
@@ -2529,7 +2608,8 @@ eval_const_expressions_mutator(Node *node,
InvalidOid,
InvalidOid,
&args,
- false, true, context);
+ true, context,
+ cachable);
if (simple) /* successfully simplified output fn */
{
/*
@@ -2550,7 +2630,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;
}
@@ -2561,7 +2642,7 @@ eval_const_expressions_mutator(Node *node,
* argument.
*/
newexpr = makeNode(CoerceViaIO);
- newexpr->arg = arg;
+ newexpr->arg = (Expr *) linitial(args);
newexpr->resulttype = expr->resulttype;
newexpr->resultcollid = expr->resultcollid;
newexpr->coerceformat = expr->coerceformat;
@@ -2578,8 +2659,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;
@@ -2603,6 +2685,18 @@ eval_const_expressions_mutator(Node *node,
newexpr->resulttypmod,
newexpr->resultcollid);
+ /*
+ * If the argument is cachable, but conversion isn't, insert a
+ * CacheExpr above the argument
+ */
+ if (context->use_cache && 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;
}
@@ -2618,8 +2712,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))
{
@@ -2687,12 +2782,13 @@ 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,
+ newarg = caching_const_expressions_mutator((Node *) caseexpr->arg,
context);
/* Set up for contained CaseTestExpr nodes */
@@ -2713,13 +2809,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);
+ const_expressions_mutator((Node *) oldcasewhen->expr,
+ context, &condCachable);
/*
* If the test condition is constant FALSE (or NULL), then drop
@@ -2738,8 +2836,8 @@ eval_const_expressions_mutator(Node *node,
/* Simplify this alternative's result value */
caseresult =
- eval_const_expressions_mutator((Node *) oldcasewhen->result,
- context);
+ const_expressions_mutator((Node *) oldcasewhen->result,
+ context, &resultCachable);
/* If non-constant test condition, emit a new WHEN node */
if (!const_true_cond)
@@ -2750,6 +2848,23 @@ eval_const_expressions_mutator(Node *node,
newcasewhen->result = (Expr *) caseresult;
newcasewhen->location = oldcasewhen->location;
newargs = lappend(newargs, newcasewhen);
+
+ if (condCachable)
+ {
+ if (context->use_cache && is_cache_useful((Expr *) casecond))
+ cachable_args = lappend(cachable_args, &newcasewhen->expr);
+ }
+ else
+ *cachable = false;
+
+ if (resultCachable)
+ {
+ if (context->use_cache && is_cache_useful((Expr *) caseresult))
+ cachable_args = lappend(cachable_args, &newcasewhen->result);
+ }
+ else
+ *cachable = false;
+
continue;
}
@@ -2763,15 +2878,39 @@ eval_const_expressions_mutator(Node *node,
/* Simplify the default result, unless we replaced it above */
if (!const_true_cond)
+ {
+ bool isCachable = true;
+
defresult =
- eval_const_expressions_mutator((Node *) caseexpr->defresult,
- context);
+ const_expressions_mutator((Node *) caseexpr->defresult,
+ context, &isCachable);
+
+ if (isCachable)
+ {
+ if (context->use_cache && is_cache_useful((Expr *) defresult))
+ cachable_args = lappend(cachable_args, &defresult);
+ }
+ else
+ *cachable = false;
+ }
context->case_val = save_case_val;
/* If no non-FALSE alternatives, CASE reduces to the default result */
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;
@@ -2792,7 +2931,10 @@ eval_const_expressions_mutator(Node *node,
if (context->case_val)
return copyObject(context->case_val);
else
+ {
+ *cachable = false;
return copyObject(node);
+ }
}
if (IsA(node, ArrayExpr))
{
@@ -2802,13 +2944,15 @@ eval_const_expressions_mutator(Node *node,
List *newelems;
ListCell *element;
+ *cachable = false; /* XXX cachable! */
+
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);
@@ -2835,15 +2979,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 non-null
@@ -2852,16 +2999,25 @@ eval_const_expressions_mutator(Node *node,
* it's the next argument, but we can 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 (context->use_cache && is_cache_useful((Expr *) arg))
+ cachable_args = lappend(cachable_args, &llast(newargs));
+ }
+ else
+ *cachable = false;
}
/* If all the arguments were constant null, the result is just null */
@@ -2869,6 +3025,14 @@ 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;
@@ -2889,13 +3053,17 @@ eval_const_expressions_mutator(Node *node,
* We must however check that the declared type of the field 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)
{
@@ -2946,8 +3114,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))
{
/*
@@ -3029,8 +3198,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;
@@ -3075,7 +3245,7 @@ eval_const_expressions_mutator(Node *node,
newbtest->booltesttype = btest->booltesttype;
return (Node *) newbtest;
}
- if (IsA(node, PlaceHolderVar) &&context->estimate)
+ if (IsA(node, PlaceHolderVar) && context->estimate)
{
/*
* In estimation mode, just strip the PlaceHolderVar node altogether;
@@ -3086,18 +3256,36 @@ eval_const_expressions_mutator(Node *node,
*/
PlaceHolderVar *phv = (PlaceHolderVar *) node;
- return eval_const_expressions_mutator((Node *) phv->phexpr,
- context);
+ return const_expressions_mutator((Node *) phv->phexpr,
+ context,
+ cachable);
}
+ if (IsA(node, CacheExpr))
+ {
+ /*
+ * The planner asked us to re-simplify a simplified expression tree.
+ * Strip CacheExpr nodes since the planner only evaluates the
+ * expression once.
+ */
+ CacheExpr *cache = (CacheExpr *) node;
+
+ Assert(context->estimate && !context->use_cache);
+
+ return const_expressions_mutator((Node *) cache->arg, context, cachable);
+ }
+
+ if (!IsA(node, Const))
+ *cachable = false; /* Everything else is not cachable */
/*
* 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.
+ * 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);
}
@@ -3119,13 +3307,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;
/*
@@ -3140,6 +3338,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);
@@ -3162,7 +3361,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
@@ -3204,10 +3403,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;
}
/*
@@ -3228,13 +3457,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 */
@@ -3242,6 +3481,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);
@@ -3264,7 +3504,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
@@ -3306,10 +3546,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;
}
/*
@@ -3384,7 +3654,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.
@@ -3403,14 +3673,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; /* XXX use bitmapset instead? */
/*
* We have three strategies for simplification: execute the function to
@@ -3424,19 +3697,52 @@ 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(context->use_cache && 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
@@ -3473,30 +3779,109 @@ 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;
@@ -3530,7 +3915,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);
@@ -3539,10 +3923,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++;
}
}
@@ -3555,32 +3936,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;
}
@@ -3591,20 +3946,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)
@@ -3613,28 +3965,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;
}
@@ -3724,7 +4054,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;
@@ -3736,7 +4067,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
@@ -3750,7 +4084,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.
@@ -3847,7 +4187,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;
@@ -4126,7 +4467,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;
@@ -4206,6 +4547,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
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c112a9c..0c969da 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -4559,8 +4559,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:
@@ -5681,6 +5685,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/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a71729c..f5647e8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -756,6 +756,16 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
+ /* XXX Probably won't need this, but it's useful for debugging */
+ {"enable_cacheexpr", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enables caching of stable execution-time-constant expressions."),
+ NULL
+ },
+ &enable_cacheexpr,
+ true,
+ NULL, NULL, NULL
+ },
+ {
{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
gettext_noop("Enables genetic query optimization."),
gettext_noop("This algorithm attempts to do planning without "
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b3eed7d..d2d0ff6 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -672,6 +672,22 @@ 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 *subexpr; /* state of sub-expression */
+
+ 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 532681f..699b5ce 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -73,6 +73,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 *subexpr);
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 ecf62b3..5649d27 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -170,6 +170,7 @@ typedef enum NodeTag
T_JoinExpr,
T_FromExpr,
T_IntoClause,
+ T_CacheExpr,
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
@@ -203,6 +204,7 @@ typedef enum NodeTag
T_NullTestState,
T_CoerceToDomainState,
T_DomainConstraintState,
+ T_CacheExprState,
/*
* TAGS FOR PLANNER NODES (relation.h)
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 535eca7..4647535 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
List *invalItems; /* other dependencies, as PlanInvalItems */
int nParamExec; /* number of PARAM_EXEC Params used */
+
+ bool isSimple; /* possibly simple expression: no CacheExpr */
} PlannedStmt;
/* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f1e20ef..c20adb5 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1024,6 +1024,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/include/nodes/relation.h b/src/include/nodes/relation.h
index ecbbc1c..f82c69e 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
Index lastRowMarkId; /* highest PlanRowMark ID assigned */
bool transientPlan; /* redo plan when TransactionXmin changes? */
+
+ bool isSimple; /* possibly simple expression: no CacheExpr */
} PlannerGlobal;
/* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 604df33..02b91e8 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -60,6 +60,7 @@ extern bool enable_nestloop;
extern bool enable_material;
extern bool enable_mergejoin;
extern bool enable_hashjoin;
+extern bool enable_cacheexpr; /* XXX probably a wrong place for this */
extern int constraint_exclusion;
extern double clamp_row_est(double nrows);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index df785c9..6c5b8f4 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5406,6 +5406,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:
@@ -5638,6 +5643,15 @@ exec_simple_check_node(Node *node)
return TRUE;
}
+ case T_CacheExpr:
+ /*
+ * The planner should prevent generation of CacheExprs in queries
+ * that can be deemed simple, see standard_planner()
+ */
+ ereport(WARNING,
+ (errmsg("potentially-simple expression contains CacheExpr")));
+ return FALSE;
+
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..1e4664b
--- /dev/null
+++ b/src/test/regress/expected/cache.out
@@ -0,0 +1,676 @@
+--
+-- 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"
+-- 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/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 51d561b..1210693 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -2,6 +2,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
name | setting
-------------------+---------
enable_bitmapscan | on
+ enable_cacheexpr | on
enable_hashagg | on
enable_hashjoin | on
enable_indexscan | on
@@ -11,7 +12,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(10 rows)
+(11 rows)
CREATE TABLE foo2(fooid int, f2 int);
INSERT INTO foo2 VALUES(1, 11);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 376f28d..39ed9ea 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -79,7 +79,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 bb654f9..a5f3a19 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -90,6 +90,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..8cd8908
--- /dev/null
+++ b/src/test/regress/sql/cache.sql
@@ -0,0 +1,179 @@
+--
+-- 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
+\.
+
+-- 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;
All,
I'd love to see someone evaluate the impact of Marti's patch on JDBC applications which use named prepared statements. Anyone have a benchmark handy?
--Josh
On Sat, Sep 24, 2011 at 9:09 PM, Marti Raudsepp <marti@juffo.org> wrote:
Hi list!
This is the third version of my CacheExpr patch.
i wanted to try this, but it no longer applies in head... specially
because of the change of IF's for a switch in
src/backend/optimizer/util/clauses.c it also needs adjustment for the
rangetypes regress test
--
Jaime Casanova www.2ndQuadrant.com
Professional PostgreSQL: Soporte 24x7 y capacitación
On 25.09.2011 05:09, Marti Raudsepp wrote:
This is the third version of my CacheExpr patch.
This seems to have bitrotted, thanks to the recent refactoring in
eval_const_expressions().
For explanation about design decisions, please read these earlier messages:
http://archives.postgresql.org/pgsql-hackers/2011-09/msg00579.php
http://archives.postgresql.org/pgsql-hackers/2011-09/msg00812.php
http://archives.postgresql.org/pgsql-hackers/2011-09/msg00833.php
I wonder if it would be better to add the CacheExpr nodes to the tree as
a separate pass, instead of shoehorning it into eval_const_expressions?
I think would be more readable that way, even though a separate pass
would be more expensive. And there are callers of
eval_const_expressions() that have no use for the caching, like
process_implied_equality().
This comment in RelationGetExpressions() also worries me:
/*
* Run the expressions through eval_const_expressions. This is not just an
* optimization, but is necessary, because the planner will be comparing
* them to similarly-processed qual clauses, and may fail to detect valid
* matches without this. We don't bother with canonicalize_qual, however.
*/
result = (List *) eval_const_expressions(NULL, (Node *) result);
Do the injected CacheExprs screw up that equality? Or the constraint
exclusion logic in predtest.c?
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
I wonder if it would be better to add the CacheExpr nodes to the tree as
a separate pass, instead of shoehorning it into eval_const_expressions?
I think would be more readable that way, even though a separate pass
would be more expensive.
A separate pass would be very considerably more expensive, because
(1) it would require making a whole new copy of each expression tree,
and (2) it would require looking up the volatility status of each
function and operator. eval_const_expressions already has to do the
latter, or has to do it in a lot of cases anyway, so I think it's
probably the best place to add this. If it weren't for (2) I would
suggest adding the work to setrefs.c instead, but as it is I think
we'd better suck it up and deal with any fallout in the later stages
of the planner.
regards, tom lane
On Sun, Dec 4, 2011 at 22:53, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:
This seems to have bitrotted, thanks to the recent refactoring in
eval_const_expressions().
Luckily the conflicts are mostly whitespace changes, so shouldn't be
hard to fix. I'll try to come up with an updated patch today or
tomorrow.
I wonder if it would be better to add the CacheExpr nodes to the tree as a
separate pass, instead of shoehorning it into eval_const_expressions? I
think would be more readable that way, even though a separate pass would be
more expensive. And there are callers of eval_const_expressions() that have
no use for the caching, like process_implied_equality().
Per Tom's comment, I won't split out the cache insertion for now.
The context struct has a boolean 'cache' attribute that controls
whether caching is desired or not. I think this could be exposed to
the caller as an eval_const_expressions() argument.
This comment in RelationGetExpressions() also worries me:
[...]
Do the injected CacheExprs screw up that equality? Or the constraint
exclusion logic in predtest.c?
I suspect these cases are guaranteed not to produce any CacheExprs.
They're always immutable expressions. If they contain Var references
they're stored as is (not cachable); if not, they're folded to a
constant.
But I will have to double-check all the callers; it might be a good
idea to disable caching anyway in these cases.
Regards,
Marti
Marti Raudsepp <marti@juffo.org> writes:
On Sun, Dec 4, 2011 at 22:53, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:This comment in RelationGetExpressions() also worries me:
[...]
Do the injected CacheExprs screw up that equality? Or the constraint
exclusion logic in predtest.c?
I suspect these cases are guaranteed not to produce any CacheExprs.
They're always immutable expressions. If they contain Var references
they're stored as is (not cachable); if not, they're folded to a
constant.
But I will have to double-check all the callers; it might be a good
idea to disable caching anyway in these cases.
I think if you have some call sites inject CacheExprs and others not,
it will get more difficult to match up expressions that should be
considered equal. On the whole this seems like a bad idea. What is
the reason for having such a control boolean in the first place?
regards, tom lane
On Mon, Dec 5, 2011 at 19:31, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think if you have some call sites inject CacheExprs and others not,
it will get more difficult to match up expressions that should be
considered equal. On the whole this seems like a bad idea. What is
the reason for having such a control boolean in the first place?
It's needed for correctness with PL/pgSQL simple expressions.
This seems a bit of a kludge, but I considered it the "least bad"
solution. Here's what I added to planner.c standard_planner():
/*
* glob->isSimple is a hint to eval_const_expressions() and PL/pgSQL that
* this statement is potentially a simple expression -- it contains no
* table references, no subqueries and no join clauses.
*
* We need this here because this prevents insertion of CacheExpr, which
* would break simple expressions in PL/pgSQL. Such queries wouldn't
* benefit from constant caching anyway.
*
* The actual definition of a simple statement is more strict, but we
* don't want to spend that checking overhead here.
*
* Caveat: Queries with set-returning functions in SELECT list could
* still potentially benefit from caching, but we don't check that now.
*/
glob->isSimple = (parse->commandType == CMD_SELECT &&
parse->jointree->fromlist == NIL &&
parse->hasSubLinks == FALSE &&
parse->cteList == NIL);
----
I considered stripping CacheExpr nodes later in PL/pgSQL, but I can't
remember right now why I rejected that approach (sorry, it's been 2
months).
Currently I'm also disabling CacheExpr nodes in
estimate_expression_value() since we know for a fact that the planner
only evaluates it once. But that probably doesn't make much of a
difference.
Regards,
Marti
On 05.12.2011 20:53, Marti Raudsepp wrote:
I considered stripping CacheExpr nodes later in PL/pgSQL, but I can't
remember right now why I rejected that approach (sorry, it's been 2
months).
Yet another idea would be to leave the CacheExprs there, but provide a
way to reset the caches. PL/pgSQL could then reset the caches between
every invocation. Or pass a flag to ExecInitExpr() to skip through the
CacheExprs.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
On 05.12.2011 20:53, Marti Raudsepp wrote:
I considered stripping CacheExpr nodes later in PL/pgSQL, but I can't
remember right now why I rejected that approach (sorry, it's been 2
months).
Yet another idea would be to leave the CacheExprs there, but provide a
way to reset the caches. PL/pgSQL could then reset the caches between
every invocation.
We're likely to need a way to reset these caches anyway, at some point...
Or pass a flag to ExecInitExpr() to skip through the CacheExprs.
Not sure what you mean by that --- are you imagining that the ExprState
tree would have structure not matching the Expr tree? That seems just
about guaranteed to break something somewhere.
regards, tom lane
On 05.12.2011 21:36, Tom Lane wrote:
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:
Or pass a flag to ExecInitExpr() to skip through the CacheExprs.
Not sure what you mean by that --- are you imagining that the ExprState
tree would have structure not matching the Expr tree?
Yes. Or it could just set a flag in the CacheExprState nodes to not do
the caching.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
On Sun, Dec 4, 2011 at 22:53, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:
This seems to have bitrotted, thanks to the recent refactoring in
eval_const_expressions().
So here's the rebased version of this. It probably took me longer to
rebase this than it took Andres to write his patch ;)
Passes all regression tests and diffing clauses.c seems to have
introduced no functional differences.
But the good news is that I learned how to use git rebase and
pgindent, so it was worth it.
I will look into addressing some of the comments tomorrow, much thanks
for the feedback everyone!
Regards,
Marti
Attachments:
cacheexpr-v3-rebased.patchtext/x-patch; charset=US-ASCII; name=cacheexpr-v3-rebased.patchDownload
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 887e5ce8..1bf97f4 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -51,6 +51,7 @@
#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"
@@ -157,6 +158,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);
@@ -3754,6 +3761,59 @@ 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->subexpr, econtext, isNull, isDone);
+
+ /* Set-returning expressions can't be cached */
+ Assert(isDone == NULL || *isDone == ExprSingleResult);
+
+ 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
*
@@ -4858,6 +4918,17 @@ ExecInitExpr(Expr *node, PlanState *parent)
state = (ExprState *) gstate;
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+ CacheExprState *cstate = makeNode(CacheExprState);
+
+ cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCacheExpr;
+ cstate->subexpr = ExecInitExpr(cache->arg, parent);
+
+ state = (ExprState *) cstate;
+ }
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c70a5bd..6f809f2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1684,6 +1684,19 @@ _copyBooleanTest(BooleanTest *from)
}
/*
+ * _copyCacheExpr
+ */
+static CacheExpr *
+_copyCacheExpr(CacheExpr *from)
+{
+ CacheExpr *newnode = makeNode(CacheExpr);
+
+ COPY_NODE_FIELD(arg);
+
+ return newnode;
+}
+
+/*
* _copyCoerceToDomain
*/
static CoerceToDomain *
@@ -4004,6 +4017,9 @@ copyObject(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 f490a7a..af7ebe7 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -678,6 +678,14 @@ _equalBooleanTest(BooleanTest *a, BooleanTest *b)
}
static bool
+_equalCacheExpr(CacheExpr *a, CacheExpr *b)
+{
+ COMPARE_NODE_FIELD(arg);
+
+ return true;
+}
+
+static bool
_equalCoerceToDomain(CoerceToDomain *a, CoerceToDomain *b)
{
COMPARE_NODE_FIELD(arg);
@@ -2560,6 +2568,9 @@ equal(void *a, 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 683e751..6d7eaca 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 0e57f6c..0d38627 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -212,6 +212,9 @@ exprType(Node *expr)
case T_BooleanTest:
type = BOOLOID;
break;
+ case T_CacheExpr:
+ type = exprType((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
type = ((CoerceToDomain *) expr)->resulttype;
break;
@@ -792,6 +795,9 @@ exprCollation(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 = ((CoerceToDomain *) expr)->resultcollid;
break;
@@ -1263,6 +1269,10 @@ exprLocation(Node *expr)
/* just use argument's location */
loc = exprLocation((Node *) ((BooleanTest *) expr)->arg);
break;
+ case T_CacheExpr:
+ /* original expression location */
+ loc = exprLocation((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *cexpr = (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 31af47f..58dcc26 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1367,6 +1367,14 @@ _outBooleanTest(StringInfo str, BooleanTest *node)
}
static void
+_outCacheExpr(StringInfo str, CacheExpr *node)
+{
+ WRITE_NODE_TYPE("CACHEEXPR");
+
+ WRITE_NODE_FIELD(arg);
+}
+
+static void
_outCoerceToDomain(StringInfo str, CoerceToDomain *node)
{
WRITE_NODE_TYPE("COERCETODOMAIN");
@@ -2918,6 +2926,9 @@ _outNode(StringInfo str, 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/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5c18b72..650b32f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -164,6 +164,26 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
glob->lastRowMarkId = 0;
glob->transientPlan = false;
+ /*
+ * glob->isSimple is a hint to eval_const_expressions() and PL/pgSQL that
+ * this statement is potentially a simple expression -- it contains no
+ * table references, no subqueries and no join clauses.
+ *
+ * We need this here because this prevents insertion of CacheExpr, which
+ * would break simple expressions in PL/pgSQL. Such queries wouldn't
+ * benefit from constant caching anyway.
+ *
+ * The actual definition of a simple statement is more strict, but we
+ * don't want to spend that checking overhead here.
+ *
+ * Caveat: Queries with set-returning functions in SELECT list could
+ * still potentially benefit from caching, but we don't check that now.
+ */
+ glob->isSimple = (parse->commandType == CMD_SELECT &&
+ parse->jointree->fromlist == NIL &&
+ parse->hasSubLinks == FALSE &&
+ parse->cteList == NIL);
+
/* Determine what fraction of the plan is likely to be scanned */
if (cursorOptions & CURSOR_OPT_FAST_PLAN)
{
@@ -240,6 +260,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
result->relationOids = glob->relationOids;
result->invalItems = glob->invalItems;
result->nParamExec = list_length(glob->paramlist);
+ result->isSimple = glob->isSimple;
return result;
}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index ad02950..f85a869 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -49,6 +49,9 @@
#include "utils/typcache.h"
+bool enable_cacheexpr = true;
+
+
typedef struct
{
PlannerInfo *root;
@@ -62,6 +65,7 @@ typedef struct
List *active_fns;
Node *case_val;
bool estimate;
+ bool use_cache; /* insert cache nodes where possible? */
} eval_const_expressions_context;
typedef struct
@@ -97,43 +101,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 +2039,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 +2082,10 @@ 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);
+ context.use_cache = enable_cacheexpr &&
+ (root == NULL || !root->glob->isSimple);
+
+ return caching_const_expressions_mutator(node, &context);
}
/*--------------------
@@ -2084,12 +2103,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 = false; /* short-circuit some checks */
context.boundParams = root->glob->boundParams; /* bound Params */
/* we do not need to mark the plan as depending on inlined functions */
@@ -2097,13 +2118,44 @@ 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);
+ context.use_cache = false; /* no caching, planner only evaluates once */
+
+ 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 && context->use_cache)
+ 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 (if context->use_cache is enabled).
+ */
+static Node *
+const_expressions_mutator(Node *node,
+ eval_const_expressions_context *context,
+ bool *cachable)
+{
+ Assert(!context->use_cache || *cachable == true);
+
if (node == NULL)
return NULL;
switch (nodeTag(node))
@@ -2112,6 +2164,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 +2223,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 +2241,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 +2267,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 +2287,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 +2325,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 +2404,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 +2421,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 +2479,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 +2508,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 +2533,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 +2559,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 +2572,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 +2608,6 @@ eval_const_expressions_mutator(Node *node,
case T_CoerceViaIO:
{
CoerceViaIO *expr = (CoerceViaIO *) node;
- Expr *arg;
List *args;
Oid outfunc;
bool outtypisvarlena;
@@ -2540,9 +2619,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 +2630,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 +2641,8 @@ eval_const_expressions_mutator(Node *node,
InvalidOid,
InvalidOid,
&args,
- false, true, context);
+ true, context,
+ cachable);
if (simple) /* successfully simplified output fn */
{
/*
@@ -2594,7 +2672,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 +2684,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 +2701,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 +2726,17 @@ 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 (context->use_cache && 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 +2754,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 +2825,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 +2852,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 +2879,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 +2892,23 @@ eval_const_expressions_mutator(Node *node,
newcasewhen->result = (Expr *) caseresult;
newcasewhen->location = oldcasewhen->location;
newargs = lappend(newargs, newcasewhen);
+
+ if (condCachable)
+ {
+ if (context->use_cache && is_cache_useful((Expr *) casecond))
+ cachable_args = lappend(cachable_args, &newcasewhen->expr);
+ }
+ else
+ *cachable = false;
+
+ if (resultCachable)
+ {
+ if (context->use_cache && is_cache_useful((Expr *) caseresult))
+ cachable_args = lappend(cachable_args, &newcasewhen->result);
+ }
+ else
+ *cachable = false;
+
continue;
}
@@ -2809,8 +2923,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 (context->use_cache && is_cache_useful((Expr *) defresult))
+ cachable_args = lappend(cachable_args, &defresult);
+ }
+ else
+ *cachable = false;
+ }
context->case_val = save_case_val;
@@ -2820,6 +2947,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 +2980,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 +2993,15 @@ eval_const_expressions_mutator(Node *node,
List *newelems;
ListCell *element;
+ *cachable = false; /* XXX cachable! */
+
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 +3028,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 +3049,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 (context->use_cache && is_cache_useful((Expr *) arg))
+ cachable_args = lappend(cachable_args, &llast(newargs));
+ }
+ else
+ *cachable = false;
}
/*
@@ -2921,6 +3078,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 +3108,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 +3170,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 +3255,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;
@@ -3142,22 +3315,40 @@ eval_const_expressions_mutator(Node *node,
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
- return eval_const_expressions_mutator((Node *) phv->phexpr,
- context);
+ return const_expressions_mutator((Node *) phv->phexpr,
+ context,
+ cachable);
}
break;
+ case T_CacheExpr:
+ {
+ /*
+ * The planner asked us to re-simplify a simplified expression
+ * tree. Strip CacheExpr nodes since the planner only
+ * evaluates the expression once.
+ */
+ CacheExpr *cache = (CacheExpr *) node;
+
+ Assert(context->estimate && !context->use_cache);
+
+ return const_expressions_mutator((Node *) cache->arg, context, cachable);
+ }
default:
break;
}
+ if (!IsA(node, Const))
+ *cachable = false; /* Everything else is not cachable */
+
/*
* 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 +3370,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 +3401,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 +3424,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 +3466,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 +3520,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 +3544,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 +3567,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 +3609,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 +3717,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 +3736,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; /* XXX use bitmapset instead? */
/*
* We have three strategies for simplification: execute the function to
@@ -3484,19 +3760,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 (context->use_cache && 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 +3843,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 +3981,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 +3989,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 +4002,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 +4012,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 +4031,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 +4120,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 +4133,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 +4150,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 +4253,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 +4533,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 +4613,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
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 29df748..b0190bb 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/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index da7b6d4..6cd6eab 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -765,6 +765,16 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
+ /* XXX Probably won't need this, but it's useful for debugging */
+ {"enable_cacheexpr", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enables caching of stable execution-time-constant expressions."),
+ NULL
+ },
+ &enable_cacheexpr,
+ true,
+ NULL, NULL, NULL
+ },
+ {
{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
gettext_noop("Enables genetic query optimization."),
gettext_noop("This algorithm attempts to do planning without "
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0a89f18..42e28fc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -673,6 +673,22 @@ 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 *subexpr; /* state of sub-expression */
+
+ 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 c5ed6cb..c279ddc 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 *subexpr);
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 3a24089..eb2009c 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/plannodes.h b/src/include/nodes/plannodes.h
index 6685864..6c4869a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
List *invalItems; /* other dependencies, as PlanInvalItems */
int nParamExec; /* number of PARAM_EXEC Params used */
+
+ bool isSimple; /* possibly simple expression: no CacheExpr */
} PlannedStmt;
/* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 28a2b12..d599d23 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/include/nodes/relation.h b/src/include/nodes/relation.h
index a400960..7338b94 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
Index lastRowMarkId; /* highest PlanRowMark ID assigned */
bool transientPlan; /* redo plan when TransactionXmin changes? */
+
+ bool isSimple; /* possibly simple expression: no CacheExpr */
} PlannerGlobal;
/* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 125808a..61412a4 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -61,6 +61,7 @@ extern bool enable_nestloop;
extern bool enable_material;
extern bool enable_mergejoin;
extern bool enable_hashjoin;
+extern bool enable_cacheexpr; /* XXX probably a wrong place for this */
extern int constraint_exclusion;
extern double clamp_row_est(double nrows);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 717ad79..a107056 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5446,6 +5446,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 +5683,15 @@ exec_simple_check_node(Node *node)
return TRUE;
}
+ case T_CacheExpr:
+ /*
+ * The planner should prevent generation of CacheExprs in queries
+ * that can be deemed simple, see standard_planner()
+ */
+ ereport(WARNING,
+ (errmsg("potentially-simple expression contains CacheExpr")));
+ return FALSE;
+
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..1e4664b
--- /dev/null
+++ b/src/test/regress/expected/cache.out
@@ -0,0 +1,676 @@
+--
+-- 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"
+-- 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/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 5f20c93..813741a 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -2,6 +2,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
name | setting
----------------------+---------
enable_bitmapscan | on
+ enable_cacheexpr | on
enable_hashagg | on
enable_hashjoin | on
enable_indexonlyscan | on
@@ -12,7 +13,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(11 rows)
+(12 rows)
CREATE TABLE foo2(fooid int, f2 int);
INSERT INTO foo2 VALUES(1, 11);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index b47b08b..8b6e563 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 57806b5..bc7e325 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -91,6 +91,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..8cd8908
--- /dev/null
+++ b/src/test/regress/sql/cache.sql
@@ -0,0 +1,179 @@
+--
+-- 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
+\.
+
+-- 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, Dec 5, 2011 at 21:04, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:
Yet another idea would be to leave the CacheExprs there, but provide a way
to reset the caches. PL/pgSQL could then reset the caches between every
invocation. Or pass a flag to ExecInitExpr() to skip through the CacheExprs.
Great idea, I've ripped out all the conditional CacheExpr generation
logic from the planner and added a new boolean to CacheExprState
instead. This makes me happy as I'm now rid of most kludgy bits and
reduced the patch size somewhat. This also solves Tom's concern.
ExecInitExpr enables the cache when its 'PlanState *parent' attribute
isn't NULL. On the one hand this works out well since PlanState always
has a predictable life cycle thus caching is always safe.
On the other hand, a few places lose caching support this way since
they don't go through the planner:
* Column defaults in a COPY FROM operation. Common use case is
'timestamp default now()'
This might be a significant loss in some data-loading scenarios.
* ALTER TABLE t ALTER col TYPE x USING some_expr(); No big loss here.
Regards,
Marti
Attachments:
cacheexpr-v4-wip.patchtext/x-patch; charset=US-ASCII; name=cacheexpr-v4-wip.patchDownload
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 887e5ce8..3498db9 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -51,6 +51,7 @@
#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"
@@ -157,6 +158,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);
@@ -3754,6 +3761,62 @@ 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->subexpr, econtext, isNull, isDone);
+
+ if (!cstate->cacheEnabled)
+ return result; /* Cache disabled, pass thru the result */
+
+ /* Set-returning expressions can't be cached */
+ Assert(isDone == NULL || *isDone == ExprSingleResult);
+
+ 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
*
@@ -4858,6 +4921,26 @@ ExecInitExpr(Expr *node, PlanState *parent)
state = (ExprState *) gstate;
}
break;
+ case T_CacheExpr:
+ {
+ CacheExpr *cache = (CacheExpr *) node;
+ CacheExprState *cstate = makeNode(CacheExprState);
+
+ cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCacheExpr;
+ cstate->subexpr = ExecInitExpr(cache->arg, parent);
+ /*
+ * We only enable cache when there's an associated PlanState.
+ * For other callers we can't guarantee the
+ * lifetime/invalidation of the cache.
+ */
+ if (parent)
+ cstate->cacheEnabled = true;
+ else
+ cstate->cacheEnabled = false;
+
+ state = (ExprState *) cstate;
+ }
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c70a5bd..6f809f2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1684,6 +1684,19 @@ _copyBooleanTest(BooleanTest *from)
}
/*
+ * _copyCacheExpr
+ */
+static CacheExpr *
+_copyCacheExpr(CacheExpr *from)
+{
+ CacheExpr *newnode = makeNode(CacheExpr);
+
+ COPY_NODE_FIELD(arg);
+
+ return newnode;
+}
+
+/*
* _copyCoerceToDomain
*/
static CoerceToDomain *
@@ -4004,6 +4017,9 @@ copyObject(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 f490a7a..af7ebe7 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -678,6 +678,14 @@ _equalBooleanTest(BooleanTest *a, BooleanTest *b)
}
static bool
+_equalCacheExpr(CacheExpr *a, CacheExpr *b)
+{
+ COMPARE_NODE_FIELD(arg);
+
+ return true;
+}
+
+static bool
_equalCoerceToDomain(CoerceToDomain *a, CoerceToDomain *b)
{
COMPARE_NODE_FIELD(arg);
@@ -2560,6 +2568,9 @@ equal(void *a, 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 683e751..6d7eaca 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 0e57f6c..0d38627 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -212,6 +212,9 @@ exprType(Node *expr)
case T_BooleanTest:
type = BOOLOID;
break;
+ case T_CacheExpr:
+ type = exprType((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
type = ((CoerceToDomain *) expr)->resulttype;
break;
@@ -792,6 +795,9 @@ exprCollation(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 = ((CoerceToDomain *) expr)->resultcollid;
break;
@@ -1263,6 +1269,10 @@ exprLocation(Node *expr)
/* just use argument's location */
loc = exprLocation((Node *) ((BooleanTest *) expr)->arg);
break;
+ case T_CacheExpr:
+ /* original expression location */
+ loc = exprLocation((Node *) ((CacheExpr *) expr)->arg);
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *cexpr = (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 31af47f..58dcc26 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1367,6 +1367,14 @@ _outBooleanTest(StringInfo str, BooleanTest *node)
}
static void
+_outCacheExpr(StringInfo str, CacheExpr *node)
+{
+ WRITE_NODE_TYPE("CACHEEXPR");
+
+ WRITE_NODE_FIELD(arg);
+}
+
+static void
_outCoerceToDomain(StringInfo str, CoerceToDomain *node)
{
WRITE_NODE_TYPE("COERCETODOMAIN");
@@ -2918,6 +2926,9 @@ _outNode(StringInfo str, 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/util/clauses.c b/src/backend/optimizer/util/clauses.c
index ad02950..a559af0 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; /* XXX cachable! */
+
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,26 @@ 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:
+ /* */
default:
+ /* Everything else is not cachable */
+ *cachable = false;
break;
}
@@ -3154,10 +3329,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 +3355,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 +3386,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 +3409,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 +3451,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 +3505,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 +3529,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 +3552,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 +3594,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 +3702,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 +3721,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; /* XXX use bitmapset instead? */
/*
* We have three strategies for simplification: execute the function to
@@ -3484,19 +3745,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 +3828,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 +3966,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 +3974,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 +3987,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 +3997,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 +4016,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 +4105,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 +4118,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 +4135,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 +4238,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 +4518,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 +4598,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
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 29df748..b0190bb 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/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0a89f18..8ff570a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -673,6 +673,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 *subexpr; /* state of sub-expression */
+
+ bool cacheEnabled;
+ 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 c5ed6cb..fe488db 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 3a24089..eb2009c 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 28a2b12..d599d23 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 717ad79..a417715 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5446,6 +5446,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 +5683,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..1e4664b
--- /dev/null
+++ b/src/test/regress/expected/cache.out
@@ -0,0 +1,676 @@
+--
+-- 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"
+-- 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 b47b08b..8b6e563 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 57806b5..bc7e325 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -91,6 +91,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..8cd8908
--- /dev/null
+++ b/src/test/regress/sql/cache.sql
@@ -0,0 +1,179 @@
+--
+-- 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
+\.
+
+-- 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 Wed, Dec 7, 2011 at 00:29, Marti Raudsepp <marti@juffo.org> wrote:
ExecInitExpr enables the cache when its 'PlanState *parent' attribute
isn't NULL
[...]
On the other hand, a few places lose caching support this way since
they don't go through the planner:
* Column defaults in a COPY FROM operation. Common use case is
'timestamp default now()'
This might be a significant loss in some data-loading scenarios.
* ALTER TABLE t ALTER col TYPE x USING some_expr(); No big loss here.
Let me rephrase that as a question: Does it seem worthwhile to add a
new argument to ExecInitExpr to handle those two cases? Does relying
on the PlanState argument being NULL seem like a bad idea for any
reason?
PS: I forgot to mention that 2 test cases covering the two above query
types are deliberately left failing in the v4-wip patch.
Regards,
Marti
Marti Raudsepp <marti@juffo.org> writes:
Let me rephrase that as a question: Does it seem worthwhile to add a
new argument to ExecInitExpr to handle those two cases?
Possibly. Another way would be to keep its API as-is and introduce a
different function name for the other behavior. I would think that
we'd always know for any given caller which behavior we need, so a
flag as such isn't notationally helpful.
Does relying
on the PlanState argument being NULL seem like a bad idea for any
reason?
Yes, that seemed like a pretty horrid idea when I read your description,
but I hadn't got round to looking at just how awful it might be.
regards, tom lane
On 12/07/2011 04:58 PM, Marti Raudsepp wrote:
PS: I forgot to mention that 2 test cases covering the two above query
types are deliberately left failing in the v4-wip patch.
It's not completely clear what happens next with this. Are you hoping
code churn here has calmed down enough for Jaime or someone else to try
and look at this more already? Or should we wait for a new update based
on the feedback that Heikki and Tom have already provided first, perhaps
one that proposes fixes for these two test cases?
One general suggestion about the fight with upstream changes you've run
into here. Now that you're settling into the git workflow, you might
consider publishing updates to a site like Github in the future too.
That lets testing of the code at the point you wrote it always
possible. Given just the patch, reviewers normally must reconcile any
bit rot before they can even compile your code to try it. That gets
increasingly sketchy the longer your patch waits before the next
CommitFest considers it. With a published git working tree, reviewers
can pull that for some hands-on testing whenever, even if a merge
wouldn't actually work out at that point. You just need to be careful
not to push an update that isn't self-consistent to the world. I
normally attach the best formatted patch I can and publish to Github.
Then reviewers can use whichever they find easier, and always have the
option of postponing a look at merge issues if they just want to play
with the program.
--
Greg Smith 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.us
On Sat, Dec 10, 2011 at 16:50, Greg Smith <greg@2ndquadrant.com> wrote:
Or should we wait for a new update based on the
feedback that Heikki and Tom have already provided first, perhaps one that
proposes fixes for these two test cases?
Yes, I will post and updated and rebased version tomorrow.
Now that you're settling into the git workflow, you might consider
publishing updates to a site like Github in the future too.
It's in the 'cache' branch on my Github:
https://github.com/intgr/postgres/commits/cache
(I linked to it in the v3 posting, but forgot to mention it later)
Regards,
Marti
On Sat, Dec 10, 2011 at 21:05, Marti Raudsepp <marti@juffo.org> wrote:
Yes, I will post and updated and rebased version tomorrow.
(Sorry, I'm 1 day late :)
Here's v5 of the patch. Changes from v4:
* Rebased over Thomas Munro's constifying patch
* ExecInitExpr is now split in two:
* ExecInitExprMutator, which now takes a context struct argument, to
prevent having to pass around 2 arguments through recursion every
time.
* ExecInitExpr, which is the public function, now has a new boolean
argument to enable/disable cache. All callers have been converted.
* A new test case for a VALUES() expression.
I don't know of anything lacking in the current patch so I'm not
planning to make changes until further feedback. I'm also not very
confident about the refactoring I made with ExecInitExpr, so
bikeshedding is welcome. :)
Patch attached. Also available in the 'cache' branch on my Github:
https://github.com/intgr/postgres/commits/cache
Regards,
Marti
Attachments:
cacheexpr-v5.patchtext/x-patch; charset=US-ASCII; name=cacheexpr-v5.patchDownload
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9c994ef..ce85f56 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 1ee201c..1b5129a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3601,7 +3601,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 84ba1a6..7041246 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3026,7 +3026,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 887e5ce8..666e3df 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 0701da4..f0aff13 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 cb780b6..2fda824 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 ec39f29..efdfd31 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 841ae69..9176d08 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 5d5727e..b0b7522 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 7bef8bb..03499a0 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 091aef9..7572055 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 c3c4db4..1a4cc51 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 a07686d..e321378 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 e3be5a2..bbece18 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 f2d356d..dde72c3 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 634931d..bc210cb 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 070f27c..ddcf216 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 9fe7841..09775e9 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 28c4412..9d2f806 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 5b652c9..06b4771 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 0e12bb5..c502c67 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 7e4d5de..d713813 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 69f47ff..265bcc6 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 d5260e4..bba5146 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 c90d405..df64300 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 bdebb6d..4cc7005 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 7178b52..6bf96ed 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 *
@@ -4004,6 +4017,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 9f7daf4..f8dfc4a 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);
@@ -2560,6 +2568,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 683e751..6d7eaca 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 48b0590..2603bcb 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 bef1e78..498df17 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");
@@ -2918,6 +2926,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/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 16137e0..3d7faae 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 3e4a775..e7dc6bf 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 29df748..b0190bb 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 bdd499b..9c52d7d 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 f5935e2..4c68dc3 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 c5ed6cb..fe488db 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 f0c6381..7181dcf 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 28a2b12..d599d23 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 717ad79..d5c6dcb 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 b47b08b..8b6e563 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 57806b5..bc7e325 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -91,6 +91,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 Sun, Dec 4, 2011 at 22:53, Heikki Linnakangas
<heikki.linnakangas@enterprisedb.com> wrote:
I wonder if it would be better to add the CacheExpr nodes to the tree as a
separate pass, instead of shoehorning it into eval_const_expressions? I
think would be more readable that way, even though a separate pass would be
more expensive. And there are callers of eval_const_expressions() that have
no use for the caching, like process_implied_equality().
I had an idea how to implement this approach with minimal performance
impact. It might well be a dumb idea, but I wanted to get it out
there.
The 'struct Expr' type could have another attribute that stores
whether its sub-expressions contain stable/volatile functions, and
whether it only contains of constant arguments. This attribute would
be filled in for every Expr by eval_const_expressions.
Based on this information, ExecInitExpr (which is pretty simple for
now) could decide where to inject CacheExprState nodes. It's easier to
make that decision there, since by that stage the cachability of the
parent node with each argument is already known. (Currently I have to
stick all cachable arguments in a temporary list until I've determined
whether all arguments are cachable, or just some of them are)
This would decouple expression caching from the planner entirely;
CacheExpr wouldn't exist anymore. AFAICT this would also let us get
rid of contain_mutable_functions_walker() and possibly others.
Regards,
Marti
Marti Raudsepp <marti@juffo.org> writes:
The 'struct Expr' type could have another attribute that stores
whether its sub-expressions contain stable/volatile functions, and
whether it only contains of constant arguments. This attribute would
be filled in for every Expr by eval_const_expressions.
Hmm. I'm a bit hesitant to burn the extra space that would be required
for that. On the other hand it is a relatively clean solution to
postponing the whether-to-actually-cache decisions until runtime.
But on the third hand, I don't know whether this is really much cleaner
than always injecting CacheExpr and having some runtime flag that
prevents it from caching. The argument that CacheExpr could confuse
equal() checks applies just as much to this, unless you make some ad-hoc
decision that equal() should ignore these flag bits.
This would decouple expression caching from the planner entirely;
CacheExpr wouldn't exist anymore. AFAICT this would also let us get
rid of contain_mutable_functions_walker() and possibly others.
Yeah, you could imagine getting rid of tree walks for both mutability
and returns-set tests, and maybe other things too --- once you've paid
the price of an expression attributes flag word, there's room for quite
a few bits in there. Don't know offhand if that's attractive enough to
tip the scales.
regards, tom lane
I think what would be helpful here next is a self-contained benchmarking
script. You posted an outline of what you did for the original version
at
http://archives.postgresql.org/message-id/CABRT9RC-1wGxZC_Z5mwkdk70fgY2DRX3sLXzdP4voBKuKPZDow@mail.gmail.com
It doesn't sound like it would be hard to turn that into a little shell
script. I'd rather see one from you mainly so I don't have to worry
about whether I duplicated your procedure correctly or not. Don't even
worry about "best of 3", just run it three times, grep out the line that
shows the TPS number, and show them all. One of the first questions I
have is whether the performance changed between there and your v5. A
self-contained and fully automated performance test case would ease
review, and give some performance regression testing for other suggested
changes to use as a reference.
--
Greg Smith 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.us
On Fri, Dec 16, 2011 at 09:13, Greg Smith <greg@2ndquadrant.com> wrote:
I think what would be helpful here next is a self-contained benchmarking
script.
Alright, a simple script is attached.
One of the first questions I have is whether
the performance changed between there and your v5.
Not those testcases anyway, since the executor side changed very
little since my original patch. I'm more worried about the planner, as
that's where the bulk of the code is. There is a small but measurable
regression there (hence the latter 2 tests).
I'm running with shared_buffers=256MB
Unpatched Postgres (git commit 6d09b210):
select * from ts where ts between to_timestamp('2005-01-01',
'YYYY-MM-DD') and to_timestamp('2005-01-01', 'YYYY-MM-DD');
tps = 1.194965 (excluding connections establishing)
tps = 1.187269 (excluding connections establishing)
tps = 1.192899 (excluding connections establishing)
select * from ts where ts>now();
tps = 8.986270 (excluding connections establishing)
tps = 8.974889 (excluding connections establishing)
tps = 8.976249 (excluding connections establishing)
/*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')
tps = 11647.167712 (excluding connections establishing)
tps = 11836.858624 (excluding connections establishing)
tps = 11658.372658 (excluding connections establishing)
/*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')
tps = 9762.035996 (excluding connections establishing)
tps = 9695.627270 (excluding connections establishing)
tps = 9791.141908 (excluding connections establishing)
Patched Postgres (v5 applied on top of above commit):
select * from ts where ts between to_timestamp('2005-01-01',
'YYYY-MM-DD') and to_timestamp('2005-01-01', 'YYYY-MM-DD');
tps = 8.580669 (excluding connections establishing)
tps = 8.583070 (excluding connections establishing)
tps = 8.544887 (excluding connections establishing)
select * from ts where ts>now();
tps = 10.467226 (excluding connections establishing)
tps = 10.429396 (excluding connections establishing)
tps = 10.441230 (excluding connections establishing)
/*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')
tps = 11578.768193 (excluding connections establishing)
tps = 11594.920258 (excluding connections establishing)
tps = 11667.866443 (excluding connections establishing)
/*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')
tps = 9505.943115 (excluding connections establishing)
tps = 9502.316023 (excluding connections establishing)
tps = 9546.047208 (excluding connections establishing)
Regards,
Marti
Attachments:
That looks easy enough to try out now, thanks for the script.
Jaime was interested in trying this out, and I hope he still finds time
to do that soon. Now that things have settled down and it's obvious how
to start testing, that should be a project he can take on. I don't
think this is going to reach ready to commit in the next few days
though, so I'm going to mark it as returned for this CommitFest. The
results you're showing are quite interesting, perhaps someone can help
sorting out the parsing area that's accidentally being decelerated.
--
Greg Smith 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.us
On Fri, Dec 16, 2011 at 18:08, Greg Smith <greg@2ndquadrant.com> wrote:
I don't think this
is going to reach ready to commit in the next few days though, so I'm going
to mark it as returned for this CommitFest.
Fair enough, I just hope this doesn't get dragged into the next
commitfest without feedback.
perhaps someone can help sorting out the parsing area
that's accidentally being decelerated.
Well the slowdown isn't "accidental", I think it's expected since I'm
adding a fair bit of code to expression processing (which isn't all
pretty).
It could be reduced by doing the caching decisions in a 2nd pass,
inside ExecInitExpr, but it would mean adding an extra field to
'struct Expr' and require a significant rewrite of the patch. I'm not
sure if it's worthwhile to attempt that approach:
http://archives.postgresql.org/pgsql-hackers/2011-12/msg00483.php
Regards,
Marti
On Fri, Dec 16, 2011 at 11:08 AM, Greg Smith <greg@2ndquadrant.com> wrote:
That looks easy enough to try out now, thanks for the script.
Jaime was interested in trying this out, and I hope he still finds time to
do that soon.
Actually i tried some benchmarks with the original version of the
patch and saw some regression with normal pgbench runs, but it wasn't
much... so i was trying to found out some queries that show benefit
now that we have it, that will be a lot more easier
--
Jaime Casanova www.2ndQuadrant.com
Professional PostgreSQL: Soporte 24x7 y capacitación
On Fri, Dec 16, 2011 at 21:32, Jaime Casanova <jaime@2ndquadrant.com> wrote:
Actually i tried some benchmarks with the original version of the
patch and saw some regression with normal pgbench runs, but it wasn't
much... so i was trying to found out some queries that show benefit
now that we have it, that will be a lot more easier
Ah, one more trick for testing this patch: if you build with
-DDEBUG_CACHEEXPR=1 then EXPLAIN VERBOSE displays cached
subexpressions between a CACHE[...] marker.
Regards,
Marti
On Sat, Dec 17, 2011 at 12:25 PM Marti Raudsepp <marti@juffo.org> wrote:
Ah, one more trick for testing this patch: if you build with
-DDEBUG_CACHEEXPR=1 then EXPLAIN VERBOSE displays cached
subexpressions between a CACHE[...] marker.
Unless I missed something, this 2011 thread is the latest one about
this patch, and the patch was never applied, and nothing similar ever
got done either. Is that correct? If so, was there any particular
reason why this wasn't judged to be a good idea, or did it just not
get enough attention?
This was on my mind today because of a FDW pushdown question that came
up. Someone observed that a predicate of the form foreign_table_column
pushable_operator hairy_but_stable_expression is not pushed down. In
theory, it could be: just compute the value of
hairy_but_stable_expression locally, and then send the result across.
The trick is that it requires identifying a maximal stable
subexpression. In this case, the whole expression isn't stable,
because every row will provide a different value for
foreign_table_column, but the subexpression which is the right child
of the toplevel OpExpr is stable. Identifying that seems like it could
be somewhat expensive and I seem to vaguely recall some discussion of
that issue, but I think not on this thread. But if this patch - or
some other one - happened to inject a CacheExpr at the right place in
the plan tree, then postgres_fdw could leverage that existing
determination in a pretty straightforward way, precomputing the cached
value locally and then pushing the clause if otherwise safe.
That's also just a fringe benefit. Marti's test results show
significant speedup in all-local cases, which seem likely to benefit a
pretty broad class of queries. We'd probably have to give some thought
to how the technique would work with Andres's rewrite of expression
evaluation, but I imagine that's a solvable problem.
--
Robert Haas
EDB: http://www.enterprisedb.com