From 65b530c7bb469d86868744ae7b7d8f755e830951 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Thu, 24 May 2018 12:29:01 +0300
Subject: [PATCH v9] Precalculate stable/immutable expressions: infrastructure

Create CacheableExpr which is a 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. In planner if expression can be cached it becomes a part
of CachedExpr node.

To execute cached expressions as PARAM_EXEC, add a list of non-internal cached
expressions in PlannerGlobal / PlannedStmt.

Usually plans for all cached expressions are handled during query planning and
they are compiled separatly. But sometimes they are used in dynamically loaded
plans (for example, domain constraints plans). Usually in this case the new node
PlannedExpr is used.
---
 src/backend/nodes/copyfuncs.c  |  35 ++++++++++++
 src/backend/nodes/equalfuncs.c |  17 ++++++
 src/backend/nodes/nodeFuncs.c  |  74 +++++++++++++++++++++++++
 src/backend/nodes/outfuncs.c   |  26 +++++++++
 src/backend/nodes/print.c      |  10 +++-
 src/backend/nodes/readfuncs.c  |  33 +++++++++++
 src/include/nodes/nodes.h      |   3 +
 src/include/nodes/plannodes.h  |  19 +++++++
 src/include/nodes/primnodes.h  | 122 +++++++++++++++++++++++++++++++++--------
 src/include/nodes/relation.h   |   3 +
 10 files changed, 318 insertions(+), 24 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7c045a7..1fbd60f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -102,6 +102,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_LOCATION_FIELD(stmt_location);
 	COPY_LOCATION_FIELD(stmt_len);
+	COPY_NODE_FIELD(cachedExprs);
 
 	return newnode;
 }
@@ -1190,6 +1191,20 @@ _copyPlanInvalItem(const PlanInvalItem *from)
 	return newnode;
 }
 
+/*
+ * _copyPlannedExpr
+ */
+static PlannedExpr *
+_copyPlannedExpr(const PlannedExpr *from)
+{
+	PlannedExpr *newnode = makeNode(PlannedExpr);
+
+	COPY_NODE_FIELD(expr);
+	COPY_NODE_FIELD(cachedExprs);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					   primnodes.h copy functions
  * ****************************************************************
@@ -1425,6 +1440,20 @@ _copyWindowFunc(const WindowFunc *from)
 }
 
 /*
+ * _copyCachedExpr
+ */
+static CachedExpr *
+_copyCachedExpr(const CachedExpr *from)
+{
+	CachedExpr *newnode = makeNode(CachedExpr);
+
+	COPY_NODE_FIELD(subexpr);
+	COPY_SCALAR_FIELD(cached_id);
+
+	return newnode;
+}
+
+/*
  * _copyArrayRef
  */
 static ArrayRef *
@@ -4904,6 +4933,9 @@ copyObjectImpl(const void *from)
 		case T_PlanInvalItem:
 			retval = _copyPlanInvalItem(from);
 			break;
+		case T_PlannedExpr:
+			retval = _copyPlannedExpr(from);
+			break;
 
 			/*
 			 * PRIMITIVE NODES
@@ -4938,6 +4970,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 6a971d0..e04442b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -264,6 +264,20 @@ _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
 }
 
 static bool
+_equalCachedExpr(const CachedExpr *a, const CachedExpr *b)
+{
+	COMPARE_NODE_FIELD(subexpr);
+
+	/*
+	 * Do not compare cached_id because we do not care if both cached
+	 * expressions are internal or not, or if they have the same order in the
+	 * planned statements/expressions.
+	 */
+
+	return true;
+}
+
+static bool
 _equalArrayRef(const ArrayRef *a, const ArrayRef *b)
 {
 	COMPARE_SCALAR_FIELD(refarraytype);
@@ -3037,6 +3051,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 a10014f..9fdd738 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,14 @@ exprIsLengthCoercion(const Node *expr, int32 *coercedTypmod)
 		return true;
 	}
 
