cacheexpr-v2.patch
text/x-patch
Filename: cacheexpr-v2.patch
Type: text/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v2
| File | + | − |
|---|---|---|
| src/backend/executor/execQual.c | 75 | 0 |
| src/backend/nodes/copyfuncs.c | 16 | 0 |
| src/backend/nodes/equalfuncs.c | 11 | 0 |
| src/backend/nodes/makefuncs.c | 15 | 0 |
| src/backend/nodes/nodeFuncs.c | 28 | 1 |
| src/backend/nodes/outfuncs.c | 11 | 0 |
| src/backend/optimizer/util/clauses.c | 403 | 177 |
| src/backend/parser/parse_expr.c | 1 | 0 |
| src/backend/utils/adt/ruleutils.c | 19 | 1 |
| src/backend/utils/misc/guc.c | 10 | 0 |
| src/include/nodes/execnodes.h | 16 | 0 |
| src/include/nodes/makefuncs.h | 1 | 0 |
| src/include/nodes/nodes.h | 2 | 0 |
| src/include/nodes/primnodes.h | 11 | 0 |
| src/include/optimizer/cost.h | 1 | 0 |
| src/pl/plpgsql/src/pl_exec.c | 7 | 0 |
| src/test/regress/expected/cache.out | 411 | 0 |
| src/test/regress/expected/rangefuncs.out | 2 | 1 |
| src/test/regress/parallel_schedule | 1 | 1 |
| src/test/regress/serial_schedule | 1 | 0 |
| src/test/regress/sql/cache.sql | 90 | 0 |
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 80f08d8..c565ecb 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,14 @@ 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 +3755,61 @@ ExecEvalBooleanTest(GenericExprState *bstate,
}
}
+/* ----------------------------------------------------------------
+ * ExecEvalCacheExpr
+ *
+ * XXX
+ * ----------------------------------------------------------------
+ */
+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)->subexpr);
+ 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 +4914,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->subexpr, 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..f3e1113 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(subexpr);
+
+ 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..6a8ce05 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(subexpr);
+
+ 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..a070ccd 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 *subexpr)
+{
+ CacheExpr *cacheexpr;
+
+ cacheexpr = makeNode(CacheExpr);
+ cacheexpr->subexpr = subexpr;
+
+ return cacheexpr;
+}
+
+/*
* makeDefElem -
* build a DefElem node
*
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 0e57f6c..c288e75 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)->subexpr);
+ 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)->subexpr);
+ break;
case T_CoerceToDomain:
coll = ((CoerceToDomain *) expr)->resultcollid;
break;
@@ -999,6 +1005,10 @@ exprSetCollation(Node *expr, Oid collation)
case T_CurrentOfExpr:
Assert(!OidIsValid(collation)); /* result is always boolean */
break;
+ case T_CacheExpr:
+ /* I think this should never occur, but leave this here just in case */
+ elog(ERROR, "Can't set collation on CacheExpr");
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1263,6 +1273,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)->subexpr);
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *cexpr = (CoerceToDomain *) expr;
@@ -1722,6 +1736,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)->subexpr, context);
case T_CoerceToDomain:
return walker(((CoerceToDomain *) node)->arg, context);
case T_TargetEntry:
@@ -2374,6 +2390,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->subexpr, cache->subexpr, Expr *);
+ return (Node *) newnode;
+ }
+ break;
case T_CoerceToDomain:
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
@@ -2522,7 +2548,6 @@ expression_tree_mutator(Node *node,
return NULL;
}
-
/*
* query_tree_mutator --- initiate modification of a Query's expressions
*
@@ -2781,6 +2806,8 @@ bool
return walker(((NullTest *) node)->arg, context);
case T_BooleanTest:
return walker(((BooleanTest *) node)->arg, context);
+ case T_CacheExpr:
+ return walker(((CacheExpr *) node)->subexpr, context);
case T_JoinExpr:
{
JoinExpr *join = (JoinExpr *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0d0ce3c..131cc47 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(subexpr);
+}
+
+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/util/clauses.c b/src/backend/optimizer/util/clauses.c
index baa90fa..e0ae622 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 cache; /* insert cache nodes where possible? */
} eval_const_expressions_context;
typedef struct
@@ -96,43 +100,49 @@ 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 *caching_const_expressions_mutator(Node *node,
+ eval_const_expressions_context *context);
static Node *eval_const_expressions_mutator(Node *node,
- eval_const_expressions_context *context);
+ 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 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,
@@ -2065,7 +2075,9 @@ 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.cache = enable_cacheexpr; /* XXX this should be passed in here as argument? */
+
+ return caching_const_expressions_mutator(node, &context);
}
/*--------------------
@@ -2089,6 +2101,7 @@ 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 +2109,53 @@ 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.cache = false; /* no caching, planner only evaluates once */
+
+ return eval_const_expressions_mutator(node, &context, &isCachable);
+}
+
+/*
+ * Calls eval_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 = eval_const_expressions_mutator(node, context, &isCachable);
+ if (isCachable && context->cache)
+ node = (Node *) insert_cache((Expr *) node);
+
+ return node;
}
+
static Node *
eval_const_expressions_mutator(Node *node,
- eval_const_expressions_context *context)
+ eval_const_expressions_context *context,
+ bool *cachable)
{
+ if (context->cache)
+ Assert(*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 +2195,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 +2218,8 @@ eval_const_expressions_mutator(Node *node,
expr->funccollid,
expr->inputcollid,
&args,
- has_named_args, true, context);
+ true, context,
+ cachable); /* XXX untested */
if (simple) /* successfully simplified it */
return (Node *) simple;
@@ -2212,20 +2243,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 +2263,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,7 +2300,7 @@ eval_const_expressions_mutator(Node *node,
if (IsA(node, DistinctExpr))
{
DistinctExpr *expr = (DistinctExpr *) node;
- List *args;
+ List *args = expr->args;
ListCell *arg;
bool has_null_input = false;
bool all_null_input = true;
@@ -2286,15 +2309,6 @@ eval_const_expressions_mutator(Node *node,
DistinctExpr *newexpr;
/*
- * Reduce constants in the DistinctExpr's arguments. We know args is
- * either NIL or a List node, so we can call expression_tree_mutator
- * directly rather than recursing to self.
- */
- args = (List *) expression_tree_mutator((Node *) expr->args,
- eval_const_expressions_mutator,
- (void *) context);
-
- /*
* We must do our own check for NULLs because DistinctExpr has
* different results for NULL input than the underlying operator does.
*/
@@ -2339,7 +2353,11 @@ eval_const_expressions_mutator(Node *node,
expr->opcollid,
expr->inputcollid,
&args,
- false, false, context);
+ false, context,
+ cachable);
+
+ *cachable = false; /* XXX cachable? */
+
if (simple) /* successfully simplified it */
{
/*
@@ -2354,6 +2372,8 @@ eval_const_expressions_mutator(Node *node,
return (Node *) csimple;
}
}
+ else
+ *cachable = false; /* XXX cachable? */
/*
* The expression cannot be simplified any further, so build and
@@ -2384,7 +2404,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 +2426,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)
@@ -2425,7 +2447,8 @@ eval_const_expressions_mutator(Node *node,
Assert(list_length(expr->args) == 1);
arg = eval_const_expressions_mutator(linitial(expr->args),
- context);
+ context,
+ cachable);
/*
* Use negate_clause() to see if we can simplify away the
@@ -2448,6 +2471,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; /* XXX cachable? */
return node;
}
if (IsA(node, RelabelType))
@@ -2461,7 +2485,8 @@ eval_const_expressions_mutator(Node *node,
Node *arg;
arg = eval_const_expressions_mutator((Node *) relabel->arg,
- context);
+ context,
+ cachable);
/*
* If we find stacked RelabelTypes (eg, from foo :: int :: oid) we can
@@ -2495,7 +2520,6 @@ eval_const_expressions_mutator(Node *node,
if (IsA(node, CoerceViaIO))
{
CoerceViaIO *expr = (CoerceViaIO *) node;
- Expr *arg;
List *args;
Oid outfunc;
bool outtypisvarlena;
@@ -2503,13 +2527,14 @@ eval_const_expressions_mutator(Node *node,
Oid intypioparam;
Expr *simple;
CoerceViaIO *newexpr;
+ bool isCachable = true;
+
+ *cachable = false; /* XXX cachable? */
/*
* 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 +2545,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,9 +2554,11 @@ eval_const_expressions_mutator(Node *node,
InvalidOid,
InvalidOid,
&args,
- false, true, context);
+ true, context,
+ &isCachable);
if (simple) /* successfully simplified output fn */
{
+ isCachable = true;
/*
* Input functions may want 1 to 3 arguments. We always supply
* all three, trusting that nothing downstream will complain.
@@ -2550,7 +2577,8 @@ eval_const_expressions_mutator(Node *node,
expr->resultcollid,
InvalidOid,
&args,
- false, true, context);
+ true, context,
+ &isCachable);
if (simple) /* successfully simplified input fn */
return (Node *) simple;
}
@@ -2561,7 +2589,7 @@ eval_const_expressions_mutator(Node *node,
* argument.
*/
newexpr = makeNode(CoerceViaIO);
- newexpr->arg = arg;
+ newexpr->arg = (Expr *) linitial(args); /* XXX this didn't use simplified arg before? */
newexpr->resulttype = expr->resulttype;
newexpr->resultcollid = expr->resultcollid;
newexpr->coerceformat = expr->coerceformat;
@@ -2579,7 +2607,8 @@ eval_const_expressions_mutator(Node *node,
* new ArrayCoerceExpr.
*/
arg = (Expr *) eval_const_expressions_mutator((Node *) expr->arg,
- context);
+ context,
+ cachable);
newexpr = makeNode(ArrayCoerceExpr);
newexpr->arg = arg;
@@ -2603,11 +2632,24 @@ 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->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;
}
if (IsA(node, CollateExpr))
{
+ /* XXX cachable? */
/*
* If we can simplify the input to a constant, then we don't need the
* CollateExpr node at all: just change the constcollid field of the
@@ -2619,7 +2661,8 @@ eval_const_expressions_mutator(Node *node,
Node *arg;
arg = eval_const_expressions_mutator((Node *) collate->arg,
- context);
+ context,
+ cachable); /* XXX cachable? */
if (arg && IsA(arg, Const))
{
@@ -2691,8 +2734,10 @@ eval_const_expressions_mutator(Node *node,
Node *defresult = NULL;
ListCell *arg;
+ *cachable = false;
+
/* 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 */
@@ -2718,8 +2763,8 @@ eval_const_expressions_mutator(Node *node,
/* Simplify this alternative's test condition */
casecond =
- eval_const_expressions_mutator((Node *) oldcasewhen->expr,
- context);
+ caching_const_expressions_mutator((Node *) oldcasewhen->expr,
+ context);
/*
* If the test condition is constant FALSE (or NULL), then drop
@@ -2738,7 +2783,7 @@ eval_const_expressions_mutator(Node *node,
/* Simplify this alternative's result value */
caseresult =
- eval_const_expressions_mutator((Node *) oldcasewhen->result,
+ caching_const_expressions_mutator((Node *) oldcasewhen->result,
context);
/* If non-constant test condition, emit a new WHEN node */
@@ -2764,7 +2809,7 @@ 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,
+ caching_const_expressions_mutator((Node *) caseexpr->defresult,
context);
context->case_val = save_case_val;
@@ -2792,7 +2837,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 +2850,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);
@@ -2837,13 +2887,15 @@ eval_const_expressions_mutator(Node *node,
List *newargs;
ListCell *arg;
+ *cachable = false; /* XXX cachable? */
+
newargs = NIL;
foreach(arg, coalesceexpr->args)
{
Node *e;
- e = eval_const_expressions_mutator((Node *) lfirst(arg),
- context);
+ e = caching_const_expressions_mutator((Node *) lfirst(arg),
+ context);
/*
* We can remove null constants from the list. For a non-null
@@ -2894,8 +2946,10 @@ eval_const_expressions_mutator(Node *node,
FieldSelect *newfselect;
Node *arg;
- arg = eval_const_expressions_mutator((Node *) fselect->arg,
- context);
+ *cachable = false; /* XXX cachable? */
+
+ arg = caching_const_expressions_mutator((Node *) fselect->arg,
+ context);
if (arg && IsA(arg, Var) &&
((Var *) arg)->varattno == InvalidAttrNumber)
{
@@ -2947,7 +3001,8 @@ eval_const_expressions_mutator(Node *node,
Node *arg;
arg = eval_const_expressions_mutator((Node *) ntest->arg,
- context);
+ context,
+ cachable);
if (arg && IsA(arg, RowExpr))
{
/*
@@ -3030,7 +3085,8 @@ eval_const_expressions_mutator(Node *node,
Node *arg;
arg = eval_const_expressions_mutator((Node *) btest->arg,
- context);
+ context,
+ cachable);
if (arg && IsA(arg, Const))
{
Const *carg = (Const *) arg;
@@ -3075,7 +3131,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;
@@ -3087,17 +3143,35 @@ eval_const_expressions_mutator(Node *node,
PlaceHolderVar *phv = (PlaceHolderVar *) node;
return eval_const_expressions_mutator((Node *) phv->phexpr,
- context);
+ 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->cache);
+
+ return eval_const_expressions_mutator((Node *) cache->subexpr, 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,16 +3193,24 @@ 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.
+ *
+ * XXX update comment
*/
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;
/*
+ * XXX we could group cachable expressions together here and cache the
+ * whole list as one boolean expression
+ */
+
+ /*
* Since the parser considers OR to be a binary operator, long OR lists
* become deeply nested expressions. We must flatten these into long
* argument lists of a single OR operator. To avoid blowing out the stack
@@ -3140,6 +3222,7 @@ simplify_or_arguments(List *args,
while (unprocessed_args)
{
Node *arg = (Node *) linitial(unprocessed_args);
+ bool isCachable = true; /* XXX return to caller */
unprocessed_args = list_delete_first(unprocessed_args);
@@ -3162,7 +3245,7 @@ simplify_or_arguments(List *args,
}
/* If it's not an OR, simplify it */
- arg = eval_const_expressions_mutator(arg, context);
+ arg = eval_const_expressions_mutator(arg, context, &isCachable);
/*
* It is unlikely but not impossible for simplification of a non-OR
@@ -3203,11 +3286,42 @@ simplify_or_arguments(List *args,
continue;
}
- /* else emit the simplified arg into the result list */
- newargs = lappend(newargs, arg);
+ /* else emit the simplified arg into the result list XXX update comment */
+ 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); /* XXX leaks list header */
+ else
+ arg = makeBoolExpr(OR_EXPR, cachable_args, -1);
+
+ /*
+ * Assume that the cachable expression is cheaper to evaluate, so put
+ * it first
+ */
+ nocache_args = lcons(insert_cache(arg), nocache_args);
+
+ *cachable = false;
+ return nocache_args;
+ }
+ else if (nocache_args)
+ {
+ *cachable = false;
+ return nocache_args;
+ }
+ else
+ {
+ *cachable = true;
+ return cachable_args;
+ }
}
/*
@@ -3228,13 +3342,16 @@ 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.
+ *
+ * XXX update comment
*/
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 +3359,7 @@ simplify_and_arguments(List *args,
while (unprocessed_args)
{
Node *arg = (Node *) linitial(unprocessed_args);
+ bool isCachable = true; /* XXX return to caller */
unprocessed_args = list_delete_first(unprocessed_args);
@@ -3264,7 +3382,7 @@ simplify_and_arguments(List *args,
}
/* If it's not an AND, simplify it */
- arg = eval_const_expressions_mutator(arg, context);
+ arg = eval_const_expressions_mutator(arg, context, &isCachable);
/*
* It is unlikely but not impossible for simplification of a non-AND
@@ -3306,10 +3424,41 @@ 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); /* XXX leaks list header */
+ else
+ arg = makeBoolExpr(AND_EXPR, cachable_args, -1);
+
+ /*
+ * Assume that the cachable expression is cheaper to evaluate, so put
+ * it first
+ */
+ nocache_args = lcons(insert_cache(arg), nocache_args);
+
+ *cachable = false;
+ return nocache_args;
+ }
+ else if (nocache_args)
+ {
+ *cachable = false;
+ return nocache_args;
+ }
+ else
+ {
+ *cachable = true;
+ return cachable_args;
+ }
}
/*
@@ -3384,7 +3533,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 +3552,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 +3576,37 @@ 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 = eval_const_expressions_mutator(arg, context, &isCachable);
+ lfirst(lc) = arg;
+
+ if (isCachable && context->cache)
+ cachable_args = lappend(cachable_args, arg);
+ 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 +3643,112 @@ 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, cache all cachable arguments
+ */
+ if (!newexpr && !(*cachable) && cachable_args != NIL)
+ {
+ foreach(lc, args)
+ {
+ Node *arg = (Node *) lfirst(lc);
+
+ /* XXX ugly as hell and N^2 to the number of arguments */
+ if (list_member(cachable_args, arg))
+ lfirst(lc) = insert_cache((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 +3782,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 +3790,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 +3803,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 +3813,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 +3832,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 +3921,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 +3934,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
@@ -3748,9 +3949,17 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
* the case of a NULL function result there doesn't seem to be any clean
* way to fix that. In view of the likelihood of there being still other
* gotchas, seems best to leave the function call unreduced.
+ *
+ * XXX does the above apply to "cachable" too?
*/
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 +4056,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 +4336,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 = eval_const_expressions_mutator(newexpr, context, cachable);
context->active_fns = list_delete_first(context->active_fns);
error_context_stack = sqlerrcontext.previous;
@@ -4206,6 +4416,22 @@ sql_inline_error_callback(void *arg)
}
/*
+ * XXX
+ */
+static Expr *
+insert_cache(Expr *expr)
+{
+ /* Don't cache obviously cheap expressions */
+ if (IsA(expr, Const))
+ return expr;
+ if (IsA(expr, Param))
+ 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/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 65d03ad..75a7292 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -323,6 +323,7 @@ transformExpr(ParseState *pstate, Node *expr)
case T_CollateExpr:
case T_CaseTestExpr:
case T_ArrayExpr:
+ /* case T_CacheExpr: XXX ??? */
case T_CoerceToDomain:
case T_CoerceToDomainValue:
case T_SetToDefault:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c112a9c..6118e7f 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->subexpr, context, true);
+ appendStringInfoChar(buf, ']');
+#else
+ get_rule_expr((Node *) cache->subexpr, 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..fd6eb06 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 cached 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/primnodes.h b/src/include/nodes/primnodes.h
index f1e20ef..bd2c73e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1024,6 +1024,17 @@ typedef struct BooleanTest
} BooleanTest;
/*
+ * CacheExpr
+ *
+ * XXX
+ */
+typedef struct CacheExpr
+{
+ Expr xpr;
+ Expr *subexpr;
+} CacheExpr;
+
+/*
* CoerceToDomain
*
* CoerceToDomain represents the operation of coercing a value to a domain
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..b90cc4e 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5618,6 +5618,13 @@ exec_simple_check_node(Node *node)
case T_BooleanTest:
return exec_simple_check_node((Node *) ((BooleanTest *) node)->arg);
+ case T_CacheExpr:
+ /*
+ * XXX what do we do with simple expressions? Remove CacheExpr
+ * nodes after we discover that the expr is simple?
+ */
+ return FALSE;
+
case T_CoerceToDomain:
return exec_simple_check_node((Node *) ((CoerceToDomain *) node)->arg);
diff --git a/src/test/regress/expected/cache.out b/src/test/regress/expected/cache.out
new file mode 100644
index 0000000..1fa3bf6
--- /dev/null
+++ b/src/test/regress/expected/cache.out
@@ -0,0 +1,411 @@
+--
+-- 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;$$;
+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)
+
+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)
+
+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..0698b95
--- /dev/null
+++ b/src/test/regress/sql/cache.sql
@@ -0,0 +1,90 @@
+--
+-- 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;$$;
+
+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;
+
+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;
+
+drop table two;