Hello everyone again!
This is the continuation of my previous patch on the same topic; here
there are changes made thanks to Tom Lane comments (see thread here
[1]). To not send big patch I have split it (that's why version starts
with the first again) and here I send infrastructure patch which
includes:
- creation of CachedExpr node
- usual node functions for it
- mutator to replace nonovolatile functions' and operators' expressions
by appropriate cached expressions.
Any suggestions are welcome!
[1]
https://www.postgresql.org/message-id/flat/98c77534fa51aa4bf84a5b39931c42ea%40postgrespro.ru#98c77534fa51aa4bf84a5b39931c4...@postgrespro.ru
--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
From d7871c9aaf64210130b591a93c13b18c74ebb2b4 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyak...@postgrespro.ru>
Date: Wed, 3 May 2017 18:09:16 +0300
Subject: [PATCH] Precalculate stable functions, infrastructure v1
Now in Postgresql only immutable functions are precalculated; stable functions
are calculated for every row so in fact they don't differ from volatile
functions.
This patch includes:
- creation of CachedExpr node
- usual node functions for it
- mutator to replace nonovolatile functions' and operators' expressions by
appropriate cached expressions.
---
src/backend/nodes/copyfuncs.c | 22 +++++++
src/backend/nodes/equalfuncs.c | 22 +++++++
src/backend/nodes/nodeFuncs.c | 121 ++++++++++++++++++++++++++++++++++
src/backend/nodes/outfuncs.c | 32 +++++++++
src/backend/nodes/readfuncs.c | 33 ++++++++++
src/backend/optimizer/plan/planner.c | 124 +++++++++++++++++++++++++++++++++++
src/include/nodes/nodeFuncs.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/primnodes.h | 32 +++++++++
9 files changed, 389 insertions(+)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 35a237a..1a16e3a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1529,6 +1529,25 @@ _copyNullIfExpr(const NullIfExpr *from)
return newnode;
}
+static CachedExpr *
+_copyCachedExpr(const CachedExpr *from)
+{
+ CachedExpr *newnode = makeNode(CachedExpr);
+
+ COPY_SCALAR_FIELD(subexprtype);
+ switch(from->subexprtype)
+ {
+ case CACHED_FUNCEXPR:
+ COPY_NODE_FIELD(subexpr.funcexpr);
+ break;
+ case CACHED_OPEXPR:
+ COPY_NODE_FIELD(subexpr.opexpr);
+ break;
+ }
+
+ return newnode;
+}
+
/*
* _copyScalarArrayOpExpr
*/
@@ -4869,6 +4888,9 @@ copyObjectImpl(const void *from)
case T_NullIfExpr:
retval = _copyNullIfExpr(from);
break;
+ case T_CachedExpr:
+ retval = _copyCachedExpr(from);
+ break;
case T_ScalarArrayOpExpr:
retval = _copyScalarArrayOpExpr(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 21dfbb0..5a0929a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -384,6 +384,25 @@ _equalNullIfExpr(const NullIfExpr *a, const NullIfExpr *b)
}
static bool
+_equalCachedExpr(const CachedExpr *a, const CachedExpr *b)
+{
+ COMPARE_SCALAR_FIELD(subexprtype);
+
+ /* the same subexprtype for b because we have already compared it */
+ switch(a->subexprtype)
+ {
+ case CACHED_FUNCEXPR:
+ COMPARE_NODE_FIELD(subexpr.funcexpr);
+ break;
+ case CACHED_OPEXPR:
+ COMPARE_NODE_FIELD(subexpr.opexpr);
+ break;
+ }
+
+ return true;
+ }
+
+static bool
_equalScalarArrayOpExpr(const ScalarArrayOpExpr *a, const ScalarArrayOpExpr *b)
{
COMPARE_SCALAR_FIELD(opno);
@@ -3031,6 +3050,9 @@ equal(const void *a, const void *b)
case T_NullIfExpr:
retval = _equalNullIfExpr(a, b);
break;
+ case T_CachedExpr:
+ retval = _equalCachedExpr(a, b);
+ break;
case T_ScalarArrayOpExpr:
retval = _equalScalarArrayOpExpr(a, b);
break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3e8189c..9621511 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -32,6 +32,7 @@ static bool planstate_walk_subplans(List *plans, bool (*walker) (),
void *context);
static bool planstate_walk_members(List *plans, PlanState **planstates,
bool (*walker) (), void *context);
+static const Node *get_const_subexpr(const CachedExpr *cachedexpr);
/*
@@ -92,6 +93,9 @@ exprType(const Node *expr)
case T_NullIfExpr:
type = ((const NullIfExpr *) expr)->opresulttype;
break;
+ case T_CachedExpr:
+ type = exprType(get_const_subexpr((const CachedExpr *) expr));
+ break;
case T_ScalarArrayOpExpr:
type = BOOLOID;
break;
@@ -311,6 +315,8 @@ exprTypmod(const Node *expr)
return exprTypmod((Node *) linitial(nexpr->args));
}
break;
+ case T_CachedExpr:
+ return exprTypmod(get_const_subexpr((const CachedExpr *) expr));
case T_SubLink:
{
const SubLink *sublink = (const SubLink *) expr;
@@ -573,6 +579,10 @@ exprIsLengthCoercion(const Node *expr, int32 *coercedTypmod)
return true;
}
+ if (expr && IsA(expr, CachedExpr))
+ return exprIsLengthCoercion(
+ get_const_subexpr((const CachedExpr *) expr), coercedTypmod);
+
return false;
}
@@ -655,6 +665,10 @@ strip_implicit_coercions(Node *node)
if (c->coercionformat == COERCE_IMPLICIT_CAST)
return strip_implicit_coercions((Node *) c->arg);
}
+ else if (IsA(node, CachedExpr))
+ {
+ return strip_implicit_coercions(get_subexpr((CachedExpr *) node));
+ }
return node;
}
@@ -727,6 +741,8 @@ expression_returns_set_walker(Node *node, void *context)
return false;
if (IsA(node, XmlExpr))
return false;
+ if (IsA(node, CachedExpr))
+ return false;
return expression_tree_walker(node, expression_returns_set_walker,
context);
@@ -790,6 +806,9 @@ exprCollation(const Node *expr)
case T_NullIfExpr:
coll = ((const NullIfExpr *) expr)->opcollid;
break;
+ case T_CachedExpr:
+ coll = exprCollation(get_const_subexpr((const CachedExpr *) expr));
+ break;
case T_ScalarArrayOpExpr:
coll = InvalidOid; /* result is always boolean */
break;
@@ -973,6 +992,10 @@ exprInputCollation(const Node *expr)
case T_NullIfExpr:
coll = ((const NullIfExpr *) expr)->inputcollid;
break;
+ case T_CachedExpr:
+ coll = exprInputCollation(
+ get_const_subexpr((const CachedExpr *) expr));
+ break;
case T_ScalarArrayOpExpr:
coll = ((const ScalarArrayOpExpr *) expr)->inputcollid;
break;
@@ -1034,6 +1057,9 @@ exprSetCollation(Node *expr, Oid collation)
case T_NullIfExpr:
((NullIfExpr *) expr)->opcollid = collation;
break;
+ case T_CachedExpr:
+ exprSetCollation(get_subexpr((CachedExpr *) expr), collation);
+ break;
case T_ScalarArrayOpExpr:
Assert(!OidIsValid(collation)); /* result is always boolean */
break;
@@ -1168,6 +1194,10 @@ exprSetInputCollation(Node *expr, Oid inputcollation)
case T_NullIfExpr:
((NullIfExpr *) expr)->inputcollid = inputcollation;
break;
+ case T_CachedExpr:
+ exprSetInputCollation(get_subexpr((CachedExpr *) expr),
+ inputcollation);
+ break;
case T_ScalarArrayOpExpr:
((ScalarArrayOpExpr *) expr)->inputcollid = inputcollation;
break;
@@ -1277,6 +1307,9 @@ exprLocation(const Node *expr)
exprLocation((Node *) opexpr->args));
}
break;
+ case T_CachedExpr:
+ loc = exprLocation(get_const_subexpr((const CachedExpr *) expr));
+ break;
case T_ScalarArrayOpExpr:
{
const ScalarArrayOpExpr *saopexpr = (const ScalarArrayOpExpr *) expr;
@@ -1611,6 +1644,8 @@ fix_opfuncids_walker(Node *node, void *context)
{
if (node == NULL)
return false;
+ if (IsA(node, CachedExpr))
+ return fix_opfuncids_walker(get_subexpr((CachedExpr *) node), context);
if (IsA(node, OpExpr))
set_opfuncid((OpExpr *) node);
else if (IsA(node, DistinctExpr))
@@ -1710,6 +1745,9 @@ check_functions_in_node(Node *node, check_function_callback checker,
return true;
}
break;
+ case T_CachedExpr:
+ return check_functions_in_node(get_subexpr((CachedExpr *) node),
+ checker, context);
case T_ScalarArrayOpExpr:
{
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
@@ -1980,6 +2018,17 @@ expression_tree_walker(Node *node,
return true;
}
break;
+ case T_CachedExpr:
+ {
+ /*
+ * cachedexpr is processed by my_walker, so its subexpr is
+ * processed too and we need to process sub-nodes of subexpr.
+ */
+ if (expression_tree_walker(get_subexpr((CachedExpr *) node),
+ walker, context))
+ return true;
+ }
+ break;
case T_ScalarArrayOpExpr:
{
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
@@ -2617,6 +2666,36 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
+ case T_CachedExpr:
+ {
+ CachedExpr *expr = (CachedExpr *) node;
+ CachedExpr *newnode;
+
+ FLATCOPY(newnode, expr, CachedExpr);
+
+ /*
+ * expr is already mutated, so its subexpr is already mutated
+ * too and we need to mutate sub-nodes of subexpr.
+ */
+ switch(newnode->subexprtype)
+ {
+ case CACHED_FUNCEXPR:
+ newnode->subexpr.funcexpr = (FuncExpr *)
+ expression_tree_mutator(
+ (Node *) expr->subexpr.funcexpr,
+ mutator, context);
+ break;
+ case CACHED_OPEXPR:
+ newnode->subexpr.opexpr = (OpExpr *)
+ expression_tree_mutator(
+ (Node *) expr->subexpr.opexpr,
+ mutator, context);
+ break;
+ }
+
+ return (Node *) newnode;
+ }
+ break;
case T_ScalarArrayOpExpr:
{
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
@@ -3838,3 +3917,45 @@ planstate_walk_members(List *plans, PlanState **planstates,
return false;
}
+
+/*
+ * get_const_subexpr
+ * Get const subexpression of given const cached expression.
+ */
+static const Node *
+get_const_subexpr(const CachedExpr *cachedexpr)
+{
+ if (cachedexpr == NULL)
+ return NULL;
+
+ switch (cachedexpr->subexprtype)
+ {
+ case CACHED_FUNCEXPR:
+ return (const Node *) cachedexpr->subexpr.funcexpr;
+ case CACHED_OPEXPR:
+ return (const Node *) cachedexpr->subexpr.opexpr;
+ }
+
+ return NULL;
+}
+
+/*
+ * get_subexpr
+ * Get subexpression of given cached expression.
+ */
+Node *
+get_subexpr(CachedExpr *cachedexpr)
+{
+ if (cachedexpr == NULL)
+ return NULL;
+
+ switch (cachedexpr->subexprtype)
+ {
+ case CACHED_FUNCEXPR:
+ return (Node *) cachedexpr->subexpr.funcexpr;
+ case CACHED_OPEXPR:
+ return (Node *) cachedexpr->subexpr.opexpr;
+ }
+
+ return NULL;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 98f6768..94bcf11 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1239,6 +1239,35 @@ _outNullIfExpr(StringInfo str, const NullIfExpr *node)
}
static void
+_outCachedExpr(StringInfo str, const CachedExpr *node)
+{
+ WRITE_NODE_TYPE("CACHEDEXPR");
+
+ /* do-it-yourself enum representation; out subexprtype begin... */
+ appendStringInfoString(str, " :subexprtype ");
+
+ switch(node->subexprtype)
+ {
+ case CACHED_FUNCEXPR:
+ {
+ /* ... out subexprtype end */
+ outToken(str, "cached_funcexpr");
+
+ WRITE_NODE_FIELD(subexpr.funcexpr);
+ }
+ break;
+ case CACHED_OPEXPR:
+ {
+ /* ... out subexprtype end */
+ outToken(str, "cached_opexpr");
+
+ WRITE_NODE_FIELD(subexpr.opexpr);
+ }
+ break;
+ }
+}
+
+static void
_outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
{
WRITE_NODE_TYPE("SCALARARRAYOPEXPR");
@@ -3769,6 +3798,9 @@ outNode(StringInfo str, const void *obj)
case T_NullIfExpr:
_outNullIfExpr(str, obj);
break;
+ case T_CachedExpr:
+ _outCachedExpr(str, obj);
+ break;
case T_ScalarArrayOpExpr:
_outScalarArrayOpExpr(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index f9a227e..836c20a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -750,6 +750,37 @@ _readNullIfExpr(void)
}
/*
+ * _readCachedExpr
+ */
+static CachedExpr *
+_readCachedExpr(void)
+{
+ READ_LOCALS(CachedExpr);
+
+ /* do-it-yourself enum representation */
+ token = pg_strtok(&length); /* skip :subexprtype */
+ token = pg_strtok(&length); /* get field value */
+ if (strncmp(token, "cached_funcexpr", 15) == 0)
+ local_node->subexprtype = CACHED_FUNCEXPR;
+ else if (strncmp(token, "cached_opexpr", 13) == 0)
+ local_node->subexprtype = CACHED_OPEXPR;
+ else
+ elog(ERROR, "unrecognized subexprtype \"%.*s\"", length, token);
+
+ switch (local_node->subexprtype)
+ {
+ case CACHED_FUNCEXPR:
+ READ_NODE_FIELD(subexpr.funcexpr);
+ break;
+ case CACHED_OPEXPR:
+ READ_NODE_FIELD(subexpr.opexpr);
+ break;
+ }
+
+ READ_DONE();
+}
+
+/*
* _readScalarArrayOpExpr
*/
static ScalarArrayOpExpr *
@@ -2464,6 +2495,8 @@ parseNodeString(void)
return_value = _readDistinctExpr();
else if (MATCH("NULLIFEXPR", 10))
return_value = _readNullIfExpr();
+ else if (MATCH("CACHEDEXPR", 10))
+ return_value = _readCachedExpr();
else if (MATCH("SCALARARRAYOPEXPR", 17))
return_value = _readScalarArrayOpExpr();
else if (MATCH("BOOLEXPR", 8))
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index c4a5651..4dd8cbb 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -184,6 +184,7 @@ static PathTarget *make_sort_input_target(PlannerInfo *root,
bool *have_postponed_srfs);
static void adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
List *targets, List *targets_contain_srfs);
+static Node *replace_cached_expressions_mutator(Node *node);
/*****************************************************************************
@@ -6086,3 +6087,126 @@ get_partitioned_child_rels(PlannerInfo *root, Index rti)
return result;
}
+
+static Node *
+replace_cached_expressions_mutator(Node *node)
+{
+ if (node == NULL)
+ return NULL;
+
+ /* mutate certain types of nodes */
+ if (IsA(node, RestrictInfo))
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) node;
+
+ /*
+ * For an OR clause, recurse into the marked-up tree so that we replace
+ * cached expressions for contained RestrictInfos too.
+ */
+ if (rinfo->orclause)
+ rinfo->orclause = (Expr *) replace_cached_expressions_mutator(
+ (Node *) rinfo->orclause);
+ else
+ rinfo->clause = (Expr *) replace_cached_expressions_mutator(
+ (Node *) rinfo->clause);
+
+ /* do NOT recurse into children */
+ return node;
+ }
+ else if (IsA(node, FuncExpr))
+ {
+ /*
+ * Function is cached if:
+ * 1) it doesn't return set,
+ * 2) it's not volatile itself,
+ * 3) its arguments are constants or cached expressions too.
+ */
+ FuncExpr *funcexpr;
+ ListCell *arg;
+ bool has_nonconst_or_noncached_input = false;
+ bool func_returns_set;
+
+ /* firstly recurse into children */
+ funcexpr = (FuncExpr *) expression_tree_mutator(node,
+ replace_cached_expressions_mutator,
+ NULL);
+ func_returns_set = funcexpr->funcretset ||
+ expression_returns_set((Node *) funcexpr->args);
+
+ foreach(arg, funcexpr->args)
+ {
+ void *arg_lfirst = lfirst(arg);
+ if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
+ has_nonconst_or_noncached_input = true;
+ }
+
+ if (func_returns_set ||
+ has_nonconst_or_noncached_input ||
+ contain_volatile_functions((Node *) &funcexpr->xpr))
+ {
+ /* return FuncExpr, which will not be cached */
+ return (Node *) funcexpr;
+ }
+ else
+ {
+ /* create and return CachedExpr */
+ CachedExpr *new_node = makeNode(CachedExpr);
+ new_node->subexprtype = CACHED_FUNCEXPR;
+ new_node->subexpr.funcexpr = funcexpr;
+
+ return (Node *) new_node;
+ }
+ }
+ else if (IsA(node, OpExpr))
+ {
+ /*
+ * Operator is cached if:
+ * 1) its function doesn't return set,
+ * 1) its function is not volatile itself,
+ * 3) its arguments are constants or cached expressions too.
+ */
+ OpExpr *opexpr = (OpExpr *) node;
+ ListCell *arg;
+ bool has_nonconst_or_noncached_input = false;
+ bool op_returns_set;
+
+ /* rely on struct equivalence to treat these all alike */
+ set_opfuncid(opexpr);
+
+ /* firstly recurse into children */
+ opexpr = (OpExpr *) expression_tree_mutator(node,
+ replace_cached_expressions_mutator,
+ NULL);
+ op_returns_set = opexpr->opretset ||
+ expression_returns_set((Node *) opexpr->args);
+
+ foreach(arg, opexpr->args)
+ {
+ void *arg_lfirst = lfirst(arg);
+ if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
+ has_nonconst_or_noncached_input = true;
+ }
+
+ if (op_returns_set ||
+ has_nonconst_or_noncached_input ||
+ contain_volatile_functions((Node *) &opexpr->xpr))
+ {
+ /* return OpExpr, which will not be cached */
+ return (Node *) opexpr;
+ }
+ else
+ {
+ /* create and return CachedExpr */
+ CachedExpr *new_node = makeNode(CachedExpr);
+ new_node->subexprtype = CACHED_OPEXPR;
+ new_node->subexpr.opexpr = opexpr;
+
+ return (Node *) new_node;
+ }
+ }
+
+ /* otherwise recurse into children */
+ return expression_tree_mutator(node,
+ replace_cached_expressions_mutator,
+ NULL);
+}
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index b6c9b48..2ec4700 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -77,4 +77,6 @@ struct PlanState;
extern bool planstate_tree_walker(struct PlanState *planstate, bool (*walker) (),
void *context);
+extern Node * get_subexpr(CachedExpr *cachedexpr);
+
#endif /* NODEFUNCS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f59d719..054bc61 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -155,6 +155,7 @@ typedef enum NodeTag
T_OpExpr,
T_DistinctExpr,
T_NullIfExpr,
+ T_CachedExpr,
T_ScalarArrayOpExpr,
T_BoolExpr,
T_SubLink,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 86ec82e..7d4cf61 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -523,6 +523,38 @@ typedef OpExpr DistinctExpr;
typedef OpExpr NullIfExpr;
/*
+ * Discriminator for CachedExpr.
+ *
+ * Identifies the subexpression to be cached in execution (= executed only once
+ * and then used cached value) and which member in the CachedExpr->subexpr union
+ * is valid.
+ */
+typedef enum CachedSubExprType
+{
+ CACHED_FUNCEXPR, /* cached FuncExpr */
+ CACHED_OPEXPR /* cached OpExpr */
+} CachedSubExprType;
+
+/*
+ * CachedExpr - expression node for precalculated stable and immutable functions
+ * (= they are calculated once for all output rows, but as many times as
+ * function is mentioned in query), if they don't return a set and their
+ * arguments are constants or recursively precalculated functions. The same for
+ * operators' functions.
+ */
+typedef struct CachedExpr
+{
+ Expr xpr;
+ CachedSubExprType subexprtype; /* expression to be cached */
+
+ union SubExpr
+ {
+ FuncExpr *funcexpr; /* for CACHED_FUNCEXPR */
+ OpExpr *opexpr; /* for CACHED_OPEXPR */
+ } subexpr;
+} CachedExpr;
+
+/*
* ScalarArrayOpExpr - expression node for "scalar op ANY/ALL (array)"
*
* The operator must yield boolean. It is applied to the left operand
--
1.9.1
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers