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

Reply via email to