const-stable-expressions.patch
text/x-patch
Filename: const-stable-expressions.patch
Type: text/x-patch
Part: 0
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 80f08d8..562e43d 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -40,6 +40,7 @@
#include "access/tupconvert.h"
#include "catalog/pg_type.h"
#include "commands/typecmds.h"
+#include "executor/executor.h"
#include "executor/execdebug.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
@@ -95,6 +96,14 @@ static void ExecPrepareTuplestoreResult(FuncExprState *fcache,
Tuplestorestate *resultStore,
TupleDesc resultDesc);
static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
+static void cache_function_result(FuncExprState *fcache,
+ ExprContext *econtext,
+ bool *isNull,
+ ExprDoneCond *isDone);
+static Datum ExecCachedFunctionResult(FuncExprState *fcache,
+ ExprContext *econtext,
+ bool *isNull,
+ ExprDoneCond *isDone);
static Datum ExecMakeFunctionResult(FuncExprState *fcache,
ExprContext *econtext,
bool *isNull,
@@ -1522,6 +1531,44 @@ tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
}
/*
+ * cache_function_result
+ */
+static void
+cache_function_result(FuncExprState *fcache,
+ ExprContext *econtext,
+ bool *isNull,
+ ExprDoneCond *isDone)
+{
+ MemoryContext oldcontext;
+
+ /* This cache has to persist for the whole query */
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+ fcache->cachedResult = ExecMakeFunctionResult(fcache, econtext, isNull, isDone);
+ fcache->cachedIsNull = *isNull;
+
+ /* Set-returning functions can't be cached */
+ Assert(!isDone || *isDone == ExprSingleResult);
+
+ MemoryContextSwitchTo(oldcontext);
+}
+
+/*
+ * ExecCachedFunctionResult
+ */
+static Datum
+ExecCachedFunctionResult(FuncExprState *fcache,
+ ExprContext *econtext,
+ bool *isNull,
+ ExprDoneCond *isDone)
+{
+ if(isDone)
+ *isDone = ExprSingleResult;
+ *isNull = fcache->cachedIsNull;
+ return fcache->cachedResult;
+}
+
+/*
* ExecMakeFunctionResult
*
* Evaluate the arguments to a function and then the function itself.
@@ -2257,10 +2304,18 @@ ExecEvalFunc(FuncExprState *fcache,
init_fcache(func->funcid, func->inputcollid, fcache,
econtext->ecxt_per_query_memory, true);
- /* Go directly to ExecMakeFunctionResult on subsequent uses */
- fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
+ if(func->stableconst)
+ {
+ cache_function_result(fcache, econtext, isNull, isDone);
+
+ /* XXX Since we only need function cache once, should we clean it up now? */
+ fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecCachedFunctionResult;
+ }
+ else
+ /* Go directly to ExecMakeFunctionResult on subsequent uses */
+ fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
- return ExecMakeFunctionResult(fcache, econtext, isNull, isDone);
+ return ExecEvalExpr((ExprState *) fcache, econtext, isNull, isDone);
}
/* ----------------------------------------------------------------
@@ -2280,10 +2335,18 @@ ExecEvalOper(FuncExprState *fcache,
init_fcache(op->opfuncid, op->inputcollid, fcache,
econtext->ecxt_per_query_memory, true);
- /* Go directly to ExecMakeFunctionResult on subsequent uses */
- fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
+ if(op->stableconst)
+ {
+ cache_function_result(fcache, econtext, isNull, isDone);
+
+ /* XXX Since we only need function cache once, should we clean it up now? */
+ fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecCachedFunctionResult;
+ }
+ else
+ /* Go directly to ExecMakeFunctionResult on subsequent uses */
+ fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResult;
- return ExecMakeFunctionResult(fcache, econtext, isNull, isDone);
+ return ExecEvalExpr((ExprState *) fcache, econtext, isNull, isDone);
}
/* ----------------------------------------------------------------
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 661a516..cb6a4e8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1183,6 +1183,7 @@ _copyFuncExpr(FuncExpr *from)
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
COPY_LOCATION_FIELD(location);
+ COPY_LOCATION_FIELD(stableconst);
return newnode;
}
@@ -1219,6 +1220,7 @@ _copyOpExpr(OpExpr *from)
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
COPY_LOCATION_FIELD(location);
+ COPY_LOCATION_FIELD(stableconst);
return newnode;
}
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 4d2eccf..4c4c909 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -465,6 +465,7 @@ makeFuncExpr(Oid funcid, Oid rettype, List *args,
funcexpr->inputcollid = inputcollid;
funcexpr->args = args;
funcexpr->location = -1;
+ funcexpr->stableconst = false;
return funcexpr;
}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index baa90fa..521bad2 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -120,9 +120,9 @@ static List *add_function_defaults(List *args, Oid result_type,
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,
+static Expr *evaluate_function(Expr *oldexpr, Oid funcid, Oid result_type,
+ int32 result_typmod, Oid result_collid, Oid input_collid,
+ List *args, HeapTuple func_tuple,
eval_const_expressions_context *context);
static Expr *inline_function(Oid funcid, Oid result_type, Oid result_collid,
Oid input_collid, List *args,
@@ -3413,7 +3413,7 @@ simplify_function(Expr *oldexpr, Oid funcid,
Oid transform;
/*
- * We have three strategies for simplification: execute the function to
+ * XXX We have three strategies for simplification: execute the function to
* deliver a constant result, use a transform function to generate a
* substitute node tree, or expand in-line the body of the function
* definition (which only works for simple SQL-language functions, but
@@ -3434,7 +3434,7 @@ simplify_function(Expr *oldexpr, Oid funcid,
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,
+ newexpr = evaluate_function(oldexpr, funcid, result_type, result_typmod,
result_collid, input_collid, *args,
func_tuple, context);
@@ -3712,7 +3712,7 @@ recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple)
/*
* evaluate_function: try to pre-evaluate a function call
*
- * We can do this if the function is strict and has any constant-null inputs
+ * XXX We can do this if the function is strict and has any constant-null inputs
* (just return a null constant), or if the function is immutable and has all
* constant inputs (call it and return the result as a Const node). In
* estimation mode we are willing to pre-evaluate stable functions too.
@@ -3720,15 +3720,17 @@ recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple)
* Returns a simplified expression if successful, or NULL if cannot
* simplify the function.
*/
+/* XXX rename this function */
static Expr *
-evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
- Oid result_collid, Oid input_collid, List *args,
- HeapTuple func_tuple,
+evaluate_function(Expr *oldexpr, Oid funcid, Oid result_type,
+ int32 result_typmod, Oid result_collid, Oid input_collid,
+ List *args, HeapTuple func_tuple,
eval_const_expressions_context *context)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
bool has_nonconst_input = false;
bool has_null_input = false;
+ bool has_non_stableconst_input = false;
ListCell *arg;
FuncExpr *newexpr;
@@ -3757,10 +3759,21 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
*/
foreach(arg, args)
{
- if (IsA(lfirst(arg), Const))
- has_null_input |= ((Const *) lfirst(arg))->constisnull;
+ Expr *expr = (Expr *) lfirst(arg);
+
+ if (IsA(expr, Const))
+ has_null_input |= ((Const *) expr)->constisnull;
else
+ {
has_nonconst_input = true;
+
+ if (IsA(expr, FuncExpr))
+ has_non_stableconst_input |= !((FuncExpr *) expr)->stableconst;
+ else if (IsA(expr, OpExpr))
+ has_non_stableconst_input |= !((OpExpr *) expr)->stableconst;
+ else
+ has_non_stableconst_input = true;
+ }
}
/*
@@ -3778,37 +3791,69 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
* non-strict function, constant NULL inputs are treated the same as
* constant non-NULL inputs.)
*/
- if (has_nonconst_input)
+ if (has_non_stableconst_input)
return NULL;
/*
- * Ordinarily we are only allowed to simplify immutable functions. But for
+ * XXX Ordinarily we are only allowed to simplify immutable functions. But for
* purposes of estimation, we consider it okay to simplify functions that
* are merely stable; the risk that the result might change from planning
* time to execution time is worth taking in preference to not being able
* to estimate the value at all.
*/
- if (funcform->provolatile == PROVOLATILE_IMMUTABLE)
- /* okay */ ;
- else if (context->estimate && funcform->provolatile == PROVOLATILE_STABLE)
- /* okay */ ;
- else
+ if (funcform->provolatile == PROVOLATILE_VOLATILE)
return NULL;
+ /* Arguments are not plan-time constants, but stable execution-time
+ * constants -- or the function itself is stable
+ */
+ if((!context->estimate) && (has_nonconst_input ||
+ funcform->provolatile == PROVOLATILE_STABLE))
+ {
+ Expr *expr;
+
+ /*
+ * We want to keep the original expression type, because EXPLAIN will
+ * show these expressions
+ */
+ if(oldexpr)
+ expr = copyObject(oldexpr);
+ else
+ /*
+ * XXX This can make EXPLAIN ANALYZE VERBOSE ugly!
+ * explain analyze verbose select current_date;
+ */
+ expr = makeFuncExpr(funcid, result_type, args, result_collid,
+ input_collid, COERCE_DONTCARE);
+
+ /*
+ * XXX If we don't mutate 'args' here, we prevent redundant caching
+ * of argument expressions. Does this have any downsides?
+ */
+ if (IsA(expr, FuncExpr))
+ {
+ ((FuncExpr *) expr)->stableconst = true;
+ ((FuncExpr *) expr)->args = args;
+ }
+ else if(IsA(expr, OpExpr))
+ {
+ ((OpExpr *) expr)->stableconst = true;
+ ((OpExpr *) expr)->args = args;
+ }
+ else
+ Assert(false); /* XXX */
+
+ return (Expr *) expr;
+ }
+
/*
* OK, looks like we can simplify this operator/function.
*
* Build a new FuncExpr node containing the already-simplified arguments.
*/
- newexpr = makeNode(FuncExpr);
- newexpr->funcid = funcid;
- newexpr->funcresulttype = result_type;
- newexpr->funcretset = false;
- newexpr->funcformat = COERCE_DONTCARE; /* doesn't matter */
- newexpr->funccollid = result_collid; /* doesn't matter */
- newexpr->inputcollid = input_collid;
- newexpr->args = args;
- newexpr->location = -1;
+
+ newexpr = makeFuncExpr(funcid, result_type, args, result_collid,
+ input_collid, COERCE_DONTCARE);
return evaluate_expr((Expr *) newexpr, result_type, result_typmod,
result_collid);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b3eed7d..8238464 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -624,6 +624,13 @@ typedef struct FuncExprState
FmgrInfo func;
/*
+ * These are used for holding the cached result of stable function calls
+ * with constant arguments.
+ */
+ Datum cachedResult;
+ bool cachedIsNull;
+
+ /*
* For a set-returning function (SRF) that returns a tuplestore, we keep
* the tuplestore here and dole out the result rows one at a time. The
* slot holds the row currently being returned.
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f1e20ef..af0c6a7 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -335,6 +335,7 @@ typedef struct FuncExpr
Oid inputcollid; /* OID of collation that function should use */
List *args; /* arguments to the function */
int location; /* token location, or -1 if unknown */
+ bool stableconst; /* cache the result for each ExprState? */
} FuncExpr;
/*
@@ -380,6 +381,7 @@ typedef struct OpExpr
Oid inputcollid; /* OID of collation that operator should use */
List *args; /* arguments to the operator (1 or 2) */
int location; /* token location, or -1 if unknown */
+ bool stableconst; /* cache the result for each ExprState? */
} OpExpr;
/*