From 31b2415d75d263be44a7e3e5e0b67adbbb783312 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Fri, 14 Jul 2017 18:23:17 +0300
Subject: [PATCH v5 1/3] Precalculate stable functions, infrastructure

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 expressions by appropriate cached expressions.
---
 src/backend/executor/execExpr.c      |    7 +-
 src/backend/nodes/copyfuncs.c        |   16 +
 src/backend/nodes/equalfuncs.c       |   11 +
 src/backend/nodes/nodeFuncs.c        |   76 +++
 src/backend/nodes/outfuncs.c         |   11 +
 src/backend/nodes/readfuncs.c        |   15 +
 src/backend/optimizer/plan/planner.c | 1114 ++++++++++++++++++++++++++++++++++
 src/backend/utils/adt/domains.c      |    5 +-
 src/backend/utils/cache/typcache.c   |   56 +-
 src/include/nodes/execnodes.h        |   19 +-
 src/include/nodes/nodes.h            |    3 +
 src/include/nodes/primnodes.h        |   96 ++-
 src/include/utils/typcache.h         |    2 +
 13 files changed, 1375 insertions(+), 56 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index a298b92..3523543 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1964,6 +1964,7 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
 				break;
 			}
 
+		/* note that DomainConstraintExpr nodes are handled within this block */
 		case T_CoerceToDomain:
 			{
 				CoerceToDomain *ctest = (CoerceToDomain *) node;
@@ -2547,6 +2548,7 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 	bool	   *domainnull = NULL;
 	Datum	   *save_innermost_domainval;
 	bool	   *save_innermost_domainnull;
+	List	   *constraints;
 	ListCell   *l;
 
 	scratch->d.domaincheck.resulttype = ctest->resulttype;
@@ -2584,15 +2586,16 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 							constraint_ref,
 							CurrentMemoryContext,
 							false);
+	constraints = GetDomainConstraintExprList(constraint_ref);
 
 	/*
 	 * Compile code to check each domain constraint.  NOTNULL constraints can
 	 * just be applied on the resv/resnull value, but for CHECK constraints we
 	 * need more pushups.
 	 */
-	foreach(l, constraint_ref->constraints)
+	foreach(l, constraints)
 	{
-		DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
+		DomainConstraintExpr *con = (DomainConstraintExpr *) lfirst(l);
 
 		scratch->d.domaincheck.constraintname = con->name;
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 67ac814..85f57bf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1411,6 +1411,19 @@ _copyWindowFunc(const WindowFunc *from)
 }
 
 /*
+ * _copyCachedExpr
+ */
+static CachedExpr *
+_copyCachedExpr(const CachedExpr *from)
+{
+	CachedExpr *newnode = makeNode(CachedExpr);
+
+	COPY_NODE_FIELD(subexpr);
+
+	return newnode;
+}
+
+/*
  * _copyArrayRef
  */
 static ArrayRef *
@@ -4850,6 +4863,9 @@ copyObjectImpl(const void *from)
 		case T_WindowFunc:
 			retval = _copyWindowFunc(from);
 			break;
+		case T_CachedExpr:
+			retval = _copyCachedExpr(from);
+			break;
 		case T_ArrayRef:
 			retval = _copyArrayRef(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 91d64b7..1ea9af7 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -264,6 +264,14 @@ _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
 }
 
 static bool
+_equalCachedExpr(const CachedExpr *a, const CachedExpr *b)
+{
+	COMPARE_NODE_FIELD(subexpr);
+
+	return true;
+}
+
+static bool
 _equalArrayRef(const ArrayRef *a, const ArrayRef *b)
 {
 	COMPARE_SCALAR_FIELD(refarraytype);
@@ -3013,6 +3021,9 @@ equal(const void *a, const void *b)
 		case T_WindowFunc:
 			retval = _equalWindowFunc(a, b);
 			break;
+		case T_CachedExpr:
+			retval = _equalCachedExpr(a, b);
+			break;
 		case T_ArrayRef:
 			retval = _equalArrayRef(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 97ba25f..68d480e 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -66,6 +66,10 @@ exprType(const Node *expr)
 		case T_WindowFunc:
 			type = ((const WindowFunc *) expr)->wintype;
 			break;
+		case T_CachedExpr:
+			type =
+				exprType((const Node *) ((const CachedExpr *) expr)->subexpr);
+			break;
 		case T_ArrayRef:
 			{
 				const ArrayRef *arrayref = (const ArrayRef *) expr;
@@ -286,6 +290,9 @@ exprTypmod(const Node *expr)
 			return ((const Const *) expr)->consttypmod;
 		case T_Param:
 			return ((const Param *) expr)->paramtypmod;
+		case T_CachedExpr:
+			return
+				exprTypmod((const Node *) ((const CachedExpr *) expr)->subexpr);
 		case T_ArrayRef:
 			/* typmod is the same for array or element */
 			return ((const ArrayRef *) expr)->reftypmod;
@@ -573,6 +580,11 @@ exprIsLengthCoercion(const Node *expr, int32 *coercedTypmod)
 		return true;
 	}
 
+	if (expr && IsA(expr, CachedExpr))
+		return exprIsLengthCoercion(
+			(const Node *) ((const CachedExpr *) expr)->subexpr,
+			coercedTypmod);
+
 	return false;
 }
 
@@ -655,6 +667,11 @@ 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(
+			(Node *) ((CachedExpr *) node)->subexpr);
+	}
 	return node;
 }
 
@@ -699,6 +716,8 @@ expression_returns_set_walker(Node *node, void *context)
 		return false;
 	if (IsA(node, WindowFunc))
 		return false;
+	if (IsA(node, CachedExpr))
+		return false;
 
 	return expression_tree_walker(node, expression_returns_set_walker,
 								  context);
@@ -744,6 +763,10 @@ exprCollation(const Node *expr)
 		case T_WindowFunc:
 			coll = ((const WindowFunc *) expr)->wincollid;
 			break;
+		case T_CachedExpr:
+			coll = exprCollation(
+				(const Node *) ((const CachedExpr *) expr)->subexpr);
+			break;
 		case T_ArrayRef:
 			coll = ((const ArrayRef *) expr)->refcollid;
 			break;
@@ -933,6 +956,10 @@ exprInputCollation(const Node *expr)
 		case T_WindowFunc:
 			coll = ((const WindowFunc *) expr)->inputcollid;
 			break;
+		case T_CachedExpr:
+			coll = exprInputCollation(
+				(const Node *) ((const CachedExpr *) expr)->subexpr);
+			break;
 		case T_FuncExpr:
 			coll = ((const FuncExpr *) expr)->inputcollid;
 			break;