+	if (expr && IsA(expr, CachedExpr))
+	{
+		const CachedExpr *cachedexpr = (const CachedExpr *) expr;
+
+		return exprIsLengthCoercion((const Node *) cachedexpr->subexpr,
+									coercedTypmod);
+	}
+
 	return false;
 }
 
@@ -699,6 +714,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 +761,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 +954,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 +1013,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 +1158,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 +1250,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 +1627,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 +1709,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;
@@ -1910,6 +1953,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;
@@ -2539,6 +2594,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 1da9d7e..4fbaf8e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -287,6 +287,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_NODE_FIELD(utilityStmt);
 	WRITE_LOCATION_FIELD(stmt_location);
 	WRITE_LOCATION_FIELD(stmt_len);
+	WRITE_NODE_FIELD(cachedExprs);
 }
 
 /*
@@ -1019,6 +1020,15 @@ _outPlanInvalItem(StringInfo str, const PlanInvalItem *node)
 	WRITE_UINT_FIELD(hashValue);
 }
 
+static void
+_outPlannedExpr(StringInfo str, const PlannedExpr *node)
+{
+	WRITE_NODE_TYPE("PLANNEDEXPR");
+
+	WRITE_NODE_FIELD(expr);
+	WRITE_NODE_FIELD(cachedExprs);
+}
+
 /*****************************************************************************
  *
  *	Stuff from primnodes.h.
@@ -1188,6 +1198,15 @@ _outWindowFunc(StringInfo str, const WindowFunc *node)
 }
 
 static void
+_outCachedExpr(StringInfo str, const CachedExpr *node)
+{
+	WRITE_NODE_TYPE("CACHEDEXPR");
+
+	WRITE_NODE_FIELD(subexpr);
+	WRITE_INT_FIELD(cached_id);
+}
+
+static void
 _outArrayRef(StringInfo str, const ArrayRef *node)
 {
 	WRITE_NODE_TYPE("ARRAYREF");
@@ -2250,6 +2269,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_BOOL_FIELD(parallelModeOK);
 	WRITE_BOOL_FIELD(parallelModeNeeded);
 	WRITE_CHAR_FIELD(maxParallelHazard);
+	WRITE_NODE_FIELD(cachedExprs);
 }
 
 static void
@@ -3822,6 +3842,9 @@ outNode(StringInfo str, const void *obj)
 			case T_PlanInvalItem:
 				_outPlanInvalItem(str, obj);
 				break;
+			case T_PlannedExpr:
+				_outPlannedExpr(str, obj);
+				break;
 			case T_Alias:
 				_outAlias(str, obj);
 				break;
@@ -3852,6 +3875,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/print.c b/src/backend/nodes/print.c
index b9bad5e..20208dd 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -375,11 +375,14 @@ print_expr(const Node *expr, const List *rtable)
 		printf("%s", outputstr);
 		pfree(outputstr);
 	}
-	else if (IsA(expr, OpExpr))
+	else if (IsAIfCached(expr, OpExpr))
 	{
 		const OpExpr *e = (const OpExpr *) expr;
 		char	   *opname;
 
+		if (IsA(expr, CachedExpr))
+			e = (const OpExpr *) ((const CachedExpr *) expr)->subexpr;
+
 		opname = get_opname(e->opno);
 		if (list_length(e->args) > 1)
 		{
@@ -394,12 +397,15 @@ print_expr(const Node *expr, const List *rtable)
 			print_expr(get_leftop((const Expr *) e), rtable);
 		}
 	}
-	else if (IsA(expr, FuncExpr))
+	else if (IsAIfCached(expr, FuncExpr))
 	{
 		const FuncExpr *e = (const FuncExpr *) expr;
 		char	   *funcname;
 		ListCell   *l;
 
+		if (IsA(expr, CachedExpr))
+			e = (const FuncExpr *) ((const CachedExpr *) expr)->subexpr;
+
 		funcname = get_func_name(e->funcid);
 		printf("%s(", ((funcname != NULL) ? funcname : "(invalid function)"));
 		foreach(l, e->args)
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 2826cec..8e9ba71 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -645,6 +645,20 @@ _readWindowFunc(void)
 }
 
 /*
+ * _readCachedExpr
+ */
+static CachedExpr *
+_readCachedExpr(void)
+{
+	READ_LOCALS(CachedExpr);
+
+	READ_NODE_FIELD(subexpr);
+	READ_INT_FIELD(cached_id);
+
+	READ_DONE();
+}
+
+/*
  * _readArrayRef
  */
 static ArrayRef *
@@ -1533,6 +1547,7 @@ _readPlannedStmt(void)
 	READ_NODE_FIELD(utilityStmt);
 	READ_LOCATION_FIELD(stmt_location);
 	READ_LOCATION_FIELD(stmt_len);
+	READ_NODE_FIELD(cachedExprs);
 
 	READ_DONE();
 }
@@ -2377,6 +2392,20 @@ _readPlanInvalItem(void)
 }
 
 /*
+ * _readPlannedExpr
+ */
+static PlannedExpr *
+_readPlannedExpr(void)
+{
+	READ_LOCALS(PlannedExpr);
+
+	READ_NODE_FIELD(expr);
+	READ_NODE_FIELD(cachedExprs);
+
+	READ_DONE();
+}
+
+/*
  * _readSubPlan
  */
 static SubPlan *
@@ -2538,6 +2567,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))
@@ -2724,6 +2755,8 @@ parseNodeString(void)
 		return_value = _readPlanRowMark();
 	else if (MATCH("PLANINVALITEM", 13))
 		return_value = _readPlanInvalItem();
+	else if (MATCH("PLANNEDEXPR", 11))
+		return_value = _readPlannedExpr();
 	else if (MATCH("SUBPLAN", 7))
 		return_value = _readSubPlan();
 	else if (MATCH("ALTERNATIVESUBPLAN", 18))
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index adb159a..90b0fe4 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -88,6 +88,7 @@ typedef enum NodeTag
 	T_NestLoopParam,
 	T_PlanRowMark,
 	T_PlanInvalItem,
+	T_PlannedExpr,
 
 	/*
 	 * TAGS FOR PLAN STATE NODES (execnodes.h)
@@ -146,6 +147,8 @@ typedef enum NodeTag
 	T_Expr,
 	T_Var,
 	T_Const,
+	T_CacheableExpr,
+	T_CachedExpr,
 	T_Param,
 	T_Aggref,
 	T_GroupingFunc,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index f2dda82..fe2fe2d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -98,6 +98,9 @@ typedef struct PlannedStmt
 	/* statement location in source string (copied from Query) */
 	int			stmt_location;	/* start location, or -1 if unknown */
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
+
+	List	   *cachedExprs;	/* list of non-internal cached expressions, or
+								 * NIL */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -1062,4 +1065,20 @@ typedef struct PlanInvalItem
 	uint32		hashValue;		/* hash value of object's cache lookup key */
 } PlanInvalItem;
 
+/*
+ * Planned expression
+ *
+ * Sometimes standalone expressions are planned (see function
+ * expression_planner) and executed (see, for example, the function
+ * ExecPrepareExpr). In these cases we store all the non-internal cached
+ * expressions separatly to initialize and execute them as PARAM_EXEC.
+ */
+typedef struct PlannedExpr
+{
+	NodeTag		type;
+	Expr	   *expr;			/* expression for execution */
+	List	   *cachedExprs;	/* list of non-internal cached expressions, or
+								 * NIL */
+} PlannedExpr;
+
 #endif							/* PLANNODES_H */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f90aa7b..67944d6 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -205,6 +205,84 @@ typedef struct Const
 } Const;
 
 /*
+ * 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 cached 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 */
+
+	/*
+	 * Numeric ID for the cached expression (used by the executor). -1 if the
+	 * cached expression is internal, is used only in other cached expressions
+	 * and is not cached by itself.
+	 */
+	int			cached_id;
+} CachedExpr;
+
+/*
+ * Examine the type of the subexpression of the cached expression.
+ */
+#define IsACached(nodeptr, _type_) \
+	(nodeTag(nodeptr) == T_CachedExpr && \
+	 nodeTag(((CachedExpr *) (nodeptr))->subexpr) == T_##_type_)
+
+/*
+ * If the node is a cached expression and the requested type is not CachedExpr,
+ * examine its subexpression.
+ */
+#define IsAIfCached(nodeptr, _type_) \
+	(nodeTag(nodeptr) == T_##_type_ || IsACached(nodeptr, _type_))
+
+/*
+ * castNodeIfCached(type, ptr) casts ptr (or its subexpression if ptr is a
+ * cached expression and the requested type is not CachedExpr) to "type *", and
+ * if assertions are enabled, verifies that the node has the appropriate type
+ * (using its nodeTag()).
+ */
+
+static inline Node *
+castNodeIfCachedImpl(NodeTag type, void *ptr)
+{
+#ifdef USE_ASSERT_CHECKING
+	Assert(ptr == NULL ||
+		   nodeTag(ptr) == type ||
+		   (nodeTag(ptr) == T_CachedExpr &&
+			(((CachedExpr *) ptr)->subexpr == NULL ||
+			 nodeTag(((CachedExpr *) ptr)->subexpr) == type)));
+#endif							/* USE_ASSERT_CHECKING */
+	if (ptr && nodeTag(ptr) == T_CachedExpr && type != T_CachedExpr)
+		return (Node *) ((CachedExpr *) ptr)->subexpr;
+	return (Node *) ptr;
+}
+
+#define castNodeIfCached(_type_, nodeptr) \
+	((_type_ *) castNodeIfCachedImpl(T_##_type_, nodeptr))
+
+/*
  * Param
  *
  *		paramkind specifies the kind of parameter. The possible values
@@ -241,7 +319,7 @@ typedef enum ParamKind
 
 typedef struct Param
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	ParamKind	paramkind;		/* kind of parameter. See above */
 	int			paramid;		/* numeric ID for parameter */
 	Oid			paramtype;		/* pg_type OID of parameter's datatype */
@@ -396,7 +474,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) */
@@ -446,7 +524,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 */
@@ -493,7 +571,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 */
@@ -536,7 +614,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 */
@@ -559,7 +637,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 */
@@ -739,7 +817,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
@@ -791,7 +869,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) */
@@ -811,7 +889,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 */
@@ -835,7 +913,7 @@ typedef struct CoerceViaIO
 
 typedef struct ArrayCoerceExpr
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	Expr	   *arg;			/* input expression (yields an array) */
 	Expr	   *elemexpr;		/* expression representing per-element work */
 	Oid			resulttype;		/* output type of coercion (an array type) */
@@ -860,7 +938,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 */
@@ -907,7 +985,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 */
@@ -937,7 +1015,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 */
@@ -953,7 +1031,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 */
@@ -987,7 +1065,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 */
 
@@ -1035,7 +1113,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 */
@@ -1049,7 +1127,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 */
@@ -1067,7 +1145,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 */
@@ -1108,7 +1186,7 @@ typedef enum SQLValueFunctionOp
 
 typedef struct SQLValueFunction
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	SQLValueFunctionOp op;		/* which function this is */
 	Oid			type;			/* result type/typmod */
 	int32		typmod;
@@ -1146,7 +1224,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 */
@@ -1184,7 +1262,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 */
@@ -1207,7 +1285,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 */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 3b28d19..0620316 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -145,6 +145,9 @@ typedef struct PlannerGlobal
 	bool		parallelModeNeeded; /* parallel mode actually required? */
 
 	char		maxParallelHazard;	/* worst PROPARALLEL hazard level */
+
+	List	   *cachedExprs;	/* list of non-internal cached expressions, or
+								 * NIL */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
-- 
2.7.4