@@ -988,6 +1015,10 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_WindowFunc:
 			((WindowFunc *) expr)->wincollid = collation;
 			break;
+		case T_CachedExpr:
+			exprSetCollation((Node *) ((CachedExpr *) expr)->subexpr,
+							 collation);
+			break;
 		case T_ArrayRef:
 			((ArrayRef *) expr)->refcollid = collation;
 			break;
@@ -1129,6 +1160,10 @@ exprSetInputCollation(Node *expr, Oid inputcollation)
 		case T_WindowFunc:
 			((WindowFunc *) expr)->inputcollid = inputcollation;
 			break;
+		case T_CachedExpr:
+			exprSetInputCollation((Node *) ((CachedExpr *) expr)->subexpr,
+								  inputcollation);
+			break;
 		case T_FuncExpr:
 			((FuncExpr *) expr)->inputcollid = inputcollation;
 			break;
@@ -1217,6 +1252,10 @@ exprLocation(const Node *expr)
 			/* function name should always be the first thing */
 			loc = ((const WindowFunc *) expr)->location;
 			break;
+		case T_CachedExpr:
+			loc = exprLocation(
+				(const Node *) ((const CachedExpr *) expr)->subexpr);
+			break;
 		case T_ArrayRef:
 			/* just use array argument's location */
 			loc = exprLocation((Node *) ((const ArrayRef *) expr)->refexpr);
@@ -1590,6 +1629,9 @@ fix_opfuncids_walker(Node *node, void *context)
 {
 	if (node == NULL)
 		return false;
+	if (IsA(node, CachedExpr))
+		return fix_opfuncids_walker((Node *) ((CachedExpr *) node)->subexpr,
+									context);
 	if (IsA(node, OpExpr))
 		set_opfuncid((OpExpr *) node);
 	else if (IsA(node, DistinctExpr))
@@ -1669,6 +1711,9 @@ check_functions_in_node(Node *node, check_function_callback checker,
 					return true;
 			}
 			break;
+		case T_CachedExpr:
+			return check_functions_in_node(
+				(Node *) ((CachedExpr *) node)->subexpr, checker, context);
 		case T_FuncExpr:
 			{
 				FuncExpr   *expr = (FuncExpr *) node;
@@ -1919,6 +1964,18 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_CachedExpr:
+			{
+				/*
+				 * cachedexpr is processed by walker, so its subexpr is
+				 * processed too and we need to process sub-nodes of subexpr.
+				 */
+				if (expression_tree_walker(
+										(Node *) ((CachedExpr *) node)->subexpr,
+										walker, context))
+					return true;
+			}
+			break;
 		case T_ArrayRef:
 			{
 				ArrayRef   *aref = (ArrayRef *) node;
@@ -2529,6 +2586,25 @@ 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.
+				 */
+				newnode->subexpr = (CacheableExpr *) expression_tree_mutator(
+														(Node *) expr->subexpr,
+														mutator,
+														context);
+
+				return (Node *) newnode;
+			}
+			break;
 		case T_ArrayRef:
 			{
 				ArrayRef   *arrayref = (ArrayRef *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b0abe9e..3403cbb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1168,6 +1168,14 @@ _outWindowFunc(StringInfo str, const WindowFunc *node)
 }
 
 static void
+_outCachedExpr(StringInfo str, const CachedExpr *node)
+{
+	WRITE_NODE_TYPE("CACHEDEXPR");
+
+	WRITE_NODE_FIELD(subexpr);
+}
+
+static void
 _outArrayRef(StringInfo str, const ArrayRef *node)
 {
 	WRITE_NODE_TYPE("ARRAYREF");
@@ -3770,6 +3778,9 @@ outNode(StringInfo str, const void *obj)
 			case T_WindowFunc:
 				_outWindowFunc(str, obj);
 				break;
+			case T_CachedExpr:
+				_outCachedExpr(str, obj);
+				break;
 			case T_ArrayRef:
 				_outArrayRef(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 1380703..918c4dd 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -633,6 +633,19 @@ _readWindowFunc(void)
 }
 
 /*
+ * _readCachedExpr
+ */
+static CachedExpr *
+_readCachedExpr(void)
+{
+	READ_LOCALS(CachedExpr);
+
+	READ_NODE_FIELD(subexpr);
+
+	READ_DONE();
+}
+
+/*
  * _readArrayRef
  */
 static ArrayRef *
@@ -2453,6 +2466,8 @@ parseNodeString(void)
 		return_value = _readGroupingFunc();
 	else if (MATCH("WINDOWFUNC", 10))
 		return_value = _readWindowFunc();
+	else if (MATCH("CACHEDEXPR", 10))
+		return_value = _readCachedExpr();
 	else if (MATCH("ARRAYREF", 8))
 		return_value = _readArrayRef();
 	else if (MATCH("FUNCEXPR", 8))
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 2988c11..bce4a41 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,7 @@
 #include "utils/selfuncs.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 
 /* GUC parameters */
@@ -108,6 +109,18 @@ typedef struct
 	int		   *tleref_to_colnum_map;
 } grouping_sets_data;
 
+typedef struct replace_cached_expressions_context
+{
+	PlannerInfo *root;
+
+	/*
+	 * Pointers are nulls if there're not any CaseExpr / CoerceToDomain
+	 * respectively in parent nodes.
+	 */
+	bool		*innermost_caseexpr_nonconst_or_noncached_testvalue;
+	bool		*innermost_coercetodomain_nonconst_or_noncached_value;
+} replace_cached_expressions_context;
+
 /* Local functions */
 static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
 static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
@@ -184,6 +197,8 @@ 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,
+								replace_cached_expressions_context *context);
 
 
 /*****************************************************************************
@@ -6086,3 +6101,1102 @@ get_partitioned_child_rels(PlannerInfo *root, Index rti)
 
 	return result;
 }
+
+static Node *
+replace_cached_expressions_mutator(Node *node,
+								   replace_cached_expressions_context *context)
+{
+	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, context);
+		else
+			rinfo->clause = (Expr *) replace_cached_expressions_mutator(
+				(Node *) rinfo->clause, context);
+
+		/* do NOT recurse into children */
+		return node;
+	}
+	else if (IsA(node, ArrayRef))
+	{
+		/*
+		 * ArrayRef is cached if all its subexpressions (refupperindexpr etc.)
+		 * are consts or cached expressions too. (it returns array or array
+		 * single element so we don't need to check if it returns set; and
+		 * knowing its inputs its behaviour is quite defined so we don't need to
+		 * check volatility)
+		 */
+		ArrayRef   *aref = (ArrayRef *) node;
+		ListCell   *indexpr;
+		Expr	   *refexpr;
+		Expr	   *refassgnexpr;
+		bool		has_nonconst_or_noncached_input = false;
+
+		/* firstly recurse into children */
+		aref = (ArrayRef *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		/* check expressions of upper array indexes */
+		foreach(indexpr, aref->refupperindexpr)
+		{
+			void	   *indexpr_lfirst = lfirst(indexpr);
+			if (indexpr_lfirst != NULL &&
+				!(IsA(indexpr_lfirst, Const) ||
+				  IsA(indexpr_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		/* check expressions of lower array indexes */
+		foreach(indexpr, aref->reflowerindexpr)
+		{
+			void	   *indexpr_lfirst = lfirst(indexpr);
+			if (indexpr_lfirst != NULL &&
+				!(IsA(indexpr_lfirst, Const) ||
+				  IsA(indexpr_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		/* check expression of array value */
+		refexpr = aref->refexpr;
+		if (!(IsA(refexpr, Const) || IsA(refexpr, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+
+		/* check expression of source value */
+		refassgnexpr = aref->refassgnexpr;
+		if (refassgnexpr != NULL &&
+			!(IsA(refassgnexpr, Const) || IsA(refassgnexpr, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+
+		if (has_nonconst_or_noncached_input)
+		{
+			/* return ArrayRef, which will not be cached */
+			return (Node *) aref;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) aref;
+
+			return (Node *) new_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,
+											(void *) context);
+		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))
+		{
+			/* return FuncExpr, which will not be cached */
+			return (Node *) funcexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) 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,
+											(void *) context);
+		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))
+		{
+			/* return OpExpr, which will not be cached */
+			return (Node *) opexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) opexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, DistinctExpr))
+	{
+		/*
+		 * Operator of DistinctExpr 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.
+		 */
+		DistinctExpr *distinctexpr = (DistinctExpr *) 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 *) distinctexpr);
+
+		/* firstly recurse into children */
+		distinctexpr = (DistinctExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+		op_returns_set = distinctexpr->opretset ||
+			expression_returns_set((Node *) distinctexpr->args);
+
+		foreach(arg, distinctexpr->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 *) distinctexpr))
+		{
+			/* return DistinctExpr, which will not be cached */
+			return (Node *) distinctexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) distinctexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, NullIfExpr))
+	{
+		/*
+		 * Operator of NullIfExpr 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.
+		 */
+		NullIfExpr *nullifexpr = (NullIfExpr *) 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 *) nullifexpr);
+
+		/* firstly recurse into children */
+		nullifexpr = (NullIfExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+		op_returns_set = nullifexpr->opretset ||
+			expression_returns_set((Node *) nullifexpr->args);
+
+		foreach(arg, nullifexpr->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 *) nullifexpr))
+		{
+			/* return NullIfExpr, which will not be cached */
+			return (Node *) nullifexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) nullifexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, ScalarArrayOpExpr))
+	{
+		/*
+		 * Operator of ScalarArrayOpExpr is cached if:
+		 * 1) its function is not volatile itself,
+		 * 2) its arguments are constants or cached expressions too.
+		 * (it returns boolean so we don't need to check if it returns set)
+		 */
+		ScalarArrayOpExpr *saopexpr = (ScalarArrayOpExpr *) node;
+		ListCell   *arg;
+		bool		has_nonconst_or_noncached_input = false;
+
+		set_sa_opfuncid(saopexpr);
+
+		/* firstly recurse into children */
+		saopexpr = (ScalarArrayOpExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(arg, saopexpr->args)
+		{
+			void	   *arg_lfirst = lfirst(arg);
+			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		if (has_nonconst_or_noncached_input ||
+			contain_volatile_functions((Node *) saopexpr))
+		{
+			/* return ScalarArrayOpExpr, which will not be cached */
+			return (Node *) saopexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) saopexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, BoolExpr))
+	{
+		/*
+		 * BoolExpr is cached if its arguments are constants or cached
+		 * expressions too. (it returns boolean so we don't need to check if it
+		 * returns set; and its too simple for evaluation so we don't need to
+		 * check volatility)
+		 */
+		BoolExpr   *boolexpr = (BoolExpr *) node;
+		ListCell   *arg;
+		bool		has_nonconst_or_noncached_input = false;
+
+		/* firstly recurse into children */
+		boolexpr = (BoolExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(arg, boolexpr->args)
+		{
+			void	   *arg_lfirst = lfirst(arg);
+			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		if (has_nonconst_or_noncached_input)
+		{
+			/* return BoolExpr, which will not be cached */
+			return (Node *) boolexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) boolexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, FieldSelect))
+	{
+		/*
+		 * FieldSelect is cached if its argument is const or cached expression
+		 * too. (it returns one field from a tuple value so we don't need to
+		 * check if it returns set; and knowing its argument its behaviour is
+		 * quite defined so we don't need to check volatility)
+		 */
+		FieldSelect *fselect = (FieldSelect *) node;
+		Expr	   *arg;
+
+		/* firstly recurse into children */
+		fselect = (FieldSelect *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+		arg = fselect->arg;
+
+		if (!(IsA(arg, Const) || IsA(arg, CachedExpr)))
+		{
+			/* return FieldSelect, which will not be cached */
+			return (Node *) fselect;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) fselect;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, RelabelType))
+	{
+		/*
+		 * RelabelType is cached if its argument is const or cached expression
+		 * too. (it returns its argument so we don't need to check if it returns
+		 * set; and it is a no-op at runtime so we don't need to check
+		 * volatility)
+		 */
+		RelabelType *relabel = (RelabelType *) node;
+		Expr	   *arg;
+
+		/* firstly recurse into children */
+		relabel = (RelabelType *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+		arg = relabel->arg;
+
+		if (!(IsA(arg, Const) || IsA(arg, CachedExpr)))
+		{
+			/* return RelabelType, which will not be cached */
+			return (Node *) relabel;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) relabel;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, CoerceViaIO))
+	{
+		/*
+		 * CoerceViaIO is cached if:
+		 * 1) its argument is const or cached expression too,
+		 * 2) the source type's typoutput function and the destination type's
+		 * typinput function are not volatile themselves.
+		 * (it returns its argument with a type coercion so we don't need
+		 * to check if it returns set)
+		 */
+		CoerceViaIO *iocoerce = (CoerceViaIO *) node;
+		Expr	   *arg;
+
+		/* firstly recurse into children */
+		iocoerce = (CoerceViaIO *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+		arg = iocoerce->arg;
+
+		if (!(IsA(arg, Const) || IsA(arg, CachedExpr)) ||
+			contain_volatile_functions((Node *) iocoerce))
+		{
+			/* return CoerceViaIO, which will not be cached */
+			return (Node *) iocoerce;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) iocoerce;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, ArrayCoerceExpr))
+	{
+		/*
+		 * ArrayCoerceExpr is cached if:
+		 * 1) its argument is const or cached expression too,
+		 * 2) element-type coercion function (if it is needed) is not volatile
+		 * itself.
+		 * (it returns its argument with a type coercion so we don't need
+		 * to check if it returns set)
+		 */
+		ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
+		Expr	   *arg;
+
+		/* firstly recurse into children */
+		acoerce = (ArrayCoerceExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+		arg = acoerce->arg;
+
+		if (!(IsA(arg, Const) || IsA(arg, CachedExpr)) ||
+			contain_volatile_functions((Node *) acoerce))
+		{
+			/* return ArrayCoerceExpr, which will not be cached */
+			return (Node *) acoerce;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) acoerce;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, ConvertRowtypeExpr))
+	{
+		/*
+		 * ConvertRowtypeExpr is cached if its argument is const or cached
+		 * expression too. (it returns its argument (row) maybe without values
+		 * of some columns so we don't need to check if it returns set; and
+		 * knowing its argument its behaviour is quite defined so we don't need
+		 * to check volatility)
+		 */
+		ConvertRowtypeExpr *convexpr = (ConvertRowtypeExpr *) node;
+		Expr	   *arg;
+
+		/* firstly recurse into children */
+		convexpr = (ConvertRowtypeExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+		arg = convexpr->arg;
+
+		if (!(IsA(arg, Const) || IsA(arg, CachedExpr)))
+		{
+			/* return ConvertRowtypeExpr, which will not be cached */
+			return (Node *) convexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) convexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, CaseExpr))
+	{
+		/*
+		 * CaseExpr is cached if all its arguments (= implicit equality
+		 * comparison argument and WHEN clauses) and the default result
+		 * expression are consts or cached expressions too. (it returns one of
+		 * its arguments or the default result so we don't need to check if it
+		 * returns set; and knowing its arguments its behaviour is quite defined
+		 * so we don't need to check volatility)
+		 */
+		CaseExpr   *caseexpr = (CaseExpr *) node;
+		CaseExpr   *new_caseexpr;
+		Expr	   *arg;
+		bool		has_nonconst_or_noncached_input = false;
+		bool		nonconst_or_noncached_testvalue;
+		replace_cached_expressions_context new_context;
+		ListCell   *cell;
+		Expr	   *defresult;
+
+		/*
+		 * Recurse into node manually because we will need different context for
+		 * its subnodes.
+		 */
+		new_caseexpr = (CaseExpr *) palloc(sizeof(CaseExpr));
+		memcpy((new_caseexpr), (caseexpr), sizeof(CaseExpr));
+
+		/* recurse into arg */
+		new_caseexpr->arg = (Expr *) replace_cached_expressions_mutator(
+													(Node *) new_caseexpr->arg,
+													context);
+		arg = new_caseexpr->arg;
+
+		/* check arg and fill new context */
+		nonconst_or_noncached_testvalue =
+			(arg != NULL && !(IsA(arg, Const) || IsA(arg, CachedExpr)));
+		has_nonconst_or_noncached_input = nonconst_or_noncached_testvalue;
+
+		new_context.root = context->root;
+		new_context.innermost_caseexpr_nonconst_or_noncached_testvalue =
+			&nonconst_or_noncached_testvalue;
+		new_context.innermost_coercetodomain_nonconst_or_noncached_value =
+			context->innermost_coercetodomain_nonconst_or_noncached_value;
+
+		/*
+		 * Recurse into args with new context (it will be used by CaseTestExpr
+		 * if it's used in current WHEN clauses subtrees).
+		 */
+		new_caseexpr->args = (List *) expression_tree_mutator(
+											(Node *) new_caseexpr->args,
+											replace_cached_expressions_mutator,
+											(void *) &new_context);
+
+		/* check args */
+		foreach(cell, new_caseexpr->args)
+		{
+			CaseWhen   *casewhen = lfirst(cell);
+			Expr	   *expr = casewhen->expr;
+			Expr	   *result = casewhen->result;
+
+			if (!(IsA(expr, Const) || IsA(expr, CachedExpr)) ||
+				!(IsA(result, Const) || IsA(result, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		/* recurse into defresult */
+		new_caseexpr->defresult = (Expr	*) replace_cached_expressions_mutator(
+											(Node *) new_caseexpr->defresult,
+											context);
+
+		/* check defresult */
+		defresult = new_caseexpr->defresult;
+		if (!(IsA(defresult, Const) || IsA(defresult, CachedExpr)))
+			has_nonconst_or_noncached_input = true;
+
+		if (has_nonconst_or_noncached_input)
+		{
+			/* return CaseExpr, which will not be cached */
+			return (Node *) new_caseexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) new_caseexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, CaseTestExpr))
+	{
+		/*
+		 * CaseTestExpr is cached if we got in context that it is in CaseExpr
+		 * and arg of innermost CaseExpr is const or cached expression too. (it
+		 * is a placeholder node for the test value of its CaseExpr so we don't
+		 * need to check if it returns set and we don't need to check
+		 * volatility)
+		 */
+		CaseTestExpr  *casetest = (CaseTestExpr *) node;
+
+		if (!context->innermost_caseexpr_nonconst_or_noncached_testvalue ||
+			*(context->innermost_caseexpr_nonconst_or_noncached_testvalue))
+		{
+			/* return CaseTestExpr, which will not be cached */
+			return (Node *) casetest;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) casetest;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, ArrayExpr))
+	{
+		/*
+		 * ArrayExpr is cached if its elements are consts or cached expressions
+		 * too. (it returns array so we don't need to check if it returns set;
+		 * and knowing its elements its behaviour is quite defined so we don't
+		 * need to check volatility)
+		 */
+		ArrayExpr  *arrayexpr = (ArrayExpr *) node;
+		ListCell   *element;
+		bool		has_nonconst_or_noncached_input = false;
+
+		/* firstly recurse into children */
+		arrayexpr = (ArrayExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(element, arrayexpr->elements)
+		{
+			void	   *element_lfirst = lfirst(element);
+			if (!(IsA(element_lfirst, Const) ||
+				  IsA(element_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		if (has_nonconst_or_noncached_input)
+		{
+			/* return ArrayExpr, which will not be cached */
+			return (Node *) arrayexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) arrayexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, RowExpr))
+	{
+		/*
+		 * RowExpr is cached if its arguments are consts or cached expressions
+		 * too. (it returns tuple so we don't need to check if it returns set;
+		 * and knowing its arguments its behaviour is quite defined so we don't
+		 * need to check volatility)
+		 */
+		RowExpr    *rowexpr = (RowExpr *) node;
+		ListCell   *arg;
+		bool		has_nonconst_or_noncached_input = false;
+
+		/* firstly recurse into children */
+		rowexpr = (RowExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(arg, rowexpr->args)
+		{
+			void	   *arg_lfirst = lfirst(arg);
+			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		if (has_nonconst_or_noncached_input)
+		{
+			/* return RowExpr, which will not be cached */
+			return (Node *) rowexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) rowexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, RowCompareExpr))
+	{
+		/*
+		 * RowCompareExpr is cached if:
+		 * 1) its pairwise comparison operators are not volatile themselves,
+		 * 2) its arguments are consts or cached expressions too.
+		 * (it returns boolean so we don't need to check if it returns set)
+		 */
+		RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+		ListCell   *arg;
+		bool		has_nonconst_or_noncached_input = false;
+
+		/* firstly recurse into children */
+		rcexpr = (RowCompareExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(arg, rcexpr->largs)
+		{
+			void	   *arg_lfirst = lfirst(arg);
+			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		foreach(arg, rcexpr->rargs)
+		{
+			void	   *arg_lfirst = lfirst(arg);
+			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		if (has_nonconst_or_noncached_input ||
+			contain_volatile_functions((Node *) rcexpr))
+		{
+			/* return RowCompareExpr, which will not be cached */
+			return (Node *) rcexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) rcexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, CoalesceExpr))
+	{
+		/*
+		 * CoalesceExpr is cached if its arguments are consts or cached
+		 * expressions too. (it returns one of its arguments so we don't need to
+		 * check if it returns set; and knowing its arguments its behaviour is
+		 * quite defined so we don't need to check volatility)
+		 */
+		CoalesceExpr *coalesce = (CoalesceExpr *) node;
+		ListCell   *arg;
+		bool		has_nonconst_or_noncached_input = false;
+
+		/* firstly recurse into children */
+		coalesce = (CoalesceExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(arg, coalesce->args)
+		{
+			void	   *arg_lfirst = lfirst(arg);
+			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		if (has_nonconst_or_noncached_input)
+		{
+			/* return CoalesceExpr, which will not be cached */
+			return (Node *) coalesce;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) coalesce;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, MinMaxExpr))
+	{
+		/*
+		 * MinMaxExpr is cached if its arguments are consts or cached
+		 * expressions too. (it returns one of its arguments so we don't need to
+		 * check if it returns set; and it uses btree comparison functions so we
+		 * don't need to check volatility)
+		 */
+		MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
+		ListCell   *arg;
+		bool		has_nonconst_or_noncached_input = false;
+
+		/* firstly recurse into children */
+		minmaxexpr = (MinMaxExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(arg, minmaxexpr->args)
+		{
+			void	   *arg_lfirst = lfirst(arg);
+			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		if (has_nonconst_or_noncached_input)
+		{
+			/* return MinMaxExpr, which will not be cached */
+			return (Node *) minmaxexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) minmaxexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, SQLValueFunction))
+	{
+		/*
+		 * SQLValueFunction is cached if its type is:
+		 * 1) SVFOP_CURRENT_ROLE or
+		 * 2) SVFOP_CURRENT_USER or
+		 * 3) SVFOP_USER or
+		 * 4) SVFOP_SESSION_USER or
+		 * 5) SVFOP_CURRENT_CATALOG or
+		 * 6) SVFOP_CURRENT_SCHEMA.
+		 * (all these functions don't return set and are stable)
+		 */
+		SQLValueFunction *svf = (SQLValueFunction *) node;
+		SQLValueFunctionOp op = svf->op;
+
+		if (!(op == SVFOP_CURRENT_ROLE ||
+			  op == SVFOP_CURRENT_USER ||
+			  op == SVFOP_USER ||
+			  op == SVFOP_SESSION_USER ||
+			  op == SVFOP_CURRENT_CATALOG ||
+			  op == SVFOP_CURRENT_SCHEMA))
+		{
+			/* return SQLValueFunction, which will not be cached */
+			return (Node *) svf;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) svf;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, XmlExpr))
+	{
+		/*
+		 * XmlExpr is cached if all its arguments are consts or cached
+		 * expressions too. (it returns values of different types so we don't
+		 * need to check if it returns set; and knowing its arguments its
+		 * behaviour is quite defined so we don't need to check volatility)
+		 */
+		XmlExpr    *xexpr = (XmlExpr *) node;
+		ListCell   *arg;
+		bool		has_nonconst_or_noncached_input = false;
+
+		/* firstly recurse into children */
+		xexpr = (XmlExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(arg, xexpr->named_args)
+		{
+			void	   *arg_lfirst = lfirst(arg);
+			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		foreach(arg, xexpr->args)
+		{
+			void	   *arg_lfirst = lfirst(arg);
+			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		if (has_nonconst_or_noncached_input)
+		{
+			/* return XmlExpr, which will not be cached */
+			return (Node *) xexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) xexpr;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, NullTest))
+	{
+		/*
+		 * NullTest is cached if its argument is const or cached expression too.
+		 * (it returns boolean so we don't need to check if it returns set; and
+		 * knowing its argument its behaviour is quite defined so we don't need
+		 * to check volatility)
+		 */
+		NullTest   *nulltest = (NullTest *) node;
+		Expr	   *arg;
+
+		/* firstly recurse into children */
+		nulltest = (NullTest *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+		arg = nulltest->arg;
+
+		if (!(IsA(arg, Const) || IsA(arg, CachedExpr)))
+		{
+			/* return NullTest, which will not be cached */
+			return (Node *) nulltest;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) nulltest;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, BooleanTest))
+	{
+		/*
+		 * BooleanTest is cached if its argument is const or cached expression
+		 * too. (it returns boolean so we don't need to check if it returns set;
+		 * and knowing its argument its behaviour is quite defined so we don't
+		 * need to check volatility)
+		 */
+		BooleanTest *btest = (BooleanTest *) node;
+		Expr	   *arg;
+
+		/* firstly recurse into children */
+		btest = (BooleanTest *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+		arg = btest->arg;
+
+		if (!(IsA(arg, Const) || IsA(arg, CachedExpr)))
+		{
+			/* return BooleanTest, which will not be cached */
+			return (Node *) btest;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) btest;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, CoerceToDomain))
+	{
+		/*
+		 * CoerceToDomain is cached if:
+		 * 1) its constraints can be cached,
+		 * 2) its argument is const or cached expression too.
+		 * (it returns its argument coercing a value to a domain type so we
+		 * don't need to check if it returns set)
+		 */
+		CoerceToDomain *ctest = (CoerceToDomain *) node;
+		Expr	   *arg = ctest->arg;
+		bool		has_nonconst_or_noncached_input = false;
+		bool		nonconst_or_noncached_value;
+		replace_cached_expressions_context new_context;
+		DomainConstraintRef *constraint_ref;
+		List	   *constraints;
+		ListCell   *cell;
+
+		/* firstly recurse into arg */
+		arg = (Expr *) replace_cached_expressions_mutator((Node *) arg,
+														  context);
+
+		/* check arg and fill new context */
+		nonconst_or_noncached_value =
+			!(IsA(arg, Const) || IsA(arg, CachedExpr));
+		has_nonconst_or_noncached_input = nonconst_or_noncached_value;
+
+		new_context.root = context->root;
+		new_context.innermost_caseexpr_nonconst_or_noncached_testvalue =
+			context->innermost_caseexpr_nonconst_or_noncached_testvalue;
+		new_context.innermost_coercetodomain_nonconst_or_noncached_value =
+			&nonconst_or_noncached_value;
+
+		/* get constraints and recurse into them with new context */
+		constraint_ref = (DomainConstraintRef *)
+			palloc(sizeof(DomainConstraintRef));
+		InitDomainConstraintRef(ctest->resulttype,
+								constraint_ref,
+								context->root->planner_cxt,
+								false);
+		constraints = GetDomainConstraintExprList(constraint_ref);
+		foreach(cell, constraints)
+		{
+			DomainConstraintExpr *con = (DomainConstraintExpr *) lfirst(cell);
+			Expr	   *check_expr = con->check_expr;
+
+			switch (con->constrainttype)
+			{
+				case DOM_CONSTRAINT_NOTNULL:
+					/* OK */
+					break;
+				case DOM_CONSTRAINT_CHECK:
+					check_expr = (Expr *) replace_cached_expressions_mutator(
+															(Node *) check_expr,
+															&new_context);
+					if (!(IsA(check_expr, Const) ||
+						  IsA(check_expr, CachedExpr)))
+						has_nonconst_or_noncached_input = true;
+					break;
+				default:
+					elog(ERROR, "unrecognized constraint type: %d",
+						 (int) con->constrainttype);
+					break;
+			}
+		}
+
+		if (has_nonconst_or_noncached_input)
+		{
+			/* return RowCompareExpr, which will not be cached */
+			return (Node *) ctest;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) ctest;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, CoerceToDomainValue))
+	{
+		/*
+		 * CoerceToDomainValue is cached if we got in context that it is in
+		 * CoerceToDomain expression and arg of innermost CoerceToDomain
+		 * expression is const or cached expression too. (it is a placeholder
+		 * node for the value of its CoerceToDomain expression so we don't need
+		 * to check if it returns set and we don't need to check volatility)
+		 */
+		CoerceToDomainValue *domval = (CoerceToDomainValue *) node;
+
+		if (!context->innermost_coercetodomain_nonconst_or_noncached_value ||
+			*(context->innermost_coercetodomain_nonconst_or_noncached_value))
+		{
+			/* return CoerceToDomainValue, which will not be cached */
+			return (Node *) domval;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexpr = (CacheableExpr *) domval;
+
+			return (Node *) new_node;
+		}
+	}
+
+	/* otherwise recurse into children */
+	return expression_tree_mutator(node, replace_cached_expressions_mutator,
+								   (void *) context);
+}
diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c
index e61d91b..7028cce 100644
--- a/src/backend/utils/adt/domains.c
+++ b/src/backend/utils/adt/domains.c
@@ -137,7 +137,8 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
 
 	foreach(l, my_extra->constraint_ref.constraints)
 	{
-		DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
+		DomainConstraintState *con_state = (DomainConstraintState *) lfirst(l);
+		DomainConstraintExpr *con = con_state->expr;
 
 		switch (con->constrainttype)
 		{
@@ -177,7 +178,7 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
 												   my_extra->constraint_ref.tcache->typlen);
 					econtext->domainValue_isNull = isnull;
 
-					if (!ExecCheck(con->check_exprstate, econtext))
+					if (!ExecCheck(con_state->check_exprstate, econtext))
 						ereport(ERROR,
 								(errcode(ERRCODE_CHECK_VIOLATION),
 								 errmsg("value for domain %s violates check constraint \"%s\"",
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 7ec31eb..6f12cc3 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -737,7 +737,8 @@ load_domaintype_info(TypeCacheEntry *typentry)
 			bool		isNull;
 			char	   *constring;
 			Expr	   *check_expr;
-			DomainConstraintState *r;
+			DomainConstraintState *r_state;
+			DomainConstraintExpr *r;
 
 			/* Ignore non-CHECK constraints (presently, shouldn't be any) */
 			if (c->contype != CONSTRAINT_CHECK)
@@ -776,11 +777,14 @@ load_domaintype_info(TypeCacheEntry *typentry)
 			/* ExecInitExpr will assume we've planned the expression */
 			check_expr = expression_planner(check_expr);
 
-			r = makeNode(DomainConstraintState);
+			r_state = makeNode(DomainConstraintState);
+			r_state->expr = makeNode(DomainConstraintExpr);
+			r = r_state->expr;
+
 			r->constrainttype = DOM_CONSTRAINT_CHECK;
 			r->name = pstrdup(NameStr(c->conname));
 			r->check_expr = check_expr;
-			r->check_exprstate = NULL;
+			r_state->check_exprstate = NULL;
 
 			MemoryContextSwitchTo(oldcxt);
 
@@ -797,7 +801,7 @@ load_domaintype_info(TypeCacheEntry *typentry)
 				ccons = (DomainConstraintState **)
 					repalloc(ccons, cconslen * sizeof(DomainConstraintState *));
 			}
-			ccons[nccons++] = r;
+			ccons[nccons++] = r_state;
 		}
 
 		systable_endscan(scan);
@@ -834,7 +838,8 @@ load_domaintype_info(TypeCacheEntry *typentry)
 	 */
 	if (notNull)
 	{
-		DomainConstraintState *r;
+		DomainConstraintState *r_state;
+		DomainConstraintExpr *r;
 
 		/* Create the DomainConstraintCache object and context if needed */
 		if (dcc == NULL)
@@ -854,15 +859,17 @@ load_domaintype_info(TypeCacheEntry *typentry)
 		/* Create node trees in DomainConstraintCache's context */
 		oldcxt = MemoryContextSwitchTo(dcc->dccContext);
 
-		r = makeNode(DomainConstraintState);
+		r_state = makeNode(DomainConstraintState);
+		r_state->expr = makeNode(DomainConstraintExpr);
+		r = r_state->expr;
 
 		r->constrainttype = DOM_CONSTRAINT_NOTNULL;
 		r->name = pstrdup("NOT NULL");
 		r->check_expr = NULL;
-		r->check_exprstate = NULL;
+		r_state->check_exprstate = NULL;
 
 		/* lcons to apply the nullness check FIRST */
-		dcc->constraints = lcons(r, dcc->constraints);
+		dcc->constraints = lcons(r_state, dcc->constraints);
 
 		MemoryContextSwitchTo(oldcxt);
 	}
@@ -891,7 +898,7 @@ dcs_cmp(const void *a, const void *b)
 	const DomainConstraintState *const *ca = (const DomainConstraintState *const *) a;
 	const DomainConstraintState *const *cb = (const DomainConstraintState *const *) b;
 
-	return strcmp((*ca)->name, (*cb)->name);
+	return strcmp((*ca)->expr->name, (*cb)->expr->name);
 }
 
 /*
@@ -941,16 +948,21 @@ prep_domain_constraints(List *constraints, MemoryContext execctx)
 
 	foreach(lc, constraints)
 	{
-		DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
-		DomainConstraintState *newr;
+		DomainConstraintState *r_state = (DomainConstraintState *) lfirst(lc);
+		DomainConstraintExpr *r = r_state->expr;
+		DomainConstraintState *newr_state;
+		DomainConstraintExpr *newr;
+
+		newr_state = makeNode(DomainConstraintState);
+		newr_state->expr = makeNode(DomainConstraintExpr);
+		newr = newr_state->expr;
 
-		newr = makeNode(DomainConstraintState);
 		newr->constrainttype = r->constrainttype;
 		newr->name = r->name;
 		newr->check_expr = r->check_expr;
-		newr->check_exprstate = ExecInitExpr(r->check_expr, NULL);
+		newr_state->check_exprstate = ExecInitExpr(r->check_expr, NULL);
 
-		result = lappend(result, newr);
+		result = lappend(result, newr_state);
 	}
 
 	MemoryContextSwitchTo(oldcxt);
@@ -1069,6 +1081,22 @@ DomainHasConstraints(Oid type_id)
 	return (typentry->domainData != NULL);
 }
 
+/*
+ * Return list of DomainConstraintExpr of DomainConstraintState elements of the
+ * given list.
+ */
+List *
+GetDomainConstraintExprList(DomainConstraintRef *ref)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+
+	foreach(lc, ref->constraints)
+		result = lappend(result, ((DomainConstraintState *) lfirst(lc))->expr);
+
+	return result;
+}
+
 
 /*
  * array_element_has_equality and friends are helper routines to check
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 85fac8a..5053abb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -787,25 +787,14 @@ typedef struct AlternativeSubPlanState
 	int			active;			/* list index of the one we're using */
 } AlternativeSubPlanState;
 
-/*
- * DomainConstraintState - one item to check during CoerceToDomain
- *
- * Note: we consider this to be part of an ExprState tree, so we give it
- * a name following the xxxState convention.  But there's no directly
- * associated plan-tree node.
+/* ----------------
+ *		DomainConstraintState node
+ * ----------------
  */
-typedef enum DomainConstraintType
-{
-	DOM_CONSTRAINT_NOTNULL,
-	DOM_CONSTRAINT_CHECK
-} DomainConstraintType;
-
 typedef struct DomainConstraintState
 {
 	NodeTag		type;
-	DomainConstraintType constrainttype;	/* constraint type */
-	char	   *name;			/* name of constraint (for error msgs) */
-	Expr	   *check_expr;		/* for CHECK, a boolean expression */
+	DomainConstraintExpr *expr;		/* expression plan node */
 	ExprState  *check_exprstate;	/* check_expr's eval state, or NULL */
 } DomainConstraintState;
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 0152739..a95b133 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -149,6 +149,8 @@ typedef enum NodeTag
 	T_Aggref,
 	T_GroupingFunc,
 	T_WindowFunc,
+	T_CacheableExpr,
+	T_CachedExpr,
 	T_ArrayRef,
 	T_FuncExpr,
 	T_NamedArgExpr,
@@ -180,6 +182,7 @@ typedef enum NodeTag
 	T_NullTest,
 	T_BooleanTest,
 	T_CoerceToDomain,
+	T_DomainConstraintExpr,
 	T_CoerceToDomainValue,
 	T_SetToDefault,
 	T_CurrentOfExpr,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 38015ed..495c0a5 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -364,6 +364,38 @@ typedef struct WindowFunc
 	int			location;		/* token location, or -1 if unknown */
 } WindowFunc;
 
+/*
+ * CacheableExpr - generic suberclass for expressions that can be cacheable.
+ *
+ * All expression node types that can be cacheable should derive from
+ * CacheableExpr (that is, have CacheableExpr as their first field).  Since
+ * CacheableExpr only contains NodeTag, this is a formality, but it is an easy
+ * form of documentation.
+ *
+ * Expression is cached (= is are calculated once for all output rows, but as
+ * many times as expression is mentioned in query), if:
+ * - it doesn't return a set
+ * - it is not volatile itself
+ * - its arguments are constants or recursively precalculated expressions.
+ *
+ * In planner if expression can be cached it becomes a part of CachedExpr node.
+ */
+typedef struct CacheableExpr
+{
+	NodeTag		type;
+} CacheableExpr;
+
+/*
+ * CachedExpr - expression node for cached expressions (= they are calculated
+ * once for all output rows, but as many times as function is mentioned in
+ * query).
+ */
+typedef struct CachedExpr
+{
+	Expr		xpr;
+	CacheableExpr *subexpr;		/* expression to be cached */
+} CachedExpr;
+
 /* ----------------
  *	ArrayRef: describes an array subscripting operation
  *
@@ -395,7 +427,7 @@ typedef struct WindowFunc
  */
 typedef struct ArrayRef
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Oid			refarraytype;	/* type of the array proper */
 	Oid			refelemtype;	/* type of the array elements */
 	int32		reftypmod;		/* typmod of the array (and elements too) */
@@ -445,7 +477,7 @@ typedef enum CoercionForm
  */
 typedef struct FuncExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Oid			funcid;			/* PG_PROC OID of the function */
 	Oid			funcresulttype; /* PG_TYPE OID of result value */
 	bool		funcretset;		/* true if function returns set */
@@ -492,7 +524,7 @@ typedef struct NamedArgExpr
  */
 typedef struct OpExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
 	Oid			opfuncid;		/* PG_PROC OID of underlying function */
 	Oid			opresulttype;	/* PG_TYPE OID of result value */
@@ -535,7 +567,7 @@ typedef OpExpr NullIfExpr;
  */
 typedef struct ScalarArrayOpExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Oid			opno;			/* PG_OPERATOR OID of the operator */
 	Oid			opfuncid;		/* PG_PROC OID of underlying function */
 	bool		useOr;			/* true for ANY, false for ALL */
@@ -558,7 +590,7 @@ typedef enum BoolExprType
 
 typedef struct BoolExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	BoolExprType boolop;
 	List	   *args;			/* arguments to this expression */
 	int			location;		/* token location, or -1 if unknown */
@@ -738,7 +770,7 @@ typedef struct AlternativeSubPlan
 
 typedef struct FieldSelect
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Expr	   *arg;			/* input expression */
 	AttrNumber	fieldnum;		/* attribute number of field to extract */
 	Oid			resulttype;		/* type of the field (result type of this
@@ -787,7 +819,7 @@ typedef struct FieldStore
 
 typedef struct RelabelType
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion expression */
 	int32		resulttypmod;	/* output typmod (usually -1) */
@@ -807,7 +839,7 @@ typedef struct RelabelType
 
 typedef struct CoerceViaIO
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type of coercion */
 	/* output typmod is not stored, but is presumed -1 */
@@ -830,7 +862,7 @@ typedef struct CoerceViaIO
 
 typedef struct ArrayCoerceExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Expr	   *arg;			/* input expression (yields an array) */
 	Oid			elemfuncid;		/* OID of element coercion function, or 0 */
 	Oid			resulttype;		/* output type of coercion (an array type) */
@@ -855,7 +887,7 @@ typedef struct ArrayCoerceExpr
 
 typedef struct ConvertRowtypeExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* output type (always a composite type) */
 	/* Like RowExpr, we deliberately omit a typmod and collation here */
@@ -902,7 +934,7 @@ typedef struct CollateExpr
  */
 typedef struct CaseExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Oid			casetype;		/* type of expression result */
 	Oid			casecollid;		/* OID of collation, or InvalidOid if none */
 	Expr	   *arg;			/* implicit equality comparison argument */
@@ -932,7 +964,7 @@ typedef struct CaseWhen
  */
 typedef struct CaseTestExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Oid			typeId;			/* type for substituted value */
 	int32		typeMod;		/* typemod for substituted value */
 	Oid			collation;		/* collation for the substituted value */
@@ -948,7 +980,7 @@ typedef struct CaseTestExpr
  */
 typedef struct ArrayExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Oid			array_typeid;	/* type of expression result */
 	Oid			array_collid;	/* OID of collation, or InvalidOid if none */
 	Oid			element_typeid; /* common type of array elements */
@@ -982,7 +1014,7 @@ typedef struct ArrayExpr
  */
 typedef struct RowExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	List	   *args;			/* the fields */
 	Oid			row_typeid;		/* RECORDOID or a composite type's ID */
 
@@ -1027,7 +1059,7 @@ typedef enum RowCompareType
 
 typedef struct RowCompareExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	RowCompareType rctype;		/* LT LE GE or GT, never EQ or NE */
 	List	   *opnos;			/* OID list of pairwise comparison ops */
 	List	   *opfamilies;		/* OID list of containing operator families */
@@ -1041,7 +1073,7 @@ typedef struct RowCompareExpr
  */
 typedef struct CoalesceExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Oid			coalescetype;	/* type of expression result */
 	Oid			coalescecollid; /* OID of collation, or InvalidOid if none */
 	List	   *args;			/* the arguments */
@@ -1059,7 +1091,7 @@ typedef enum MinMaxOp
 
 typedef struct MinMaxExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Oid			minmaxtype;		/* common type of arguments and result */
 	Oid			minmaxcollid;	/* OID of collation of result */
 	Oid			inputcollid;	/* OID of collation that function should use */
@@ -1100,7 +1132,7 @@ typedef enum SQLValueFunctionOp
 
 typedef struct SQLValueFunction
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	SQLValueFunctionOp op;		/* which function this is */
 	Oid			type;			/* result type/typmod */
 	int32		typmod;
@@ -1138,7 +1170,7 @@ typedef enum
 
 typedef struct XmlExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	XmlExprOp	op;				/* xml function ID */
 	char	   *name;			/* name in xml(NAME foo ...) syntaxes */
 	List	   *named_args;		/* non-XML expressions for xml_attributes */
@@ -1176,7 +1208,7 @@ typedef enum NullTestType
 
 typedef struct NullTest
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Expr	   *arg;			/* input expression */
 	NullTestType nulltesttype;	/* IS NULL, IS NOT NULL */
 	bool		argisrow;		/* T to perform field-by-field null checks */
@@ -1199,7 +1231,7 @@ typedef enum BoolTestType
 
 typedef struct BooleanTest
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Expr	   *arg;			/* input expression */
 	BoolTestType booltesttype;	/* test type */
 	int			location;		/* token location, or -1 if unknown */
@@ -1216,7 +1248,7 @@ typedef struct BooleanTest
  */
 typedef struct CoerceToDomain
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Expr	   *arg;			/* input expression */
 	Oid			resulttype;		/* domain type ID (result type) */
 	int32		resulttypmod;	/* output typmod (currently always -1) */
@@ -1226,6 +1258,24 @@ typedef struct CoerceToDomain
 } CoerceToDomain;
 
 /*
+ * DomainConstraintExpr - one item to check during CoerceToDomain
+ */
+
+typedef enum DomainConstraintType
+{
+	DOM_CONSTRAINT_NOTNULL,
+	DOM_CONSTRAINT_CHECK
+} DomainConstraintType;
+
+typedef struct DomainConstraintExpr
+{
+	Expr		xpr;
+	DomainConstraintType constrainttype;		/* constraint type */
+	char	   *name;			/* name of constraint (for error msgs) */
+	Expr	   *check_expr;		/* for CHECK, a boolean expression */
+} DomainConstraintExpr;
+
+/*
  * Placeholder node for the value to be processed by a domain's check
  * constraint.  This is effectively like a Param, but can be implemented more
  * simply since we need only one replacement value at a time.
@@ -1236,7 +1286,7 @@ typedef struct CoerceToDomain
  */
 typedef struct CoerceToDomainValue
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Oid			typeId;			/* type for substituted value */
 	int32		typeMod;		/* typemod for substituted value */
 	Oid			collation;		/* collation for the substituted value */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index c12631d..d8d842e 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -149,6 +149,8 @@ extern void UpdateDomainConstraintRef(DomainConstraintRef *ref);
 
 extern bool DomainHasConstraints(Oid type_id);
 
+extern List * GetDomainConstraintExprList(DomainConstraintRef *ref);
+
 extern TupleDesc lookup_rowtype_tupdesc(Oid type_id, int32 typmod);
 
 extern TupleDesc lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod,
-- 
1.9.1

