WIP Patch: Precalculate stable functions, infrastructure v1

Started by Marina Polyakovaover 8 years ago42 messages
#1Marina Polyakova
m.polyakova@postgrespro.ru
1 attachment(s)

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]: /messages/by-id/98c77534fa51aa4bf84a5b39931c42ea@postgrespro.ru
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]: /messages/by-id/98c77534fa51aa4bf84a5b39931c42ea@postgrespro.ru
/messages/by-id/98c77534fa51aa4bf84a5b39931c42ea@postgrespro.ru

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

Precalculate-stable-functions-infrastructure-v1.patchtext/x-diff; name=Precalculate-stable-functions-infrastructure-v1.patchDownload
From d7871c9aaf64210130b591a93c13b18c74ebb2b4 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@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

#2Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Marina Polyakova (#1)
2 attachment(s)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

and here I send infrastructure patch which includes <...>

Next 2 patches:

Patch 'planning and execution', which includes:
- replacement nonvolatile functions and operators by appropriate cached
expressions;
- planning and execution cached expressions;
- regression tests.

Patch 'costs', which includes cost changes for cached expressions
(according to their behaviour).

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

Precalculate-stable-functions-costs-v1.patchtext/x-diff; name=Precalculate-stable-functions-costs-v1.patchDownload
From cf446cbfc8625701f9e3f32d1870b47de869802a Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Thu, 4 May 2017 19:36:05 +0300
Subject: [PATCH 3/3] Precalculate stable functions, costs 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:
- cost changes for cached expressions (according to their behaviour)
---
 src/backend/optimizer/path/costsize.c | 58 ++++++++++++++++++++++++++++++++---
 1 file changed, 53 insertions(+), 5 deletions(-)

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 52643d0..34707fa 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -140,6 +140,7 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root,
 			   PathKey *pathkey);
 static void cost_rescan(PlannerInfo *root, Path *path,
 			Cost *rescan_startup_cost, Cost *rescan_total_cost);
+static double cost_eval_cacheable_expr_per_tuple(Node *node);
 static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
 static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
 						  ParamPathInfo *param_info,
@@ -3464,6 +3465,44 @@ cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root)
 	*cost = context.total;
 }
 
+/*
+ * cost_eval_cacheable_expr_per_tuple
+ *		Evaluate per tuple cost for expressions that can be cacheable.
+ *
+ * This function was created to not duplicate code for some expression and
+ * cached some expression.
+ */
+static double
+cost_eval_cacheable_expr_per_tuple(Node *node)
+{
+	double result;
+
+	/*
+	 * For each operator or function node in the given tree, we charge the
+	 * estimated execution cost given by pg_proc.procost (remember to multiply
+	 * this by cpu_operator_cost).
+	 */
+	if (IsA(node, FuncExpr))
+	{
+		result = get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+	}
+	else if (IsA(node, OpExpr))
+	{
+		OpExpr     *opexpr = (OpExpr *) node;
+
+		/* rely on struct equivalence to treat these all alike */
+		set_opfuncid(opexpr);
+
+		result = get_func_cost(opexpr->opfuncid) * cpu_operator_cost;
+	}
+	else
+	{
+		elog(ERROR, "non cacheable expression node type: %d", (int) nodeTag(node));
+	}
+
+	return result;
+}
+
 static bool
 cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 {
@@ -3537,13 +3576,22 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 	 * moreover, since our rowcount estimates for functions tend to be pretty
 	 * phony, the results would also be pretty phony.
 	 */
-	if (IsA(node, FuncExpr))
+	if (IsA(node, FuncExpr) ||
+		IsA(node, OpExpr))
 	{
-		context->total.per_tuple +=
-			get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+		context->total.per_tuple += cost_eval_cacheable_expr_per_tuple(node);
+	}
+	else if (IsA(node, CachedExpr))
+	{	
+		/* 
+		 * Calculate subexpression cost per tuple as usual and add it to startup
+		 * cost (because subexpression will be executed only once for all
+		 * tuples).
+		 */
+		context->total.startup += cost_eval_cacheable_expr_per_tuple(
+			get_subexpr((CachedExpr *) node));
 	}
-	else if (IsA(node, OpExpr) ||
-			 IsA(node, DistinctExpr) ||
+	else if (IsA(node, DistinctExpr) ||
 			 IsA(node, NullIfExpr))
 	{
 		/* rely on struct equivalence to treat these all alike */
-- 
1.9.1

Precalculate-stable-functions-planning-and-execution-v1.patchtext/x-diff; name=Precalculate-stable-functions-planning-and-execution-v1.patchDownload
From 508f8b959ff9d1ab78dfc79ab4657b4c10a11690 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Thu, 4 May 2017 19:09:51 +0300
Subject: [PATCH 2/3] Precalculate stable functions, planning and execution 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:
- replacement nonvolatile functions and operators by appropriate cached
expressions
- planning and execution cached expressions
- regression tests
---
 src/backend/executor/execExpr.c                    |  70 ++
 src/backend/executor/execExprInterp.c              | 191 +++++
 src/backend/optimizer/path/allpaths.c              |   9 +-
 src/backend/optimizer/path/clausesel.c             |  13 +
 src/backend/optimizer/plan/planagg.c               |   1 +
 src/backend/optimizer/plan/planner.c               |  28 +
 src/backend/optimizer/util/clauses.c               |  43 ++
 src/backend/utils/adt/ruleutils.c                  |   5 +
 src/include/executor/execExpr.h                    |  38 +-
 src/include/optimizer/planner.h                    |   3 +
 src/include/optimizer/tlist.h                      |   7 +-
 src/pl/plpgsql/src/pl_exec.c                       |  10 +
 .../expected/precalculate_stable_functions.out     | 827 +++++++++++++++++++++
 src/test/regress/serial_schedule                   |   1 +
 .../regress/sql/precalculate_stable_functions.sql  | 282 +++++++
 15 files changed, 1518 insertions(+), 10 deletions(-)
 create mode 100644 src/test/regress/expected/precalculate_stable_functions.out
 create mode 100644 src/test/regress/sql/precalculate_stable_functions.sql

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 5a34a46..c004f4c 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -72,6 +72,8 @@ static bool isAssignmentIndirectionExpr(Expr *expr);
 static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 					   PlanState *parent, ExprState *state,
 					   Datum *resv, bool *resnull);
+static void ExecInitCachedExpr(ExprEvalStep *scratch, CachedExpr *cachedexpr,
+							   PlanState *parent, ExprState *state);
 
 
 /*
@@ -865,6 +867,14 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
 				break;
 			}
 
+		case T_CachedExpr:
+			{
+				ExecInitCachedExpr(&scratch, (CachedExpr *) node, parent,
+								   state);
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
@@ -2675,3 +2685,63 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 		}
 	}
 }
+
+/*
+ * Prepare evaluation of an CachedExpr expression.
+ */
+static void
+ExecInitCachedExpr(ExprEvalStep *scratch, CachedExpr *cachedexpr,
+				   PlanState *parent, ExprState *state)
+{
+	FuncData   *data = palloc0(sizeof(FuncData));
+
+	/* initialize subexpression as usual */
+	switch (cachedexpr->subexprtype)
+	{
+		case CACHED_FUNCEXPR:
+			{
+				FuncExpr   *func = cachedexpr->subexpr.funcexpr;
+
+				ExecInitFunc(scratch, (Expr *) func,
+							 func->args, func->funcid, func->inputcollid,
+							 parent, state);
+			}
+			break;
+		case CACHED_OPEXPR:
+			{
+				OpExpr	   *op = cachedexpr->subexpr.opexpr;
+
+				ExecInitFunc(scratch, (Expr *) op,
+							 op->args, op->opfuncid, op->inputcollid,
+							 parent, state);
+			}
+			break;
+	}
+
+	/* copy data from scratch */
+	*data = scratch->d.func;
+
+	/* initialize scratch as cached expression */
+	switch (scratch->opcode)
+	{
+		case EEOP_FUNCEXPR:
+			scratch->opcode = EEOP_CACHED_FUNCEXPR;
+			break;
+		case EEOP_FUNCEXPR_STRICT:
+			scratch->opcode = EEOP_CACHED_FUNCEXPR_STRICT;
+			break;
+		case EEOP_FUNCEXPR_FUSAGE:
+			scratch->opcode = EEOP_CACHED_FUNCEXPR_FUSAGE;
+			break;
+		case EEOP_FUNCEXPR_STRICT_FUSAGE:
+			scratch->opcode = EEOP_CACHED_FUNCEXPR_STRICT_FUSAGE;
+			break;
+		default:
+			elog(ERROR, "unknown opcode for caching expression");
+			break;		
+	}
+	scratch->d.cachedexpr.subexprdata = data;
+	scratch->d.cachedexpr.isExecuted = false;
+	scratch->d.cachedexpr.resnull = false;
+	scratch->d.cachedexpr.resvalue = (Datum) 0;
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index fed0052..8c5989e 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -279,6 +279,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 	TupleTableSlot *innerslot;
 	TupleTableSlot *outerslot;
 	TupleTableSlot *scanslot;
+	MemoryContext oldContext;	/* for EEOP_CACHED_* */
 
 	/*
 	 * This array has to be in the same order as enum ExprEvalOp.
@@ -309,6 +310,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_FUNCEXPR_STRICT,
 		&&CASE_EEOP_FUNCEXPR_FUSAGE,
 		&&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE,
+		&&CASE_EEOP_CACHED_FUNCEXPR,
+		&&CASE_EEOP_CACHED_FUNCEXPR_STRICT,
+		&&CASE_EEOP_CACHED_FUNCEXPR_FUSAGE,
+		&&CASE_EEOP_CACHED_FUNCEXPR_STRICT_FUSAGE,
 		&&CASE_EEOP_BOOL_AND_STEP_FIRST,
 		&&CASE_EEOP_BOOL_AND_STEP,
 		&&CASE_EEOP_BOOL_AND_STEP_LAST,
@@ -721,6 +726,192 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_CACHED_FUNCEXPR)
+		{
+			FunctionCallInfo fcinfo = op->d.cachedexpr.subexprdata->fcinfo_data;
+
+			if (op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result */
+				fcinfo->isnull = op->d.cachedexpr.resnull;
+				*op->resvalue = op->d.cachedexpr.resvalue;
+				*op->resnull = fcinfo->isnull;
+
+				goto cached_funcexpr;
+			}
+
+			/*
+			 * If function is cacheable then switch per-query memory context.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* execute function as usual */
+			fcinfo->isnull = false;
+			*op->resvalue = (op->d.cachedexpr.subexprdata->fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+			/* save result and switch memory context back */
+			op->d.cachedexpr.resnull = fcinfo->isnull;
+			op->d.cachedexpr.resvalue = *op->resvalue;
+			op->d.cachedexpr.isExecuted = true;
+			MemoryContextSwitchTo(oldContext);
+
+	cached_funcexpr:
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHED_FUNCEXPR_STRICT)
+		{
+			FunctionCallInfo fcinfo = op->d.cachedexpr.subexprdata->fcinfo_data;
+			bool	   *argnull = fcinfo->argnull;
+			int			argno;
+
+			if (op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result */
+				fcinfo->isnull = op->d.cachedexpr.resnull;
+				if (!fcinfo->isnull)
+					*op->resvalue = op->d.cachedexpr.resvalue;
+				*op->resnull = fcinfo->isnull;
+
+				goto cached_funcexpr_strict;
+			}
+
+			/* strict function, so check for NULL args */
+			for (argno = 0; argno < op->d.func.nargs; argno++)
+			{
+				if (argnull[argno])
+				{
+					*op->resnull = true;
+
+					op->d.cachedexpr.resnull = *op->resnull;
+					op->d.cachedexpr.isExecuted = true;
+
+					goto cached_strictfail;
+				}
+			}
+
+			/*
+			 * If function is cacheable then switch per-query memory context.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* execute function as usual */
+			fcinfo->isnull = false;
+			*op->resvalue = (op->d.cachedexpr.subexprdata->fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+			/* save result and switch memory context back */
+			op->d.cachedexpr.resnull = fcinfo->isnull;
+			op->d.cachedexpr.resvalue = *op->resvalue;
+			op->d.cachedexpr.isExecuted = true;
+			MemoryContextSwitchTo(oldContext);
+
+	cached_funcexpr_strict:
+	cached_strictfail:
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHED_FUNCEXPR_FUSAGE)
+		{
+			FunctionCallInfo fcinfo = op->d.cachedexpr.subexprdata->fcinfo_data;
+			PgStat_FunctionCallUsage fcusage;
+
+			if (op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result */
+				fcinfo->isnull = op->d.cachedexpr.resnull;
+				*op->resvalue = op->d.cachedexpr.resvalue;
+				*op->resnull = fcinfo->isnull;
+
+				goto cached_funcexpr_fusage;
+			}
+
+			pgstat_init_function_usage(fcinfo, &fcusage);
+
+			/*
+			 * If function is cacheable then switch per-query memory context.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* execute function as usual */
+			fcinfo->isnull = false;
+			*op->resvalue = (op->d.cachedexpr.subexprdata->fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+			/* save result and switch memory context back */
+			op->d.cachedexpr.resnull = fcinfo->isnull;
+			op->d.cachedexpr.resvalue = *op->resvalue;
+			op->d.cachedexpr.isExecuted = true;
+			MemoryContextSwitchTo(oldContext);
+
+			pgstat_end_function_usage(&fcusage, true);
+
+	cached_funcexpr_fusage:
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHED_FUNCEXPR_STRICT_FUSAGE)
+		{
+			FunctionCallInfo fcinfo = op->d.cachedexpr.subexprdata->fcinfo_data;
+			PgStat_FunctionCallUsage fcusage;
+			bool	   *argnull = fcinfo->argnull;
+			int			argno;
+
+			if (op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result */
+				fcinfo->isnull = op->d.cachedexpr.resnull;
+				if (!fcinfo->isnull)
+					*op->resvalue = op->d.cachedexpr.resvalue;
+				*op->resnull = fcinfo->isnull;
+
+				goto cached_funcexpr_strict_fusage;
+			}
+
+			/* strict function, so check for NULL args */
+			for (argno = 0; argno < op->d.func.nargs; argno++)
+			{
+				if (argnull[argno])
+				{
+					*op->resnull = true;
+
+					op->d.cachedexpr.resnull = *op->resnull;
+					op->d.cachedexpr.isExecuted = true;
+
+					goto cached_strictfail_fusage;
+				}
+			}
+
+			pgstat_init_function_usage(fcinfo, &fcusage);
+
+			/*
+			 * If function is cacheable then switch per-query memory context.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* execute function as usual */
+			fcinfo->isnull = false;
+			*op->resvalue = (op->d.cachedexpr.subexprdata->fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+			/* save result and switch memory context back */
+			op->d.cachedexpr.resnull = fcinfo->isnull;
+			op->d.cachedexpr.resvalue = *op->resvalue;
+			op->d.cachedexpr.isExecuted = true;
+			MemoryContextSwitchTo(oldContext);
+
+			pgstat_end_function_usage(&fcusage, true);
+
+	cached_funcexpr_strict_fusage:
+	cached_strictfail_fusage:
+			EEO_NEXT();
+		}
+
 		/*
 		 * If any of its clauses is FALSE, an AND's result is FALSE regardless
 		 * of the states of the rest of the clauses, so we can stop evaluating
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b93b4fc..a322255 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -378,7 +378,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 				set_subquery_pathlist(root, rel, rti, rte);
 				break;
 			case RTE_FUNCTION:
-				set_function_size_estimates(root, rel);
+				{
+					rel->baserestrictinfo = replace_qual_cached_expressions(
+						rel->baserestrictinfo);
+					set_function_size_estimates(root, rel);
+				}
 				break;
 			case RTE_TABLEFUNC:
 				set_tablefunc_size_estimates(root, rel);
@@ -517,6 +521,9 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	 */
 	check_index_predicates(root, rel);
 
+	rel->baserestrictinfo = replace_qual_cached_expressions(
+		rel->baserestrictinfo);
+
 	/* Mark rel with estimated output rows, width, etc */
 	set_baserel_size_estimates(root, rel);
 }
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 758ddea..fc799f1 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
@@ -825,6 +826,18 @@ clause_selectivity(PlannerInfo *root,
 								jointype,
 								sjinfo);
 	}
+	else if (IsA(clause, CachedExpr))
+	{
+		/*
+		 * Not sure this case is needed, but it can't hurt.
+		 * Calculate selectivity of subexpression.
+		 */
+		s1 = clause_selectivity(root,
+								get_subexpr((CachedExpr *) clause),
+								varRelid,
+								jointype,
+								sjinfo);
+	}
 	else
 	{
 		/*
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 5565736..7a28764 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -38,6 +38,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "optimizer/planner.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4dd8cbb..985e1b4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6088,6 +6088,34 @@ get_partitioned_child_rels(PlannerInfo *root, Index rti)
 	return result;
 }
 
+/*
+ * replace_pathtarget_cached_expressions
+ *		Replace cached expresisons in a PathTarget tlist.
+ *
+ * As a notational convenience, returns the same PathTarget pointer passed in.
+ */
+PathTarget *
+replace_pathtarget_cached_expressions(PathTarget *target)
+{
+	target->exprs = (List *) replace_cached_expressions_mutator(
+		(Node *) target->exprs);
+
+	return target;
+}
+
+/*
+ * replace_qual_cached_expressions
+ *		Replace cacehd expressions in a WHERE clause. The input can be either an
+ *		implicitly-ANDed list of boolean expressions, or a list of RestrictInfo
+ *		nodes.
+ */
+List *
+replace_qual_cached_expressions(List *quals)
+{
+	/* No setup needed for tree walk, so away we go */
+	return (List *) replace_cached_expressions_mutator((Node *) quals);
+}
+
 static Node *
 replace_cached_expressions_mutator(Node *node)
 {
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a1dafc8..adf8dac 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2758,6 +2758,49 @@ eval_const_expressions_mutator(Node *node,
 				newexpr->location = expr->location;
 				return (Node *) newexpr;
 			}
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+				Node	   *new_subexpr = eval_const_expressions_mutator(
+					get_subexpr(cachedexpr), context);
+				CachedExpr *new_cachedexpr;
+
+				/*
+				 * If unsafe transformations are used cached expression should
+				 * be always simplified.
+				 */
+				if (context->estimate)
+					Assert(IsA(new_subexpr, Const));
+
+				if (IsA(new_subexpr, Const))
+				{
+					/* successfully simplified it */
+					return new_subexpr;	
+				}
+				else
+				{
+					/*
+					 * The expression cannot be simplified any further, so build
+					 * and return a replacement CachedExpr node using the
+					 * possibly-simplified arguments of subexpression.
+					 */
+					new_cachedexpr = makeNode(CachedExpr);
+					new_cachedexpr->subexprtype = cachedexpr->subexprtype;
+					switch (new_cachedexpr->subexprtype)
+					{
+						case CACHED_FUNCEXPR:
+							new_cachedexpr->subexpr.funcexpr = (FuncExpr *)
+								new_subexpr;
+							break;
+						case CACHED_OPEXPR:
+							new_cachedexpr->subexpr.opexpr = (OpExpr *)
+								new_subexpr;
+							break;
+					}
+
+					return (Node *) new_cachedexpr;
+				}
+			}
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cbde1ff..cc33655 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7690,6 +7690,11 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_CachedExpr:
+			get_rule_expr(get_subexpr((CachedExpr *) node), context,
+						  showimplicit);
+			break;
+
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 86fdb33..c0c4207 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -85,6 +85,12 @@ typedef enum ExprEvalOp
 	EEOP_FUNCEXPR_FUSAGE,
 	EEOP_FUNCEXPR_STRICT_FUSAGE,
 
+	/* evaluate CachedExpr */
+	EEOP_CACHED_FUNCEXPR,
+	EEOP_CACHED_FUNCEXPR_STRICT,
+	EEOP_CACHED_FUNCEXPR_FUSAGE,
+	EEOP_CACHED_FUNCEXPR_STRICT_FUSAGE,
+
 	/*
 	 * Evaluate boolean AND expression, one step per subexpression. FIRST/LAST
 	 * subexpressions are special-cased for performance.  Since AND always has
@@ -217,6 +223,20 @@ typedef enum ExprEvalOp
 } ExprEvalOp;
 
 
+/*
+ * Inline data of ExprEvalStep for operations
+ * EEOP_FUNCEXPR_* / NULLIF / DISTINCT / CACHED_FUNCEXPR_*
+ */
+typedef struct FuncData
+{
+	FmgrInfo   *finfo;	/* function's lookup data */
+	FunctionCallInfo fcinfo_data;		/* arguments etc */
+	/* faster to access without additional indirection: */
+	PGFunction	fn_addr;	/* actual call address */
+	int			nargs;	/* number of arguments */
+} FuncData;
+
+
 typedef struct ExprEvalStep
 {
 	/*
@@ -289,14 +309,18 @@ typedef struct ExprEvalStep
 		}			constval;
 
 		/* for EEOP_FUNCEXPR_* / NULLIF / DISTINCT */
+		FuncData 	func;
+
+		/* for EEOP_CACHED_FUNCEXPR_* */
 		struct
-		{
-			FmgrInfo   *finfo;	/* function's lookup data */
-			FunctionCallInfo fcinfo_data;		/* arguments etc */
-			/* faster to access without additional indirection: */
-			PGFunction	fn_addr;	/* actual call address */
-			int			nargs;	/* number of arguments */
-		}			func;
+ 		{
+ 			/* cached ExprEvalOp data */
+			FuncData   *subexprdata;
+
+			bool		isExecuted;
+			bool		resnull;
+			Datum		resvalue;
+		}			cachedexpr;
 
 		/* for EEOP_BOOL_*_STEP */
 		struct
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index f3aaa23..bbadcdd 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -59,4 +59,7 @@ extern bool plan_cluster_use_sort(Oid tableOid, Oid indexOid);
 
 extern List *get_partitioned_child_rels(PlannerInfo *root, Index rti);
 
+extern PathTarget *replace_pathtarget_cached_expressions(PathTarget *target);
+extern List *replace_qual_cached_expressions(List *quals);
+
 #endif   /* PLANNER_H */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index ccb93d8..0b893d0 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -65,8 +65,11 @@ extern void split_pathtarget_at_srfs(PlannerInfo *root,
 						 PathTarget *target, PathTarget *input_target,
 						 List **targets, List **targets_contain_srfs);
 
-/* Convenience macro to get a PathTarget with valid cost/width fields */
+/* Convenience macro to get a PathTarget with valid cost/width fields and
+ * cached expressions.
+ */
 #define create_pathtarget(root, tlist) \
-	set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
+	set_pathtarget_cost_width(root, replace_pathtarget_cached_expressions( \
+		make_pathtarget_from_tlist(tlist)))
 
 #endif   /* TLIST_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 7a40c99..2e27052 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -6535,6 +6535,16 @@ exec_simple_check_node(Node *node)
 				return TRUE;
 			}
 
+		case T_CachedExpr:
+			{
+				/*
+				 * If CachedExpr will not be initialized by ExecInitCachedExpr
+				 * possibly it will use cached value when it shouldn't (for
+				 * example, snapshot has changed), so return false.
+				 */
+				return FALSE;
+			}
+
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
new file mode 100644
index 0000000..cfef1d2
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -0,0 +1,827 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create tables for testing
+CREATE TABLE two (i integer);
+INSERT INTO two VALUES (1), (2);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(i) from two);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE operator === (PROCEDURE = equal_integers_vlt, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ==== (PROCEDURE = equal_integers_stl, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ===== (PROCEDURE = equal_integers_imm, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ====== (PROCEDURE = equal_booleans_stl_strict, LEFTARG = boolean, RIGHTARG = boolean);
+-- Simple functions testing
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- Functions with constant arguments and nested functions testing
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Nested and strict operators testing
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      2
+(1 row)
+
+INSERT INTO two VALUES (3);
+SELECT simple();
+ simple 
+--------
+      3
+(1 row)
+
+ROLLBACK;
+-- Drop tables for testing
+DROP TABLE two;
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 04206c3..f2710b9 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -179,3 +179,4 @@ test: with
 test: xml
 test: event_trigger
 test: stats
+test: precalculate_stable_functions
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
new file mode 100644
index 0000000..c86c382
--- /dev/null
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -0,0 +1,282 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+
+-- Create tables for testing
+
+CREATE TABLE two (i integer);
+INSERT INTO two VALUES (1), (2);
+
+-- Create volatile functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create stable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(i) from two);
+END
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create immutable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create operators for testing
+
+CREATE operator === (PROCEDURE = equal_integers_vlt, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ==== (PROCEDURE = equal_integers_stl, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ===== (PROCEDURE = equal_integers_imm, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ====== (PROCEDURE = equal_booleans_stl_strict, LEFTARG = boolean, RIGHTARG = boolean);
+
+-- Simple functions testing
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+-- WHERE clause testing
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+-- Functions with constant arguments and nested functions testing
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions testing
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions with null arguments testing
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+-- Operators testing
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+
+-- Nested and strict operators testing
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+
+-- Mixed functions and operators testing
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+-- Tracking functions testing
+
+SET track_functions TO 'all';
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+SET track_functions TO DEFAULT;
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+
+BEGIN;
+SELECT simple();
+INSERT INTO two VALUES (3);
+SELECT simple();
+ROLLBACK;
+
+-- Drop tables for testing
+
+DROP TABLE two;
-- 
1.9.1

#3Alexander Korotkov
a.korotkov@postgrespro.ru
In reply to: Marina Polyakova (#2)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

On Thu, May 4, 2017 at 7:51 PM, Marina Polyakova <m.polyakova@postgrespro.ru

wrote:

and here I send infrastructure patch which includes <...>

Next 2 patches:

Patch 'planning and execution', which includes:
- replacement nonvolatile functions and operators by appropriate cached
expressions;
- planning and execution cached expressions;
- regression tests.

Patch 'costs', which includes cost changes for cached expressions
(according to their behaviour).

Great, thank you for your work.
It's good and widely used practice to prepend number to the patch name
while dealing with patch set.

------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#4Aleksander Alekseev
a.alekseev@postgrespro.ru
In reply to: Alexander Korotkov (#3)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hi Marina,

I've noticed that this patch needs a review and decided to take a look.
Here is a short summary:

* Patches apply to the master branch. There are a trailing whitespaces,
though.
* All tests pass.
* I see 8-10% performance improvement on full text search queries.
* It seems that there is no obvious performance degradation on regular
queries (according to pgbench).

In short, it looks very promising.

--
Best regards,
Aleksander Alekseev

#5Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Aleksander Alekseev (#4)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hello, Aleksander!

I've noticed that this patch needs a review and decided to take a look.

Thank you very much!

There are a trailing whitespaces,
though.

Oh, sorry, I'll check them.

I see 8-10% performance improvement on full text search queries.

Glad to hear it =)

It seems that there is no obvious performance degradation on regular
queries (according to pgbench).

Thanks for testing it, I'll try not to forget about it next time =[

In short, it looks very promising.

And thanks again!

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Aleksander Alekseev (#4)
3 attachment(s)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hello!

Here's v2 of the patches. Changes from v1:
* Precalculation of DistinctExpr, NullIfExpr, ScalarArrayOpExpr (as
usual, if their operators are not volatile theirselves, don't return set
and their arguments are consts or cached expressions too);
* Removed trailing whitespaces.
Also, as I tested, it seems that there is no obvious performance
degradation too on regular queries (according to pgbench).

Patches are attached. Any suggestions are welcome!

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0001-Precalculate-stable-functions-infrastructure-v2.patchtext/x-diff; name=0001-Precalculate-stable-functions-infrastructure-v2.patchDownload
From 92ee741fc8294186db689a4438009369b9c460b4 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 15 May 2017 14:24:36 +0300
Subject: [PATCH 1/3] Precalculate stable functions, infrastructure v2

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        |  31 +++++
 src/backend/nodes/equalfuncs.c       |  31 +++++
 src/backend/nodes/nodeFuncs.c        | 151 ++++++++++++++++++++
 src/backend/nodes/outfuncs.c         |  56 ++++++++
 src/backend/nodes/readfuncs.c        |  48 +++++++
 src/backend/optimizer/plan/planner.c | 259 +++++++++++++++++++++++++++++++++++
 src/include/nodes/nodeFuncs.h        |   1 +
 src/include/nodes/nodes.h            |   1 +
 src/include/nodes/primnodes.h        |  38 +++++
 9 files changed, 616 insertions(+)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6ad3844..f9f69a1 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1527,6 +1527,34 @@ _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;
+		case CACHED_DISTINCTEXPR:
+			COPY_NODE_FIELD(subexpr.distinctexpr);
+			break;
+		case CACHED_NULLIFEXPR:
+			COPY_NODE_FIELD(subexpr.nullifexpr);
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			COPY_NODE_FIELD(subexpr.saopexpr);
+			break;
+	}
+
+	return newnode;
+}
+
 /*
  * _copyScalarArrayOpExpr
  */
@@ -4867,6 +4895,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 c9a8c34..8863759 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -384,6 +384,34 @@ _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;
+		case CACHED_DISTINCTEXPR:
+			COMPARE_NODE_FIELD(subexpr.distinctexpr);
+			break;
+		case CACHED_NULLIFEXPR:
+			COMPARE_NODE_FIELD(subexpr.nullifexpr);
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			COMPARE_NODE_FIELD(subexpr.saopexpr);
+			break;
+	}
+
+	return true;
+}
+
+static bool
 _equalScalarArrayOpExpr(const ScalarArrayOpExpr *a, const ScalarArrayOpExpr *b)
 {
 	COMPARE_SCALAR_FIELD(opno);
@@ -3031,6 +3059,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..e3dd576 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,54 @@ 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;
+					case CACHED_DISTINCTEXPR:
+						newnode->subexpr.distinctexpr = (DistinctExpr *)
+							expression_tree_mutator(
+								(Node *) expr->subexpr.distinctexpr, mutator,
+								context);
+						break;
+					case CACHED_NULLIFEXPR:
+						newnode->subexpr.nullifexpr = (NullIfExpr *)
+							expression_tree_mutator(
+								(Node *) expr->subexpr.nullifexpr, mutator,
+								context);
+						break;
+					case CACHED_SCALARARRAYOPEXPR:
+						newnode->subexpr.saopexpr = (ScalarArrayOpExpr *)
+							expression_tree_mutator(
+								(Node *) expr->subexpr.saopexpr, mutator,
+								context);
+						break;
+				}
+
+				return (Node *) newnode;
+			}
+			break;
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
@@ -3838,3 +3935,57 @@ 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;
+		case CACHED_DISTINCTEXPR:
+			return (const Node *) cachedexpr->subexpr.distinctexpr;
+		case CACHED_NULLIFEXPR:
+			return (const Node *) cachedexpr->subexpr.nullifexpr;
+		case CACHED_SCALARARRAYOPEXPR:
+			return (const Node *) cachedexpr->subexpr.saopexpr;
+	}
+
+	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;
+		case CACHED_DISTINCTEXPR:
+			return (Node *) cachedexpr->subexpr.distinctexpr;
+		case CACHED_NULLIFEXPR:
+			return (Node *) cachedexpr->subexpr.nullifexpr;
+		case CACHED_SCALARARRAYOPEXPR:
+			return (Node *) cachedexpr->subexpr.saopexpr;
+	}
+
+	return NULL;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8d9ff63..c0c8363 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1237,6 +1237,59 @@ _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;
+		case CACHED_DISTINCTEXPR:
+			{
+				/* ... out subexprtype end */
+				outToken(str, "cached_distinctexpr");
+
+				WRITE_NODE_FIELD(subexpr.distinctexpr);
+			}
+			break;
+		case CACHED_NULLIFEXPR:
+			{
+				/* ... out subexprtype end */
+				outToken(str, "cached_nullifexpr");
+
+				WRITE_NODE_FIELD(subexpr.nullifexpr);
+			}
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			{
+				/* ... out subexprtype end */
+				outToken(str, "cached_scalararrayopexpr");
+
+				WRITE_NODE_FIELD(subexpr.saopexpr);
+			}
+			break;
+	}
+}
+
+static void
 _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
 {
 	WRITE_NODE_TYPE("SCALARARRAYOPEXPR");
@@ -3767,6 +3820,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 e24f5d6..acb14f9 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -750,6 +750,52 @@ _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 if (strncmp(token, "cached_distinctexpr", 19) == 0)
+		local_node->subexprtype = CACHED_DISTINCTEXPR;
+	else if (strncmp(token, "cached_nullifexpr", 17) == 0)
+		local_node->subexprtype = CACHED_NULLIFEXPR;
+	else if (strncmp(token, "cached_scalararrayopexpr", 24) == 0)
+		local_node->subexprtype = CACHED_SCALARARRAYOPEXPR;
+	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;
+		case CACHED_DISTINCTEXPR:
+			READ_NODE_FIELD(subexpr.distinctexpr);
+			break;
+		case CACHED_NULLIFEXPR:
+			READ_NODE_FIELD(subexpr.nullifexpr);
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			READ_NODE_FIELD(subexpr.saopexpr);
+			break;
+	}
+
+	READ_DONE();
+}
+
+/*
  * _readScalarArrayOpExpr
  */
 static ScalarArrayOpExpr *
@@ -2462,6 +2508,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..552b73d 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,261 @@ 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;
+		}
+	}
+	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,
+											NULL);
+		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->xpr))
+		{
+			/* return DistinctExpr, which will not be cached */
+			return (Node *) distinctexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexprtype = CACHED_DISTINCTEXPR;
+			new_node->subexpr.distinctexpr = 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,
+											NULL);
+		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->xpr))
+		{
+			/* return NullIfExpr, which will not be cached */
+			return (Node *) nullifexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexprtype = CACHED_NULLIFEXPR;
+			new_node->subexpr.nullifexpr = 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,
+											NULL);
+
+		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->xpr))
+		{
+			/* return ScalarArrayOpExpr, which will not be cached */
+			return (Node *) saopexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexprtype = CACHED_SCALARARRAYOPEXPR;
+			new_node->subexpr.saopexpr = saopexpr;
+
+			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..0dbfa12 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -76,5 +76,6 @@ extern bool raw_expression_tree_walker(Node *node, bool (*walker) (),
 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..3f89653 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1498,4 +1498,42 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * 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 */
+	CACHED_DISTINCTEXPR,		/* cached DistinctExpr */
+	CACHED_NULLIFEXPR,			/* cached NullIfExpr */
+	CACHED_SCALARARRAYOPEXPR	/* cached ScalarArrayOpExpr */
+} 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 */
+		DistinctExpr *distinctexpr; /* for CACHED_DISTINCTEXPR */
+		NullIfExpr *nullifexpr; /* for CACHED_NULLIFEXPR */
+		ScalarArrayOpExpr *saopexpr;	/* for CACHED_SCALARARRAYOPEXPR */
+	} subexpr;
+} CachedExpr;
+
 #endif   /* PRIMNODES_H */
-- 
1.9.1

0002-Precalculate-stable-functions-planning-and-execution-v2.patchtext/x-diff; name=0002-Precalculate-stable-functions-planning-and-execution-v2.patchDownload
From cf6bf3748e836e148edd4ef332ab2d92527be712 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 15 May 2017 15:31:21 +0300
Subject: [PATCH 2/3] Precalculate stable functions, planning and execution v2

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:
- replacement nonvolatile functions and operators by appropriate cached
expressions
- planning and execution cached expressions
- regression tests
---
 src/backend/executor/execExpr.c                    |  256 +-
 src/backend/executor/execExprInterp.c              |  390 ++-
 src/backend/optimizer/path/allpaths.c              |    9 +-
 src/backend/optimizer/path/clausesel.c             |   13 +
 src/backend/optimizer/plan/planagg.c               |    1 +
 src/backend/optimizer/plan/planner.c               |   28 +
 src/backend/optimizer/util/clauses.c               |   55 +
 src/backend/utils/adt/ruleutils.c                  |    5 +
 src/include/executor/execExpr.h                    |   72 +-
 src/include/optimizer/planner.h                    |    3 +
 src/include/optimizer/tlist.h                      |    8 +-
 src/pl/plpgsql/src/pl_exec.c                       |   10 +
 .../expected/precalculate_stable_functions.out     | 2625 ++++++++++++++++++++
 src/test/regress/serial_schedule                   |    1 +
 .../regress/sql/precalculate_stable_functions.sql  |  949 +++++++
 15 files changed, 4344 insertions(+), 81 deletions(-)
 create mode 100644 src/test/regress/expected/precalculate_stable_functions.out
 create mode 100644 src/test/regress/sql/precalculate_stable_functions.sql

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 5a34a46..1127034 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -72,6 +72,13 @@ static bool isAssignmentIndirectionExpr(Expr *expr);
 static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 					   PlanState *parent, ExprState *state,
 					   Datum *resv, bool *resnull);
+static void ExecInitScalarArrayOpExpr(ExprEvalStep *scratch,
+									  ScalarArrayOpExpr *opexpr,
+									  PlanState *parent, ExprState *state,
+									  Datum *resv, bool *resnull);
+static void ExecInitCachedExpr(ExprEvalStep *scratch, CachedExpr *cachedexpr,
+							   PlanState *parent, ExprState *state, Datum *resv,
+							   bool *resnull);
 
 
 /*
@@ -865,55 +872,17 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
 				break;
 			}
 
-		case T_ScalarArrayOpExpr:
+		case T_CachedExpr:
 			{
-				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
-				Expr	   *scalararg;
-				Expr	   *arrayarg;
-				FmgrInfo   *finfo;
-				FunctionCallInfo fcinfo;
-				AclResult	aclresult;
-
-				Assert(list_length(opexpr->args) == 2);
-				scalararg = (Expr *) linitial(opexpr->args);
-				arrayarg = (Expr *) lsecond(opexpr->args);
-
-				/* Check permission to call function */
-				aclresult = pg_proc_aclcheck(opexpr->opfuncid,
-											 GetUserId(),
-											 ACL_EXECUTE);
-				if (aclresult != ACLCHECK_OK)
-					aclcheck_error(aclresult, ACL_KIND_PROC,
-								   get_func_name(opexpr->opfuncid));
-				InvokeFunctionExecuteHook(opexpr->opfuncid);
-
-				/* Set up the primary fmgr lookup information */
-				finfo = palloc0(sizeof(FmgrInfo));
-				fcinfo = palloc0(sizeof(FunctionCallInfoData));
-				fmgr_info(opexpr->opfuncid, finfo);
-				fmgr_info_set_expr((Node *) node, finfo);
-				InitFunctionCallInfoData(*fcinfo, finfo, 2,
-										 opexpr->inputcollid, NULL, NULL);
-
-				/* Evaluate scalar directly into left function argument */
-				ExecInitExprRec(scalararg, parent, state,
-								&fcinfo->arg[0], &fcinfo->argnull[0]);
+				ExecInitCachedExpr(&scratch, (CachedExpr *) node, parent,
+								   state, resv, resnull);
+				break;
+			}
 
-				/*
-				 * Evaluate array argument into our return value.  There's no
-				 * danger in that, because the return value is guaranteed to
-				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
-				 * passed to any other expression.
-				 */
-				ExecInitExprRec(arrayarg, parent, state, resv, resnull);
-
-				/* And perform the operation */
-				scratch.opcode = EEOP_SCALARARRAYOP;
-				scratch.d.scalararrayop.element_type = InvalidOid;
-				scratch.d.scalararrayop.useOr = opexpr->useOr;
-				scratch.d.scalararrayop.finfo = finfo;
-				scratch.d.scalararrayop.fcinfo_data = fcinfo;
-				scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+		case T_ScalarArrayOpExpr:
+			{
+				ExecInitScalarArrayOpExpr(&scratch, (ScalarArrayOpExpr *) node,
+										  parent, state, resv, resnull);
 				ExprEvalPushStep(state, &scratch);
 				break;
 			}
@@ -2675,3 +2644,196 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 		}
 	}
 }
+
+/*
+ * Prepare evaluation of a ScalarArrayOpExpr expression.
+ *
+ * This function was created to not duplicate code for ScalarArrayOpExpr and
+ * cached ScalarArrayOpExpr.
+ */
+static void
+ExecInitScalarArrayOpExpr(ExprEvalStep *scratch, ScalarArrayOpExpr *opexpr,
+				   		  PlanState *parent, ExprState *state, Datum *resv,
+				   		  bool *resnull)
+{
+	Expr	   *scalararg;
+	Expr	   *arrayarg;
+	FmgrInfo   *finfo;
+	FunctionCallInfo fcinfo;
+	AclResult	aclresult;
+
+	Assert(list_length(opexpr->args) == 2);
+	scalararg = (Expr *) linitial(opexpr->args);
+	arrayarg = (Expr *) lsecond(opexpr->args);
+
+	/* Check permission to call function */
+	aclresult = pg_proc_aclcheck(opexpr->opfuncid,
+								 GetUserId(),
+								 ACL_EXECUTE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, ACL_KIND_PROC,
+					   get_func_name(opexpr->opfuncid));
+	InvokeFunctionExecuteHook(opexpr->opfuncid);
+
+	/* Set up the primary fmgr lookup information */
+	finfo = palloc0(sizeof(FmgrInfo));
+	fcinfo = palloc0(sizeof(FunctionCallInfoData));
+	fmgr_info(opexpr->opfuncid, finfo);
+	fmgr_info_set_expr((Node *) opexpr, finfo);
+	InitFunctionCallInfoData(*fcinfo, finfo, 2,
+							 opexpr->inputcollid, NULL, NULL);
+
+	/* Evaluate scalar directly into left function argument */
+	ExecInitExprRec(scalararg, parent, state,
+					&fcinfo->arg[0], &fcinfo->argnull[0]);
+
+	/*
+	 * Evaluate array argument into our return value.  There's no
+	 * danger in that, because the return value is guaranteed to
+	 * be overwritten by EEOP_SCALARARRAYOP, and will not be
+	 * passed to any other expression.
+	 */
+	ExecInitExprRec(arrayarg, parent, state, resv, resnull);
+
+	/* And perform the operation */
+	scratch->opcode = EEOP_SCALARARRAYOP;
+	scratch->d.scalararrayop.element_type = InvalidOid;
+	scratch->d.scalararrayop.useOr = opexpr->useOr;
+	scratch->d.scalararrayop.finfo = finfo;
+	scratch->d.scalararrayop.fcinfo_data = fcinfo;
+	scratch->d.scalararrayop.fn_addr = finfo->fn_addr;
+}
+
+/*
+ * Prepare evaluation of an CachedExpr expression.
+ */
+static void
+ExecInitCachedExpr(ExprEvalStep *scratch, CachedExpr *cachedexpr,
+				   PlanState *parent, ExprState *state, Datum *resv,
+				   bool *resnull)
+{
+	CacheableInlineData *subexpr = palloc0(sizeof(CacheableInlineData));
+
+	/* initialize subexpression as usual */
+	switch (cachedexpr->subexprtype)
+	{
+		case CACHED_FUNCEXPR:
+			{
+				FuncExpr   *func = cachedexpr->subexpr.funcexpr;
+
+				ExecInitFunc(scratch, (Expr *) func,
+							 func->args, func->funcid, func->inputcollid,
+							 parent, state);
+			}
+			break;
+		case CACHED_OPEXPR:
+			{
+				OpExpr	   *op = cachedexpr->subexpr.opexpr;
+
+				ExecInitFunc(scratch, (Expr *) op,
+							 op->args, op->opfuncid, op->inputcollid,
+							 parent, state);
+			}
+			break;
+		case CACHED_DISTINCTEXPR:
+			{
+				DistinctExpr *distinctexpr = cachedexpr->subexpr.distinctexpr;
+
+				ExecInitFunc(scratch, (Expr *) distinctexpr,
+							 distinctexpr->args, distinctexpr->opfuncid,
+							 distinctexpr->inputcollid, parent, state);
+				/*
+				 * Change opcode of subexpression call instruction to
+				 * EEOP_DISTINCT.
+				 *
+				 * XXX: historically we've not called the function usage
+				 * pgstat infrastructure - that seems inconsistent given that
+				 * we do so for normal function *and* operator evaluation.  If
+				 * we decided to do that here, we'd probably want separate
+				 * opcodes for FUSAGE or not.
+				 */
+				scratch->opcode = EEOP_DISTINCT;
+			}
+			break;
+		case CACHED_NULLIFEXPR:
+			{
+				NullIfExpr *nullifexpr = cachedexpr->subexpr.nullifexpr;
+
+				ExecInitFunc(scratch, (Expr *) nullifexpr,
+							 nullifexpr->args, nullifexpr->opfuncid,
+							 nullifexpr->inputcollid, parent, state);
+
+				/*
+				 * Change opcode of subexpression call instruction to
+				 * EEOP_NULLIF.
+				 *
+				 * XXX: historically we've not called the function usage
+				 * pgstat infrastructure - that seems inconsistent given that
+				 * we do so for normal function *and* operator evaluation.  If
+				 * we decided to do that here, we'd probably want separate
+				 * opcodes for FUSAGE or not.
+				 */
+				scratch->opcode = EEOP_NULLIF;
+			}
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			{
+				ExecInitScalarArrayOpExpr(scratch, cachedexpr->subexpr.saopexpr,
+										  parent, state, resv, resnull);
+			}
+			break;
+	}
+
+	/* copy data from scratch */
+	switch (scratch->opcode)
+	{
+		case EEOP_FUNCEXPR:
+		case EEOP_FUNCEXPR_STRICT:
+		case EEOP_FUNCEXPR_FUSAGE:
+		case EEOP_FUNCEXPR_STRICT_FUSAGE:
+		case EEOP_DISTINCT:
+		case EEOP_NULLIF:
+			subexpr->func = scratch->d.func;
+			break;
+		case EEOP_SCALARARRAYOP:
+			subexpr->scalararrayop = scratch->d.scalararrayop;
+			break;
+		default:
+			elog(ERROR, "unknown opcode for caching expression");
+			break;
+	}
+
+	/* initialize scratch as cached expression */
+	switch (scratch->opcode)
+	{
+		case EEOP_FUNCEXPR:
+			scratch->opcode = EEOP_CACHED_FUNCEXPR;
+			break;
+		case EEOP_FUNCEXPR_STRICT:
+			scratch->opcode = EEOP_CACHED_FUNCEXPR_STRICT;
+			break;
+		case EEOP_FUNCEXPR_FUSAGE:
+			scratch->opcode = EEOP_CACHED_FUNCEXPR_FUSAGE;
+			break;
+		case EEOP_FUNCEXPR_STRICT_FUSAGE:
+			scratch->opcode = EEOP_CACHED_FUNCEXPR_STRICT_FUSAGE;
+			break;
+		case EEOP_DISTINCT:
+			scratch->opcode = EEOP_CACHED_DISTINCT;
+			break;
+		case EEOP_NULLIF:
+			scratch->opcode = EEOP_CACHED_NULLIF;
+			break;
+		case EEOP_SCALARARRAYOP:
+			scratch->opcode = EEOP_CACHED_SCALARARRAYOP;
+			break;
+		default:
+			elog(ERROR, "unknown opcode for caching expression");
+			break;		
+	}
+	scratch->d.cachedexpr.subexpr = subexpr;
+	scratch->d.cachedexpr.isExecuted = false;
+	scratch->d.cachedexpr.resnull = false;
+	scratch->d.cachedexpr.resvalue = (Datum) 0;
+	ExprEvalPushStep(state, scratch);
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index fed0052..5f456bc 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -279,6 +279,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 	TupleTableSlot *innerslot;
 	TupleTableSlot *outerslot;
 	TupleTableSlot *scanslot;
+	MemoryContext oldContext;	/* for EEOP_CACHED_* */
 
 	/*
 	 * This array has to be in the same order as enum ExprEvalOp.
@@ -309,6 +310,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_FUNCEXPR_STRICT,
 		&&CASE_EEOP_FUNCEXPR_FUSAGE,
 		&&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE,
+		&&CASE_EEOP_CACHED_FUNCEXPR,
+		&&CASE_EEOP_CACHED_FUNCEXPR_STRICT,
+		&&CASE_EEOP_CACHED_FUNCEXPR_FUSAGE,
+		&&CASE_EEOP_CACHED_FUNCEXPR_STRICT_FUSAGE,
+		&&CASE_EEOP_CACHED_DISTINCT,
+		&&CASE_EEOP_CACHED_NULLIF,
+		&&CASE_EEOP_CACHED_SCALARARRAYOP,
 		&&CASE_EEOP_BOOL_AND_STEP_FIRST,
 		&&CASE_EEOP_BOOL_AND_STEP,
 		&&CASE_EEOP_BOOL_AND_STEP_LAST,
@@ -721,6 +729,346 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_CACHED_FUNCEXPR)
+		{
+			struct FuncData *func = &(op->d.cachedexpr.subexpr->func);
+			FunctionCallInfo fcinfo = func->fcinfo_data;
+
+			if (op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result */
+				*op->resnull = op->d.cachedexpr.resnull;
+				*op->resvalue = op->d.cachedexpr.resvalue;
+
+				goto cached_funcexpr;
+			}
+
+			/*
+			 * If function is cacheable then switch per-query memory context.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* execute function as usual */
+			fcinfo->isnull = false;
+			*op->resvalue = (func->fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+			/* save result and switch memory context back */
+			op->d.cachedexpr.resnull = *op->resnull;
+			op->d.cachedexpr.resvalue = *op->resvalue;
+			op->d.cachedexpr.isExecuted = true;
+			MemoryContextSwitchTo(oldContext);
+
+	cached_funcexpr:
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHED_FUNCEXPR_STRICT)
+		{
+			struct FuncData *func = &(op->d.cachedexpr.subexpr->func);
+			FunctionCallInfo fcinfo = func->fcinfo_data;
+			bool	   *argnull = fcinfo->argnull;
+			int			argno;
+
+			if (op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result */
+				*op->resnull = op->d.cachedexpr.resnull;
+				if (!(*op->resnull))
+					*op->resvalue = op->d.cachedexpr.resvalue;
+
+				goto cached_funcexpr_strict;
+			}
+
+			/* strict function, so check for NULL args */
+			for (argno = 0; argno < func->nargs; argno++)
+			{
+				if (argnull[argno])
+				{
+					*op->resnull = true;
+
+					op->d.cachedexpr.resnull = *op->resnull;
+					op->d.cachedexpr.isExecuted = true;
+
+					goto cached_strictfail;
+				}
+			}
+
+			/*
+			 * If function is cacheable then switch per-query memory context.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* execute function as usual */
+			fcinfo->isnull = false;
+			*op->resvalue = (func->fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+			/* save result and switch memory context back */
+			op->d.cachedexpr.resnull = *op->resnull;
+			op->d.cachedexpr.resvalue = *op->resvalue;
+			op->d.cachedexpr.isExecuted = true;
+			MemoryContextSwitchTo(oldContext);
+
+	cached_funcexpr_strict:
+	cached_strictfail:
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHED_FUNCEXPR_FUSAGE)
+		{
+			struct FuncData *func = &(op->d.cachedexpr.subexpr->func);
+			FunctionCallInfo fcinfo = func->fcinfo_data;
+			PgStat_FunctionCallUsage fcusage;
+
+			if (op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result */
+				*op->resnull = op->d.cachedexpr.resnull;
+				*op->resvalue = op->d.cachedexpr.resvalue;
+
+				goto cached_funcexpr_fusage;
+			}
+
+			pgstat_init_function_usage(fcinfo, &fcusage);
+
+			/*
+			 * If function is cacheable then switch per-query memory context.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* execute function as usual */
+			fcinfo->isnull = false;
+			*op->resvalue = (func->fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+			/* save result and switch memory context back */
+			op->d.cachedexpr.resnull = *op->resnull;
+			op->d.cachedexpr.resvalue = *op->resvalue;
+			op->d.cachedexpr.isExecuted = true;
+			MemoryContextSwitchTo(oldContext);
+
+			pgstat_end_function_usage(&fcusage, true);
+
+	cached_funcexpr_fusage:
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHED_FUNCEXPR_STRICT_FUSAGE)
+		{
+			struct FuncData *func = &(op->d.cachedexpr.subexpr->func);
+			FunctionCallInfo fcinfo = func->fcinfo_data;
+			PgStat_FunctionCallUsage fcusage;
+			bool	   *argnull = fcinfo->argnull;
+			int			argno;
+
+			if (op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result */
+				*op->resnull = op->d.cachedexpr.resnull;
+				if (!(*op->resnull))
+					*op->resvalue = op->d.cachedexpr.resvalue;
+
+				goto cached_funcexpr_strict_fusage;
+			}
+
+			/* strict function, so check for NULL args */
+			for (argno = 0; argno < func->nargs; argno++)
+			{
+				if (argnull[argno])
+				{
+					*op->resnull = true;
+
+					op->d.cachedexpr.resnull = *op->resnull;
+					op->d.cachedexpr.isExecuted = true;
+
+					goto cached_strictfail_fusage;
+				}
+			}
+
+			pgstat_init_function_usage(fcinfo, &fcusage);
+
+			/*
+			 * If function is cacheable then switch per-query memory context.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* execute function as usual */
+			fcinfo->isnull = false;
+			*op->resvalue = (func->fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+			/* save result and switch memory context back */
+			op->d.cachedexpr.resnull = *op->resnull;
+			op->d.cachedexpr.resvalue = *op->resvalue;
+			op->d.cachedexpr.isExecuted = true;
+			MemoryContextSwitchTo(oldContext);
+
+			pgstat_end_function_usage(&fcusage, true);
+
+	cached_funcexpr_strict_fusage:
+	cached_strictfail_fusage:
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHED_DISTINCT)
+		{
+			/*
+			 * IS DISTINCT FROM must evaluate arguments (already done into
+			 * fcinfo->arg/argnull) to determine whether they are NULL; if
+			 * either is NULL then the result is determined.  If neither is
+			 * NULL, then proceed to evaluate the comparison function, which
+			 * is just the type's standard equality operator.  We need not
+			 * care whether that function is strict.  Because the handling of
+			 * nulls is different, we can't just reuse EEOP_CACHED_FUNCEXPR.
+			 */
+			struct FuncData *func = &(op->d.cachedexpr.subexpr->func);
+			FunctionCallInfo fcinfo = func->fcinfo_data;
+
+			if (op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result */
+				*op->resnull = op->d.cachedexpr.resnull;
+				*op->resvalue = op->d.cachedexpr.resvalue;
+
+				goto cached_distinct;
+			}
+
+			/*
+			 * If function is cacheable then switch per-query memory context.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* check function arguments for NULLness */
+			if (fcinfo->argnull[0] && fcinfo->argnull[1])
+			{
+				/* Both NULL? Then is not distinct... */
+				*op->resvalue = BoolGetDatum(false);
+				*op->resnull = false;
+			}
+			else if (fcinfo->argnull[0] || fcinfo->argnull[1])
+			{
+				/* Only one is NULL? Then is distinct... */
+				*op->resvalue = BoolGetDatum(true);
+				*op->resnull = false;
+			}
+			else
+			{
+				/* Neither null, so apply the equality function */
+				Datum		eqresult;
+
+				fcinfo->isnull = false;
+				eqresult = (func->fn_addr) (fcinfo);
+				/* Must invert result of "="; safe to do even if null */
+				*op->resvalue = BoolGetDatum(!DatumGetBool(eqresult));
+				*op->resnull = fcinfo->isnull;
+			}
+
+			/* save result and switch memory context back */
+			op->d.cachedexpr.resnull = *op->resnull;
+			op->d.cachedexpr.resvalue = *op->resvalue;
+			op->d.cachedexpr.isExecuted = true;
+			MemoryContextSwitchTo(oldContext);
+
+	cached_distinct:
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHED_NULLIF)
+		{
+			/*
+			 * The arguments are already evaluated into fcinfo->arg/argnull.
+			 */
+			struct FuncData *func = &(op->d.cachedexpr.subexpr->func);
+			FunctionCallInfo fcinfo = func->fcinfo_data;
+
+			if (op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result */
+				*op->resnull = op->d.cachedexpr.resnull;
+				*op->resvalue = op->d.cachedexpr.resvalue;
+
+				goto cached_nullif;
+			}
+
+			/*
+			 * If function is cacheable then switch per-query memory context.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/* if either argument is NULL they can't be equal */
+			if (!fcinfo->argnull[0] && !fcinfo->argnull[1])
+			{
+				Datum		result;
+
+				fcinfo->isnull = false;
+				result = (func->fn_addr) (fcinfo);
+
+				/* if the arguments are equal return null */
+				if (!fcinfo->isnull && DatumGetBool(result))
+				{
+					*op->resvalue = (Datum) 0;
+					*op->resnull = true;
+
+					goto cache_nullif;
+				}
+			}
+
+			/* Arguments aren't equal, so return the first one */
+			*op->resvalue = fcinfo->arg[0];
+			*op->resnull = fcinfo->argnull[0];
+
+	cache_nullif:
+			/* save result and switch memory context back */
+			op->d.cachedexpr.resnull = *op->resnull;
+			op->d.cachedexpr.resvalue = *op->resvalue;
+			op->d.cachedexpr.isExecuted = true;
+			MemoryContextSwitchTo(oldContext);
+
+	cached_nullif:
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHED_SCALARARRAYOP)
+		{
+			if (op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result */
+				*op->resnull = op->d.cachedexpr.resnull;
+				if (!(*op->resnull))
+					*op->resvalue = op->d.cachedexpr.resvalue;
+
+				goto cached_scalararrayop;
+			}
+
+			/*
+			 * If function is cacheable then switch per-query memory context.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			/*
+			 * Execute function as usual.
+			 * Too complex for an inline implementation.
+			 */
+			ExecEvalScalarArrayOp(state, op);
+
+			/* save result and switch memory context back */
+			op->d.cachedexpr.resnull = *op->resnull;
+			op->d.cachedexpr.resvalue = *op->resvalue;
+			op->d.cachedexpr.isExecuted = true;
+			MemoryContextSwitchTo(oldContext);
+
+	cached_scalararrayop:
+			EEO_NEXT();
+		}
+
 		/*
 		 * If any of its clauses is FALSE, an AND's result is FALSE regardless
 		 * of the states of the rest of the clauses, so we can stop evaluating
@@ -2880,9 +3228,10 @@ ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext
 void
 ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 {
-	FunctionCallInfo fcinfo = op->d.scalararrayop.fcinfo_data;
-	bool		useOr = op->d.scalararrayop.useOr;
-	bool		strictfunc = op->d.scalararrayop.finfo->fn_strict;
+	struct ScalarArrayOpData *scalararrayop;
+	FunctionCallInfo fcinfo;
+	bool		useOr;
+	bool		strictfunc;
 	ArrayType  *arr;
 	int			nitems;
 	Datum		result;
@@ -2895,6 +3244,23 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	bits8	   *bitmap;
 	int			bitmask;
 
+	switch (ExecEvalStepOp(state, op))
+	{
+		case EEOP_SCALARARRAYOP:
+			scalararrayop = &(op->d.scalararrayop);
+			break;
+		case EEOP_CACHED_SCALARARRAYOP:
+			scalararrayop = &(op->d.cachedexpr.subexpr->scalararrayop);
+			break;
+		default:
+			elog(ERROR, "unknown opcode for evaluation \"scalar op ANY/ALL (array)\"");
+			break;
+	}
+
+	fcinfo = scalararrayop->fcinfo_data;
+	useOr = scalararrayop->useOr;
+	strictfunc = scalararrayop->finfo->fn_strict;
+
 	/*
 	 * If the array is NULL then we return NULL --- it's not very meaningful
 	 * to do anything else, even if the operator isn't strict.
@@ -2933,18 +3299,18 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 	 * We arrange to look up info about the element type only once per series
 	 * of calls, assuming the element type doesn't change underneath us.
 	 */
-	if (op->d.scalararrayop.element_type != ARR_ELEMTYPE(arr))
+	if (scalararrayop->element_type != ARR_ELEMTYPE(arr))
 	{
 		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
-							 &op->d.scalararrayop.typlen,
-							 &op->d.scalararrayop.typbyval,
-							 &op->d.scalararrayop.typalign);
-		op->d.scalararrayop.element_type = ARR_ELEMTYPE(arr);
+							 &scalararrayop->typlen,
+							 &scalararrayop->typbyval,
+							 &scalararrayop->typalign);
+		scalararrayop->element_type = ARR_ELEMTYPE(arr);
 	}
 
-	typlen = op->d.scalararrayop.typlen;
-	typbyval = op->d.scalararrayop.typbyval;
-	typalign = op->d.scalararrayop.typalign;
+	typlen = scalararrayop->typlen;
+	typbyval = scalararrayop->typbyval;
+	typalign = scalararrayop->typalign;
 
 	/* Initialize result appropriately depending on useOr */
 	result = BoolGetDatum(!useOr);
@@ -2984,7 +3350,7 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
 		else
 		{
 			fcinfo->isnull = false;
-			thisresult = (op->d.scalararrayop.fn_addr) (fcinfo);
+			thisresult = (scalararrayop->fn_addr) (fcinfo);
 		}
 
 		/* Combine results per OR or AND semantics */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b93b4fc..a322255 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -378,7 +378,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 				set_subquery_pathlist(root, rel, rti, rte);
 				break;
 			case RTE_FUNCTION:
-				set_function_size_estimates(root, rel);
+				{
+					rel->baserestrictinfo = replace_qual_cached_expressions(
+						rel->baserestrictinfo);
+					set_function_size_estimates(root, rel);
+				}
 				break;
 			case RTE_TABLEFUNC:
 				set_tablefunc_size_estimates(root, rel);
@@ -517,6 +521,9 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	 */
 	check_index_predicates(root, rel);
 
+	rel->baserestrictinfo = replace_qual_cached_expressions(
+		rel->baserestrictinfo);
+
 	/* Mark rel with estimated output rows, width, etc */
 	set_baserel_size_estimates(root, rel);
 }
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 758ddea..fc799f1 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
@@ -825,6 +826,18 @@ clause_selectivity(PlannerInfo *root,
 								jointype,
 								sjinfo);
 	}
+	else if (IsA(clause, CachedExpr))
+	{
+		/*
+		 * Not sure this case is needed, but it can't hurt.
+		 * Calculate selectivity of subexpression.
+		 */
+		s1 = clause_selectivity(root,
+								get_subexpr((CachedExpr *) clause),
+								varRelid,
+								jointype,
+								sjinfo);
+	}
 	else
 	{
 		/*
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 5565736..7a28764 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -38,6 +38,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "optimizer/planner.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 552b73d..7c68d6d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6088,6 +6088,34 @@ get_partitioned_child_rels(PlannerInfo *root, Index rti)
 	return result;
 }
 
+/*
+ * replace_pathtarget_cached_expressions
+ *		Replace cached expresisons in a PathTarget tlist.
+ *
+ * As a notational convenience, returns the same PathTarget pointer passed in.
+ */
+PathTarget *
+replace_pathtarget_cached_expressions(PathTarget *target)
+{
+	target->exprs = (List *) replace_cached_expressions_mutator(
+		(Node *) target->exprs);
+
+	return target;
+}
+
+/*
+ * replace_qual_cached_expressions
+ *		Replace cacehd expressions in a WHERE clause. The input can be either an
+ *		implicitly-ANDed list of boolean expressions, or a list of RestrictInfo
+ *		nodes.
+ */
+List *
+replace_qual_cached_expressions(List *quals)
+{
+	/* No setup needed for tree walk, so away we go */
+	return (List *) replace_cached_expressions_mutator((Node *) quals);
+}
+
 static Node *
 replace_cached_expressions_mutator(Node *node)
 {
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a1dafc8..0c0284a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2758,6 +2758,61 @@ eval_const_expressions_mutator(Node *node,
 				newexpr->location = expr->location;
 				return (Node *) newexpr;
 			}
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+				Node	   *new_subexpr = eval_const_expressions_mutator(
+					get_subexpr(cachedexpr), context);
+				CachedExpr *new_cachedexpr;
+
+				/*
+				 * If unsafe transformations are used cached expression should
+				 * be always simplified.
+				 */
+				if (context->estimate)
+					Assert(IsA(new_subexpr, Const));
+
+				if (IsA(new_subexpr, Const))
+				{
+					/* successfully simplified it */
+					return new_subexpr;	
+				}
+				else
+				{
+					/*
+					 * The expression cannot be simplified any further, so build
+					 * and return a replacement CachedExpr node using the
+					 * possibly-simplified arguments of subexpression.
+					 */
+					new_cachedexpr = makeNode(CachedExpr);
+					new_cachedexpr->subexprtype = cachedexpr->subexprtype;
+					switch (new_cachedexpr->subexprtype)
+					{
+						case CACHED_FUNCEXPR:
+							new_cachedexpr->subexpr.funcexpr = (FuncExpr *)
+								new_subexpr;
+							break;
+						case CACHED_OPEXPR:
+							new_cachedexpr->subexpr.opexpr = (OpExpr *)
+								new_subexpr;
+							break;
+						case CACHED_DISTINCTEXPR:
+							new_cachedexpr->subexpr.distinctexpr =
+								(DistinctExpr *) new_subexpr;
+							break;
+						case CACHED_NULLIFEXPR:
+							new_cachedexpr->subexpr.nullifexpr = (NullIfExpr *)
+								new_subexpr;
+							break;
+						case CACHED_SCALARARRAYOPEXPR:
+							new_cachedexpr->subexpr.saopexpr =
+								(ScalarArrayOpExpr *) new_subexpr;
+							break;
+					}
+
+					return (Node *) new_cachedexpr;
+				}
+			}
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 43b1475..838389d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7720,6 +7720,11 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_CachedExpr:
+			get_rule_expr(get_subexpr((CachedExpr *) node), context,
+						  showimplicit);
+			break;
+
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 86fdb33..b515cc2 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -85,6 +85,15 @@ typedef enum ExprEvalOp
 	EEOP_FUNCEXPR_FUSAGE,
 	EEOP_FUNCEXPR_STRICT_FUSAGE,
 
+	/* evaluate CachedExpr */
+	EEOP_CACHED_FUNCEXPR,
+	EEOP_CACHED_FUNCEXPR_STRICT,
+	EEOP_CACHED_FUNCEXPR_FUSAGE,
+	EEOP_CACHED_FUNCEXPR_STRICT_FUSAGE,
+	EEOP_CACHED_DISTINCT,
+	EEOP_CACHED_NULLIF,
+	EEOP_CACHED_SCALARARRAYOP,
+
 	/*
 	 * Evaluate boolean AND expression, one step per subexpression. FIRST/LAST
 	 * subexpressions are special-cased for performance.  Since AND always has
@@ -217,6 +226,39 @@ typedef enum ExprEvalOp
 } ExprEvalOp;
 
 
+/* Inline data of ExprEvalStep for operations that can be cacheable */
+typedef union CacheableInlineData
+{
+	/*
+	 * For EEOP_FUNCEXPR_* / NULLIF / DISTINCT /
+ 	 *     EEOP_CACHED_FUNCEXPR_* / NULLIF / DISTINCT.
+ 	 */
+	struct FuncData
+	{
+		FmgrInfo   *finfo;		/* function's lookup data */
+		FunctionCallInfo fcinfo_data;	/* arguments etc */
+		/* faster to access without additional indirection: */
+		PGFunction	fn_addr;	/* actual call address */
+		int			nargs;		/* number of arguments */
+	}			func;
+
+	/* for EEOP_SCALARARRAYOP / EEOP_CACHED_SCALARARRAYOP */
+	struct ScalarArrayOpData
+	{
+		/* element_type/typlen/typbyval/typalign are filled at runtime */
+		Oid			element_type;	/* InvalidOid if not yet filled */
+		bool		useOr;		/* use OR or AND semantics? */
+		int16		typlen; 	/* array element type storage info */
+		bool		typbyval;
+		char		typalign;
+		FmgrInfo   *finfo;		/* function's lookup data */
+		FunctionCallInfo fcinfo_data;		/* arguments etc */
+		/* faster to access without additional indirection: */
+		PGFunction	fn_addr;	/* actual call address */
+	}			scalararrayop;
+} CacheableInlineData;
+
+
 typedef struct ExprEvalStep
 {
 	/*
@@ -289,14 +331,18 @@ typedef struct ExprEvalStep
 		}			constval;
 
 		/* for EEOP_FUNCEXPR_* / NULLIF / DISTINCT */
+		struct FuncData func;
+
+		/* for EEOP_CACHED_* */
 		struct
 		{
-			FmgrInfo   *finfo;	/* function's lookup data */
-			FunctionCallInfo fcinfo_data;		/* arguments etc */
-			/* faster to access without additional indirection: */
-			PGFunction	fn_addr;	/* actual call address */
-			int			nargs;	/* number of arguments */
-		}			func;
+			/* cached ExprEvalOp data */
+			CacheableInlineData *subexpr;
+
+			bool		isExecuted;
+			bool		resnull;
+			Datum		resvalue;
+		}			cachedexpr;
 
 		/* for EEOP_BOOL_*_STEP */
 		struct
@@ -500,19 +546,7 @@ typedef struct ExprEvalStep
 		}			convert_rowtype;
 
 		/* for EEOP_SCALARARRAYOP */
-		struct
-		{
-			/* element_type/typlen/typbyval/typalign are filled at runtime */
-			Oid			element_type;	/* InvalidOid if not yet filled */
-			bool		useOr;	/* use OR or AND semantics? */
-			int16		typlen; /* array element type storage info */
-			bool		typbyval;
-			char		typalign;
-			FmgrInfo   *finfo;	/* function's lookup data */
-			FunctionCallInfo fcinfo_data;		/* arguments etc */
-			/* faster to access without additional indirection: */
-			PGFunction	fn_addr;	/* actual call address */
-		}			scalararrayop;
+		struct ScalarArrayOpData scalararrayop;
 
 		/* for EEOP_XMLEXPR */
 		struct
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index f3aaa23..bbadcdd 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -59,4 +59,7 @@ extern bool plan_cluster_use_sort(Oid tableOid, Oid indexOid);
 
 extern List *get_partitioned_child_rels(PlannerInfo *root, Index rti);
 
+extern PathTarget *replace_pathtarget_cached_expressions(PathTarget *target);
+extern List *replace_qual_cached_expressions(List *quals);
+
 #endif   /* PLANNER_H */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index ccb93d8..7488bd2 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -65,8 +65,12 @@ extern void split_pathtarget_at_srfs(PlannerInfo *root,
 						 PathTarget *target, PathTarget *input_target,
 						 List **targets, List **targets_contain_srfs);
 
-/* Convenience macro to get a PathTarget with valid cost/width fields */
+/*
+ * Convenience macro to get a PathTarget with valid cost/width fields and
+ * cached expressions.
+ */
 #define create_pathtarget(root, tlist) \
-	set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
+	set_pathtarget_cost_width(root, replace_pathtarget_cached_expressions( \
+		make_pathtarget_from_tlist(tlist)))
 
 #endif   /* TLIST_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 7a40c99..2e27052 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -6535,6 +6535,16 @@ exec_simple_check_node(Node *node)
 				return TRUE;
 			}
 
+		case T_CachedExpr:
+			{
+				/*
+				 * If CachedExpr will not be initialized by ExecInitCachedExpr
+				 * possibly it will use cached value when it shouldn't (for
+				 * example, snapshot has changed), so return false.
+				 */
+				return FALSE;
+			}
+
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
new file mode 100644
index 0000000..093e6f8
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -0,0 +1,2625 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TABLE two (i integer);
+INSERT INTO two VALUES (1), (2);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_int (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_int (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(i) from two);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE operator === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ====== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE operator ==== (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- Simple functions testing
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- Functions with constant arguments and nested functions testing
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Nested and strict operators testing
+-- (also partly mixed functions and operators testing)
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl() ==== 2) ====== (x_stl() ===== 3) FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== x_stl2_boolean(NULL) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- IS DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(1 IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Nested IS DISTINCT FROM expression testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, x_stl2(NULL))) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(x_stl2(NULL), x_stl2(NULL))) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Nested NULLIF expression testing
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY('{2, 3}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL('{2, 3}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY('{2, NULL}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::int ==== ANY(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL('{2, NULL}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::int ==== ALL(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Nesting "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing (also partly mixed functions and "scalar op ANY/ALL
+-- (array)" / "scalar IN (2 or more values)" expressions testing)
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and operators testing
+-- (most of it was earlier in Nested and strict operators testing)
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and IS DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer),
+  (x_stl_my_integer() IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT equal_booleans_stl_strict(
+  (x_stl() IS DISTINCT FROM 1),
+  (x_stl() IS DISTINCT FROM 2)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF(x_stl_my_integer(), '(1)'::my_integer),
+  NULLIF(x_stl_my_integer(), '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT equal_integers_stl(NULLIF(x_stl(), 1), NULLIF(x_stl(), 2))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ equal_integers_stl 
+--------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing (partly in nesting "scalar op ANY/ALL (array)" /
+-- "scalar IN (2 or more values)" expressions testing)
+SELECT 1 ==== ANY(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY(x_stl_array_int()) FROM generate_series(1, 4) x;
+NOTICE:  s array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL(x_stl_array_int()) FROM generate_series(1, 4) x;
+NOTICE:  s array_int
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed operators and IS DISTINCT FROM expressions testing
+-- should not be precalculated
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed operators and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === 2, TRUE)) FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULLIF(1 ==== 2, TRUE)) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed operators and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT (1 === ANY('{2, 3}')) ====== (1 === ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== ANY('{2, 3}')) ====== (1 ==== ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed IS DISTINCT FROM and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed IS DISTINCT FROM and "scalar op ANY/ALL (array)" / "scalar IN (2 or
+-- more values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  (1 === ANY('{2, 3}')) IS DISTINCT FROM
+  (1 === ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  (1 ==== ANY('{2, 3}')) IS DISTINCT FROM
+  (1 ==== ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed NULLIF and "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === ANY('{2, 3}'), 1 === ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULLIF(1 ==== ANY('{2, 3}'), 1 ==== ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      2
+(1 row)
+
+INSERT INTO two VALUES (3);
+SELECT simple();
+ simple 
+--------
+      3
+(1 row)
+
+ROLLBACK;
+-- Drop tables for testing
+DROP TABLE two;
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 04206c3..f2710b9 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -179,3 +179,4 @@ test: with
 test: xml
 test: event_trigger
 test: stats
+test: precalculate_stable_functions
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
new file mode 100644
index 0000000..a59791d
--- /dev/null
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -0,0 +1,949 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+
+-- Create types and tables for testing
+
+CREATE TYPE my_integer AS (value integer);
+
+CREATE TABLE two (i integer);
+INSERT INTO two VALUES (1), (2);
+
+-- Create volatile functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_array_int (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create stable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_array_int (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(i) from two);
+END
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create immutable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create operators for testing
+
+CREATE operator === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ====== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+CREATE operator ==== (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- Simple functions testing
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+-- WHERE clause testing
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+-- Functions with constant arguments and nested functions testing
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions testing
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions with null arguments testing
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+-- Operators testing
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+
+-- Nested and strict operators testing
+-- (also partly mixed functions and operators testing)
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (x_stl() ==== 2) ====== (x_stl() ===== 3) FROM generate_series(1, 4) x;
+SELECT (1 ==== 2) ====== x_stl2_boolean(NULL) FROM generate_series(1, 4) x;
+
+-- IS DISTINCT FROM expression testing
+
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+
+-- IS DISTINCT FROM expressions with null arguments testing
+
+SELECT x_stl2_boolean(1 IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+
+-- Nested IS DISTINCT FROM expression testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+-- NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- NULLIF expressions with null arguments testing
+
+SELECT x_stl2(NULLIF(1, x_stl2(NULL))) FROM generate_series(1, 4) x;
+
+SELECT x_stl2(NULLIF(x_stl2(NULL), x_stl2(NULL))) FROM generate_series(1, 4) x;
+
+-- Nested NULLIF expression testing
+
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+
+SELECT 1 === ANY('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 === ALL('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT 1 ==== ANY('{2, 3}') FROM generate_series(1, 4) x;
+SELECT 1 ==== ALL('{2, 3}') FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+
+SELECT 1 ==== ANY('{2, NULL}') FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== ANY(NULL)) FROM generate_series(1, 4) x;
+SELECT NULL ==== ANY('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+SELECT NULL ==== ANY('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL::int ==== ANY(NULL)) FROM generate_series(1, 4) x;
+
+SELECT 1 ==== ALL('{2, NULL}') FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== ALL(NULL)) FROM generate_series(1, 4) x;
+SELECT NULL ==== ALL('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+SELECT NULL ==== ALL('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL::int ==== ALL(NULL)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM generate_series(1, 4) x;
+
+-- Nesting "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing (also partly mixed functions and "scalar op ANY/ALL
+-- (array)" / "scalar IN (2 or more values)" expressions testing)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and operators testing
+-- (most of it was earlier in Nested and strict operators testing)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+-- Mixed functions and IS DISTINCT FROM expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer),
+  (x_stl_my_integer() IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT equal_booleans_stl_strict(
+  (x_stl() IS DISTINCT FROM 1),
+  (x_stl() IS DISTINCT FROM 2)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and NULLIF expressions testing
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF(x_stl_my_integer(), '(1)'::my_integer),
+  NULLIF(x_stl_my_integer(), '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT equal_integers_stl(NULLIF(x_stl(), 1), NULLIF(x_stl(), 2))
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing (partly in nesting "scalar op ANY/ALL (array)" /
+-- "scalar IN (2 or more values)" expressions testing)
+
+SELECT 1 ==== ANY(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== ALL(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT 1 ==== ANY(x_stl_array_int()) FROM generate_series(1, 4) x;
+SELECT 1 ==== ALL(x_stl_array_int()) FROM generate_series(1, 4) x;
+
+-- Mixed operators and IS DISTINCT FROM expressions testing
+
+-- should not be precalculated
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+
+-- Mixed operators and NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === 2, TRUE)) FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(NULLIF(1 ==== 2, TRUE)) FROM generate_series(1, 4) x;
+
+-- Mixed operators and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT (1 === ANY('{2, 3}')) ====== (1 === ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT (1 ==== ANY('{2, 3}')) ====== (1 ==== ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- Mixed IS DISTINCT FROM and NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed IS DISTINCT FROM and "scalar op ANY/ALL (array)" / "scalar IN (2 or
+-- more values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  (1 === ANY('{2, 3}')) IS DISTINCT FROM
+  (1 === ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  (1 ==== ANY('{2, 3}')) IS DISTINCT FROM
+  (1 ==== ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed NULLIF and "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === ANY('{2, 3}'), 1 === ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(NULLIF(1 ==== ANY('{2, 3}'), 1 ==== ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- Tracking functions testing
+
+SET track_functions TO 'all';
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+SET track_functions TO DEFAULT;
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+
+BEGIN;
+SELECT simple();
+INSERT INTO two VALUES (3);
+SELECT simple();
+ROLLBACK;
+
+-- Drop tables for testing
+
+DROP TABLE two;
-- 
1.9.1

0003-Precalculate-stable-functions-costs-v2.patchtext/x-diff; name=0003-Precalculate-stable-functions-costs-v2.patchDownload
From 823e2aa5477342875b73b9941bdc9ed28d3ebe01 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 15 May 2017 16:05:38 +0300
Subject: [PATCH 3/3] Precalculate stable functions, costs v2

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:
- cost changes for cached expressions (according to their behaviour)
---
 src/backend/optimizer/path/costsize.c | 89 ++++++++++++++++++++++++++---------
 1 file changed, 67 insertions(+), 22 deletions(-)

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 52643d0..505772a 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -140,6 +140,7 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root,
 			   PathKey *pathkey);
 static void cost_rescan(PlannerInfo *root, Path *path,
 			Cost *rescan_startup_cost, Cost *rescan_total_cost);
+static double cost_eval_cacheable_expr_per_tuple(Node *node);
 static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
 static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
 						  ParamPathInfo *param_info,
@@ -3464,6 +3465,59 @@ cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root)
 	*cost = context.total;
 }
 
+/*
+ * cost_eval_cacheable_expr_per_tuple
+ *		Evaluate per tuple cost for expressions that can be cacheable.
+ *
+ * This function was created to not duplicate code for some expression and
+ * cached some expression.
+ */
+static double
+cost_eval_cacheable_expr_per_tuple(Node *node)
+{
+	double result;
+
+	/*
+	 * For each operator or function node in the given tree, we charge the
+	 * estimated execution cost given by pg_proc.procost (remember to multiply
+	 * this by cpu_operator_cost).
+	 */
+	if (IsA(node, FuncExpr))
+	{
+		result = get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+	}
+	else if (IsA(node, OpExpr) ||
+			 IsA(node, DistinctExpr) ||
+			 IsA(node, NullIfExpr))
+	{
+		OpExpr     *opexpr = (OpExpr *) node;
+
+		/* rely on struct equivalence to treat these all alike */
+		set_opfuncid(opexpr);
+
+		result = get_func_cost(opexpr->opfuncid) * cpu_operator_cost;
+	}
+	else if (IsA(node, ScalarArrayOpExpr))
+	{
+		/*
+		 * Estimate that the operator will be applied to about half of the
+		 * array elements before the answer is determined.
+		 */
+		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
+		Node	   *arraynode = (Node *) lsecond(saop->args);
+
+		set_sa_opfuncid(saop);
+		result = get_func_cost(saop->opfuncid) * cpu_operator_cost *
+			estimate_array_length(arraynode) * 0.5;
+	}
+	else
+	{
+		elog(ERROR, "non cacheable expression node type: %d", (int) nodeTag(node));
+	}
+
+	return result;
+}
+
 static bool
 cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 {
@@ -3537,32 +3591,23 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 	 * moreover, since our rowcount estimates for functions tend to be pretty
 	 * phony, the results would also be pretty phony.
 	 */
-	if (IsA(node, FuncExpr))
+	if (IsA(node, FuncExpr) ||
+		IsA(node, OpExpr) ||
+		IsA(node, DistinctExpr) ||
+		IsA(node, NullIfExpr) ||
+		IsA(node, ScalarArrayOpExpr))
 	{
-		context->total.per_tuple +=
-			get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+		context->total.per_tuple += cost_eval_cacheable_expr_per_tuple(node);
 	}
-	else if (IsA(node, OpExpr) ||
-			 IsA(node, DistinctExpr) ||
-			 IsA(node, NullIfExpr))
-	{
-		/* rely on struct equivalence to treat these all alike */
-		set_opfuncid((OpExpr *) node);
-		context->total.per_tuple +=
-			get_func_cost(((OpExpr *) node)->opfuncid) * cpu_operator_cost;
-	}
-	else if (IsA(node, ScalarArrayOpExpr))
-	{
+	else if (IsA(node, CachedExpr))
+	{	
 		/*
-		 * Estimate that the operator will be applied to about half of the
-		 * array elements before the answer is determined.
+		 * Calculate subexpression cost per tuple as usual and add it to startup
+		 * cost (because subexpression will be executed only once for all
+		 * tuples).
 		 */
-		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
-		Node	   *arraynode = (Node *) lsecond(saop->args);
-
-		set_sa_opfuncid(saop);
-		context->total.per_tuple += get_func_cost(saop->opfuncid) *
-			cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
+		context->total.startup += cost_eval_cacheable_expr_per_tuple(
+			get_subexpr((CachedExpr *) node));
 	}
 	else if (IsA(node, Aggref) ||
 			 IsA(node, WindowFunc))
-- 
1.9.1

#7Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Marina Polyakova (#6)
1 attachment(s)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Here's v2 of the patches. Changes from v1:

And here there's v3 of planning and execution: common executor steps for
all types of cached expression.

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

0002-Precalculate-stable-functions-planning-and-execution-v3.patchtext/x-diff; charset=us-ascii; name=0002-Precalculate-stable-functions-planning-and-execution-v3.patchDownload
From 5e89221251670526eb2b5750868ac73eee48f10b Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 15 May 2017 15:31:21 +0300
Subject: [PATCH 2/3] Precalculate stable functions, planning and execution v3

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:
- replacement nonvolatile functions and operators by appropriate cached
expressions
- planning and execution cached expressions
- regression tests
---
 src/backend/executor/execExpr.c                    |   37 +
 src/backend/executor/execExprInterp.c              |   37 +
 src/backend/optimizer/path/allpaths.c              |    9 +-
 src/backend/optimizer/path/clausesel.c             |   13 +
 src/backend/optimizer/plan/planagg.c               |    1 +
 src/backend/optimizer/plan/planner.c               |   28 +
 src/backend/optimizer/util/clauses.c               |   55 +
 src/backend/utils/adt/ruleutils.c                  |    5 +
 src/include/executor/execExpr.h                    |   19 +
 src/include/optimizer/planner.h                    |    3 +
 src/include/optimizer/tlist.h                      |    8 +-
 src/pl/plpgsql/src/pl_exec.c                       |   10 +
 .../expected/precalculate_stable_functions.out     | 2625 ++++++++++++++++++++
 src/test/regress/serial_schedule                   |    1 +
 .../regress/sql/precalculate_stable_functions.sql  |  949 +++++++
 15 files changed, 3797 insertions(+), 3 deletions(-)
 create mode 100644 src/test/regress/expected/precalculate_stable_functions.out
 create mode 100644 src/test/regress/sql/precalculate_stable_functions.sql

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 5a34a46..3c2068d 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -865,6 +865,43 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
 				break;
 			}
 
+		case T_CachedExpr:
+			{
+				int 		adjust_jump;
+
+				/*
+				 * Allocate and fill scratch memory used by all steps of
+				 * CachedExpr evaluation.
+				 */
+				scratch.d.cachedexpr.isExecuted = (bool *) palloc(sizeof(bool));
+				scratch.d.cachedexpr.resnull = (bool *) palloc(sizeof(bool));
+				scratch.d.cachedexpr.resvalue = (Datum *) palloc(sizeof(Datum));
+
+				*scratch.d.cachedexpr.isExecuted = false;
+				*scratch.d.cachedexpr.resnull = false;
+				*scratch.d.cachedexpr.resvalue = (Datum) 0;
+				scratch.d.cachedexpr.jumpdone = -1;
+
+				/* add EEOP_CACHEDEXPR_IF_CACHED step */
+				scratch.opcode = EEOP_CACHEDEXPR_IF_CACHED;
+				ExprEvalPushStep(state, &scratch);
+				adjust_jump = state->steps_len - 1;
+
+				/* add subexpression steps */
+				ExecInitExprRec((Expr *) get_subexpr((CachedExpr *) node),
+								parent, state, resv, resnull);
+
+				/* add EEOP_CACHEDEXPR_SUBEXPR_END step */
+				scratch.opcode = EEOP_CACHEDEXPR_SUBEXPR_END;
+				ExprEvalPushStep(state, &scratch);
+
+				/* adjust jump target */
+				state->steps[adjust_jump].d.cachedexpr.jumpdone =
+					state->steps_len;
+
+				break;
+			}
+
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index fed0052..ac7b7f8 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -279,6 +279,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 	TupleTableSlot *innerslot;
 	TupleTableSlot *outerslot;
 	TupleTableSlot *scanslot;
+	MemoryContext oldContext;	/* for EEOP_CACHEDEXPR_* */
 
 	/*
 	 * This array has to be in the same order as enum ExprEvalOp.
@@ -309,6 +310,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_FUNCEXPR_STRICT,
 		&&CASE_EEOP_FUNCEXPR_FUSAGE,
 		&&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE,
+		&&CASE_EEOP_CACHEDEXPR_IF_CACHED,
+		&&CASE_EEOP_CACHEDEXPR_SUBEXPR_END,
 		&&CASE_EEOP_BOOL_AND_STEP_FIRST,
 		&&CASE_EEOP_BOOL_AND_STEP,
 		&&CASE_EEOP_BOOL_AND_STEP_LAST,
@@ -721,6 +724,40 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_CACHEDEXPR_IF_CACHED)
+		{
+			if (*op->d.cachedexpr.isExecuted)
+			{
+				/* use saved result and skip subexpression evaluation */
+				*op->resnull = *op->d.cachedexpr.resnull;
+				if (!(*op->resnull))
+					*op->resvalue = *op->d.cachedexpr.resvalue;
+
+				EEO_JUMP(op->d.cachedexpr.jumpdone);
+			}
+
+			/*
+			 * Switch per-query memory context for subexpression evaluation.
+			 * It is necessary to save result between all tuples.
+			 */
+			oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHEDEXPR_SUBEXPR_END)
+		{
+			/* save result and switch memory context back */
+			*op->d.cachedexpr.resnull = *op->resnull;
+			if (!(*op->resnull))
+				*op->d.cachedexpr.resvalue = *op->resvalue;
+			*op->d.cachedexpr.isExecuted = true;
+
+			MemoryContextSwitchTo(oldContext);
+
+			EEO_NEXT();
+		}
+
 		/*
 		 * If any of its clauses is FALSE, an AND's result is FALSE regardless
 		 * of the states of the rest of the clauses, so we can stop evaluating
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b93b4fc..a322255 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -378,7 +378,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 				set_subquery_pathlist(root, rel, rti, rte);
 				break;
 			case RTE_FUNCTION:
-				set_function_size_estimates(root, rel);
+				{
+					rel->baserestrictinfo = replace_qual_cached_expressions(
+						rel->baserestrictinfo);
+					set_function_size_estimates(root, rel);
+				}
 				break;
 			case RTE_TABLEFUNC:
 				set_tablefunc_size_estimates(root, rel);
@@ -517,6 +521,9 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	 */
 	check_index_predicates(root, rel);
 
+	rel->baserestrictinfo = replace_qual_cached_expressions(
+		rel->baserestrictinfo);
+
 	/* Mark rel with estimated output rows, width, etc */
 	set_baserel_size_estimates(root, rel);
 }
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 758ddea..fc799f1 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
@@ -825,6 +826,18 @@ clause_selectivity(PlannerInfo *root,
 								jointype,
 								sjinfo);
 	}
+	else if (IsA(clause, CachedExpr))
+	{
+		/*
+		 * Not sure this case is needed, but it can't hurt.
+		 * Calculate selectivity of subexpression.
+		 */
+		s1 = clause_selectivity(root,
+								get_subexpr((CachedExpr *) clause),
+								varRelid,
+								jointype,
+								sjinfo);
+	}
 	else
 	{
 		/*
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 5565736..7a28764 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -38,6 +38,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "optimizer/planner.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 552b73d..7c68d6d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6088,6 +6088,34 @@ get_partitioned_child_rels(PlannerInfo *root, Index rti)
 	return result;
 }
 
+/*
+ * replace_pathtarget_cached_expressions
+ *		Replace cached expresisons in a PathTarget tlist.
+ *
+ * As a notational convenience, returns the same PathTarget pointer passed in.
+ */
+PathTarget *
+replace_pathtarget_cached_expressions(PathTarget *target)
+{
+	target->exprs = (List *) replace_cached_expressions_mutator(
+		(Node *) target->exprs);
+
+	return target;
+}
+
+/*
+ * replace_qual_cached_expressions
+ *		Replace cacehd expressions in a WHERE clause. The input can be either an
+ *		implicitly-ANDed list of boolean expressions, or a list of RestrictInfo
+ *		nodes.
+ */
+List *
+replace_qual_cached_expressions(List *quals)
+{
+	/* No setup needed for tree walk, so away we go */
+	return (List *) replace_cached_expressions_mutator((Node *) quals);
+}
+
 static Node *
 replace_cached_expressions_mutator(Node *node)
 {
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a1dafc8..0c0284a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2758,6 +2758,61 @@ eval_const_expressions_mutator(Node *node,
 				newexpr->location = expr->location;
 				return (Node *) newexpr;
 			}
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+				Node	   *new_subexpr = eval_const_expressions_mutator(
+					get_subexpr(cachedexpr), context);
+				CachedExpr *new_cachedexpr;
+
+				/*
+				 * If unsafe transformations are used cached expression should
+				 * be always simplified.
+				 */
+				if (context->estimate)
+					Assert(IsA(new_subexpr, Const));
+
+				if (IsA(new_subexpr, Const))
+				{
+					/* successfully simplified it */
+					return new_subexpr;	
+				}
+				else
+				{
+					/*
+					 * The expression cannot be simplified any further, so build
+					 * and return a replacement CachedExpr node using the
+					 * possibly-simplified arguments of subexpression.
+					 */
+					new_cachedexpr = makeNode(CachedExpr);
+					new_cachedexpr->subexprtype = cachedexpr->subexprtype;
+					switch (new_cachedexpr->subexprtype)
+					{
+						case CACHED_FUNCEXPR:
+							new_cachedexpr->subexpr.funcexpr = (FuncExpr *)
+								new_subexpr;
+							break;
+						case CACHED_OPEXPR:
+							new_cachedexpr->subexpr.opexpr = (OpExpr *)
+								new_subexpr;
+							break;
+						case CACHED_DISTINCTEXPR:
+							new_cachedexpr->subexpr.distinctexpr =
+								(DistinctExpr *) new_subexpr;
+							break;
+						case CACHED_NULLIFEXPR:
+							new_cachedexpr->subexpr.nullifexpr = (NullIfExpr *)
+								new_subexpr;
+							break;
+						case CACHED_SCALARARRAYOPEXPR:
+							new_cachedexpr->subexpr.saopexpr =
+								(ScalarArrayOpExpr *) new_subexpr;
+							break;
+					}
+
+					return (Node *) new_cachedexpr;
+				}
+			}
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 43b1475..838389d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7720,6 +7720,11 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_CachedExpr:
+			get_rule_expr(get_subexpr((CachedExpr *) node), context,
+						  showimplicit);
+			break;
+
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 86fdb33..650c8af 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -86,6 +86,16 @@ typedef enum ExprEvalOp
 	EEOP_FUNCEXPR_STRICT_FUSAGE,
 
 	/*
+	 * Evaluate CachedExpr.  EEOP_CACHEDEXPR_IF_CACHED is used before
+	 * subexpression evaluation (if subexpression was evaluated use cached value
+	 * and jump to next state or get prepared to subexpression evaluation
+	 * otherwise).  EEOP_CACHEDEXPR_SUBEXPR_END is used after subexpression
+	 * evaluation for caching its result.
+	 */
+	EEOP_CACHEDEXPR_IF_CACHED,
+	EEOP_CACHEDEXPR_SUBEXPR_END,
+
+	/*
 	 * Evaluate boolean AND expression, one step per subexpression. FIRST/LAST
 	 * subexpressions are special-cased for performance.  Since AND always has
 	 * at least two subexpressions, FIRST and LAST never apply to the same
@@ -298,6 +308,15 @@ typedef struct ExprEvalStep
 			int			nargs;	/* number of arguments */
 		}			func;
 
+		/* for EEOP_CACHEDEXPR_* */
+		struct
+		{
+			bool	   *isExecuted;
+			bool	   *resnull;
+			Datum	   *resvalue;
+			int			jumpdone;	/* jump here if result determined */
+		}			cachedexpr;
+
 		/* for EEOP_BOOL_*_STEP */
 		struct
 		{
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index f3aaa23..bbadcdd 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -59,4 +59,7 @@ extern bool plan_cluster_use_sort(Oid tableOid, Oid indexOid);
 
 extern List *get_partitioned_child_rels(PlannerInfo *root, Index rti);
 
+extern PathTarget *replace_pathtarget_cached_expressions(PathTarget *target);
+extern List *replace_qual_cached_expressions(List *quals);
+
 #endif   /* PLANNER_H */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index ccb93d8..7488bd2 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -65,8 +65,12 @@ extern void split_pathtarget_at_srfs(PlannerInfo *root,
 						 PathTarget *target, PathTarget *input_target,
 						 List **targets, List **targets_contain_srfs);
 
-/* Convenience macro to get a PathTarget with valid cost/width fields */
+/*
+ * Convenience macro to get a PathTarget with valid cost/width fields and
+ * cached expressions.
+ */
 #define create_pathtarget(root, tlist) \
-	set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
+	set_pathtarget_cost_width(root, replace_pathtarget_cached_expressions( \
+		make_pathtarget_from_tlist(tlist)))
 
 #endif   /* TLIST_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 7a40c99..2e27052 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -6535,6 +6535,16 @@ exec_simple_check_node(Node *node)
 				return TRUE;
 			}
 
+		case T_CachedExpr:
+			{
+				/*
+				 * If CachedExpr will not be initialized by ExecInitCachedExpr
+				 * possibly it will use cached value when it shouldn't (for
+				 * example, snapshot has changed), so return false.
+				 */
+				return FALSE;
+			}
+
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
new file mode 100644
index 0000000..093e6f8
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -0,0 +1,2625 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TABLE two (i integer);
+INSERT INTO two VALUES (1), (2);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_int (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_int (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(i) from two);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE operator === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ====== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE operator ==== (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- Simple functions testing
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- Functions with constant arguments and nested functions testing
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Nested and strict operators testing
+-- (also partly mixed functions and operators testing)
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl() ==== 2) ====== (x_stl() ===== 3) FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== x_stl2_boolean(NULL) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- IS DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(1 IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Nested IS DISTINCT FROM expression testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, x_stl2(NULL))) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(x_stl2(NULL), x_stl2(NULL))) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Nested NULLIF expression testing
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY('{2, 3}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL('{2, 3}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY('{2, NULL}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::int ==== ANY(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL('{2, NULL}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::int ==== ALL(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Nesting "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing (also partly mixed functions and "scalar op ANY/ALL
+-- (array)" / "scalar IN (2 or more values)" expressions testing)
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and operators testing
+-- (most of it was earlier in Nested and strict operators testing)
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and IS DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer),
+  (x_stl_my_integer() IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT equal_booleans_stl_strict(
+  (x_stl() IS DISTINCT FROM 1),
+  (x_stl() IS DISTINCT FROM 2)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF(x_stl_my_integer(), '(1)'::my_integer),
+  NULLIF(x_stl_my_integer(), '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT equal_integers_stl(NULLIF(x_stl(), 1), NULLIF(x_stl(), 2))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ equal_integers_stl 
+--------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing (partly in nesting "scalar op ANY/ALL (array)" /
+-- "scalar IN (2 or more values)" expressions testing)
+SELECT 1 ==== ANY(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY(x_stl_array_int()) FROM generate_series(1, 4) x;
+NOTICE:  s array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL(x_stl_array_int()) FROM generate_series(1, 4) x;
+NOTICE:  s array_int
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed operators and IS DISTINCT FROM expressions testing
+-- should not be precalculated
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed operators and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === 2, TRUE)) FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULLIF(1 ==== 2, TRUE)) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed operators and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT (1 === ANY('{2, 3}')) ====== (1 === ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== ANY('{2, 3}')) ====== (1 ==== ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed IS DISTINCT FROM and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed IS DISTINCT FROM and "scalar op ANY/ALL (array)" / "scalar IN (2 or
+-- more values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  (1 === ANY('{2, 3}')) IS DISTINCT FROM
+  (1 === ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  (1 ==== ANY('{2, 3}')) IS DISTINCT FROM
+  (1 ==== ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed NULLIF and "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === ANY('{2, 3}'), 1 === ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULLIF(1 ==== ANY('{2, 3}'), 1 ==== ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      2
+(1 row)
+
+INSERT INTO two VALUES (3);
+SELECT simple();
+ simple 
+--------
+      3
+(1 row)
+
+ROLLBACK;
+-- Drop tables for testing
+DROP TABLE two;
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 04206c3..f2710b9 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -179,3 +179,4 @@ test: with
 test: xml
 test: event_trigger
 test: stats
+test: precalculate_stable_functions
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
new file mode 100644
index 0000000..a59791d
--- /dev/null
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -0,0 +1,949 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+
+-- Create types and tables for testing
+
+CREATE TYPE my_integer AS (value integer);
+
+CREATE TABLE two (i integer);
+INSERT INTO two VALUES (1), (2);
+
+-- Create volatile functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_array_int (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create stable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_array_int (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(i) from two);
+END
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create immutable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create operators for testing
+
+CREATE operator === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ====== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+CREATE operator ==== (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- Simple functions testing
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+-- WHERE clause testing
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+-- Functions with constant arguments and nested functions testing
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions testing
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions with null arguments testing
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+-- Operators testing
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+
+-- Nested and strict operators testing
+-- (also partly mixed functions and operators testing)
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (x_stl() ==== 2) ====== (x_stl() ===== 3) FROM generate_series(1, 4) x;
+SELECT (1 ==== 2) ====== x_stl2_boolean(NULL) FROM generate_series(1, 4) x;
+
+-- IS DISTINCT FROM expression testing
+
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+
+-- IS DISTINCT FROM expressions with null arguments testing
+
+SELECT x_stl2_boolean(1 IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+
+-- Nested IS DISTINCT FROM expression testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+-- NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- NULLIF expressions with null arguments testing
+
+SELECT x_stl2(NULLIF(1, x_stl2(NULL))) FROM generate_series(1, 4) x;
+
+SELECT x_stl2(NULLIF(x_stl2(NULL), x_stl2(NULL))) FROM generate_series(1, 4) x;
+
+-- Nested NULLIF expression testing
+
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+
+SELECT 1 === ANY('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 === ALL('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT 1 ==== ANY('{2, 3}') FROM generate_series(1, 4) x;
+SELECT 1 ==== ALL('{2, 3}') FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+
+SELECT 1 ==== ANY('{2, NULL}') FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== ANY(NULL)) FROM generate_series(1, 4) x;
+SELECT NULL ==== ANY('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+SELECT NULL ==== ANY('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL::int ==== ANY(NULL)) FROM generate_series(1, 4) x;
+
+SELECT 1 ==== ALL('{2, NULL}') FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== ALL(NULL)) FROM generate_series(1, 4) x;
+SELECT NULL ==== ALL('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+SELECT NULL ==== ALL('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL::int ==== ALL(NULL)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM generate_series(1, 4) x;
+
+-- Nesting "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing (also partly mixed functions and "scalar op ANY/ALL
+-- (array)" / "scalar IN (2 or more values)" expressions testing)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and operators testing
+-- (most of it was earlier in Nested and strict operators testing)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+-- Mixed functions and IS DISTINCT FROM expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer),
+  (x_stl_my_integer() IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT equal_booleans_stl_strict(
+  (x_stl() IS DISTINCT FROM 1),
+  (x_stl() IS DISTINCT FROM 2)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and NULLIF expressions testing
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF(x_stl_my_integer(), '(1)'::my_integer),
+  NULLIF(x_stl_my_integer(), '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT equal_integers_stl(NULLIF(x_stl(), 1), NULLIF(x_stl(), 2))
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing (partly in nesting "scalar op ANY/ALL (array)" /
+-- "scalar IN (2 or more values)" expressions testing)
+
+SELECT 1 ==== ANY(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== ALL(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT 1 ==== ANY(x_stl_array_int()) FROM generate_series(1, 4) x;
+SELECT 1 ==== ALL(x_stl_array_int()) FROM generate_series(1, 4) x;
+
+-- Mixed operators and IS DISTINCT FROM expressions testing
+
+-- should not be precalculated
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+
+-- Mixed operators and NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === 2, TRUE)) FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(NULLIF(1 ==== 2, TRUE)) FROM generate_series(1, 4) x;
+
+-- Mixed operators and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT (1 === ANY('{2, 3}')) ====== (1 === ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT (1 ==== ANY('{2, 3}')) ====== (1 ==== ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- Mixed IS DISTINCT FROM and NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed IS DISTINCT FROM and "scalar op ANY/ALL (array)" / "scalar IN (2 or
+-- more values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  (1 === ANY('{2, 3}')) IS DISTINCT FROM
+  (1 === ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  (1 ==== ANY('{2, 3}')) IS DISTINCT FROM
+  (1 ==== ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed NULLIF and "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === ANY('{2, 3}'), 1 === ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(NULLIF(1 ==== ANY('{2, 3}'), 1 ==== ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- Tracking functions testing
+
+SET track_functions TO 'all';
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+SET track_functions TO DEFAULT;
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+
+BEGIN;
+SELECT simple();
+INSERT INTO two VALUES (3);
+SELECT simple();
+ROLLBACK;
+
+-- Drop tables for testing
+
+DROP TABLE two;
-- 
1.9.1

#8Andres Freund
andres@anarazel.de
In reply to: Marina Polyakova (#7)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hi,

On 2017-05-18 19:00:09 +0300, Marina Polyakova wrote:

Here's v2 of the patches. Changes from v1:

And here there's v3 of planning and execution: common executor steps for all
types of cached expression.

I've not followed this thread, but just scanned this quickly because it
affects execExpr* stuff.

+		case T_CachedExpr:
+			{
+				int 		adjust_jump;
+
+				/*
+				 * Allocate and fill scratch memory used by all steps of
+				 * CachedExpr evaluation.
+				 */
+				scratch.d.cachedexpr.isExecuted = (bool *) palloc(sizeof(bool));
+				scratch.d.cachedexpr.resnull = (bool *) palloc(sizeof(bool));
+				scratch.d.cachedexpr.resvalue = (Datum *) palloc(sizeof(Datum));
+
+				*scratch.d.cachedexpr.isExecuted = false;
+				*scratch.d.cachedexpr.resnull = false;
+				*scratch.d.cachedexpr.resvalue = (Datum) 0;

Looks like having something like struct CachedExprState would be better,
than these separate allocations? That also allows to aleviate some size
concerns when adding new fields (see below)

@@ -279,6 +279,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
TupleTableSlot *innerslot;
TupleTableSlot *outerslot;
TupleTableSlot *scanslot;
+	MemoryContext oldContext;	/* for EEOP_CACHEDEXPR_* */

I'd rather not have this on function scope - a) the stack pressure in
ExecInterpExpr is quite noticeable in profiles already b) this is going
to trigger warnings because of unused vars, because the compiler doesn't
understand that EEOP_CACHEDEXPR_IF_CACHED always follows
EEOP_CACHEDEXPR_SUBEXPR_END.

How about instead storing oldcontext in the expression itself?

I'm also not sure how acceptable it is to just assume it's ok to leave
stuff in per_query_memory, in some cases that could prove to be
problematic.

+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+				Node	   *new_subexpr = eval_const_expressions_mutator(
+					get_subexpr(cachedexpr), context);
+				CachedExpr *new_cachedexpr;
+
+				/*
+				 * If unsafe transformations are used cached expression should
+				 * be always simplified.
+				 */
+				if (context->estimate)
+					Assert(IsA(new_subexpr, Const));
+
+				if (IsA(new_subexpr, Const))
+				{
+					/* successfully simplified it */
+					return new_subexpr;	
+				}
+				else
+				{
+					/*
+					 * The expression cannot be simplified any further, so build
+					 * and return a replacement CachedExpr node using the
+					 * possibly-simplified arguments of subexpression.
+					 */

Is this actually a meaningful path? Shouldn't always have done const
evaluation before adding CachedExpr's?

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Andres Freund (#8)
3 attachment(s)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hi,

Hello!

I've not followed this thread, but just scanned this quickly because it
affects execExpr* stuff.

Thank you very much for your comments! Thanks to them I have made v4 of
the patches (as in the previous one, only planning and execution part is
changed).

Looks like having something like struct CachedExprState would be
better,
than these separate allocations? That also allows to aleviate some
size
concerns when adding new fields (see below)

I'd rather not have this on function scope - a) the stack pressure in
ExecInterpExpr is quite noticeable in profiles already b) this is going
to trigger warnings because of unused vars, because the compiler
doesn't
understand that EEOP_CACHEDEXPR_IF_CACHED always follows
EEOP_CACHEDEXPR_SUBEXPR_END.

How about instead storing oldcontext in the expression itself?

Thanks, in new version I did all of it in this way.

I'm also not sure how acceptable it is to just assume it's ok to leave
stuff in per_query_memory, in some cases that could prove to be
problematic.

I agree with you and in new version context is changed only for copying
datum of result value (if it's a pointer, its data should be allocated
in per_query_memory, or we will lost it for next tuples).

Is this actually a meaningful path? Shouldn't always have done const
evaluation before adding CachedExpr's?

eval_const_expressions_mutator is used several times, and one of them in
functions for selectivity evaluation (set_baserel_size_estimates ->
clauselist_selectivity -> clause_selectivity -> restriction_selectivity
-> ... -> get_restriction_variable -> estimate_expression_value ->
eval_const_expressions_mutator). In set_baserel_size_estimates function
right after selectivity evaluation there's costs evaluation and cached
expressions should be replaced before costs. I'm not sure that it is a
good idea to insert cached expressions replacement in
set_baserel_size_estimates, because in comments to it it's said "The
rel's targetlist and restrictinfo list must have been constructed
already, and rel->tuples must be set." and its file costsize.c is
entitled as "Routines to compute (and set) relation sizes and path
costs". So I have inserted cached expressions replacement just before it
(but I'm not sure that I have seen all places where it should be
inserted). What do you think about all of this?

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v4-0001-Precalculate-stable-functions-infrastructure.patchtext/x-diff; name=v4-0001-Precalculate-stable-functions-infrastructure.patchDownload
From 02262b9f3a3215d3884b6ac188bafa6517ac543d Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 15 May 2017 14:24:36 +0300
Subject: [PATCH v4 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 functions' and operators' expressions by
appropriate cached expressions.
---
 src/backend/nodes/copyfuncs.c        |  31 +++++
 src/backend/nodes/equalfuncs.c       |  31 +++++
 src/backend/nodes/nodeFuncs.c        | 151 ++++++++++++++++++++
 src/backend/nodes/outfuncs.c         |  56 ++++++++
 src/backend/nodes/readfuncs.c        |  48 +++++++
 src/backend/optimizer/plan/planner.c | 259 +++++++++++++++++++++++++++++++++++
 src/include/nodes/nodeFuncs.h        |   1 +
 src/include/nodes/nodes.h            |   1 +
 src/include/nodes/primnodes.h        |  38 +++++
 9 files changed, 616 insertions(+)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6ad3844..f9f69a1 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1527,6 +1527,34 @@ _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;
+		case CACHED_DISTINCTEXPR:
+			COPY_NODE_FIELD(subexpr.distinctexpr);
+			break;
+		case CACHED_NULLIFEXPR:
+			COPY_NODE_FIELD(subexpr.nullifexpr);
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			COPY_NODE_FIELD(subexpr.saopexpr);
+			break;
+	}
+
+	return newnode;
+}
+
 /*
  * _copyScalarArrayOpExpr
  */
@@ -4867,6 +4895,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 c9a8c34..8863759 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -384,6 +384,34 @@ _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;
+		case CACHED_DISTINCTEXPR:
+			COMPARE_NODE_FIELD(subexpr.distinctexpr);
+			break;
+		case CACHED_NULLIFEXPR:
+			COMPARE_NODE_FIELD(subexpr.nullifexpr);
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			COMPARE_NODE_FIELD(subexpr.saopexpr);
+			break;
+	}
+
+	return true;
+}
+
+static bool
 _equalScalarArrayOpExpr(const ScalarArrayOpExpr *a, const ScalarArrayOpExpr *b)
 {
 	COMPARE_SCALAR_FIELD(opno);
@@ -3031,6 +3059,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..e3dd576 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,54 @@ 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;
+					case CACHED_DISTINCTEXPR:
+						newnode->subexpr.distinctexpr = (DistinctExpr *)
+							expression_tree_mutator(
+								(Node *) expr->subexpr.distinctexpr, mutator,
+								context);
+						break;
+					case CACHED_NULLIFEXPR:
+						newnode->subexpr.nullifexpr = (NullIfExpr *)
+							expression_tree_mutator(
+								(Node *) expr->subexpr.nullifexpr, mutator,
+								context);
+						break;
+					case CACHED_SCALARARRAYOPEXPR:
+						newnode->subexpr.saopexpr = (ScalarArrayOpExpr *)
+							expression_tree_mutator(
+								(Node *) expr->subexpr.saopexpr, mutator,
+								context);
+						break;
+				}
+
+				return (Node *) newnode;
+			}
+			break;
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
@@ -3838,3 +3935,57 @@ 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;
+		case CACHED_DISTINCTEXPR:
+			return (const Node *) cachedexpr->subexpr.distinctexpr;
+		case CACHED_NULLIFEXPR:
+			return (const Node *) cachedexpr->subexpr.nullifexpr;
+		case CACHED_SCALARARRAYOPEXPR:
+			return (const Node *) cachedexpr->subexpr.saopexpr;
+	}
+
+	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;
+		case CACHED_DISTINCTEXPR:
+			return (Node *) cachedexpr->subexpr.distinctexpr;
+		case CACHED_NULLIFEXPR:
+			return (Node *) cachedexpr->subexpr.nullifexpr;
+		case CACHED_SCALARARRAYOPEXPR:
+			return (Node *) cachedexpr->subexpr.saopexpr;
+	}
+
+	return NULL;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8d9ff63..c0c8363 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1237,6 +1237,59 @@ _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;
+		case CACHED_DISTINCTEXPR:
+			{
+				/* ... out subexprtype end */
+				outToken(str, "cached_distinctexpr");
+
+				WRITE_NODE_FIELD(subexpr.distinctexpr);
+			}
+			break;
+		case CACHED_NULLIFEXPR:
+			{
+				/* ... out subexprtype end */
+				outToken(str, "cached_nullifexpr");
+
+				WRITE_NODE_FIELD(subexpr.nullifexpr);
+			}
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			{
+				/* ... out subexprtype end */
+				outToken(str, "cached_scalararrayopexpr");
+
+				WRITE_NODE_FIELD(subexpr.saopexpr);
+			}
+			break;
+	}
+}
+
+static void
 _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
 {
 	WRITE_NODE_TYPE("SCALARARRAYOPEXPR");
@@ -3767,6 +3820,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 e24f5d6..acb14f9 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -750,6 +750,52 @@ _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 if (strncmp(token, "cached_distinctexpr", 19) == 0)
+		local_node->subexprtype = CACHED_DISTINCTEXPR;
+	else if (strncmp(token, "cached_nullifexpr", 17) == 0)
+		local_node->subexprtype = CACHED_NULLIFEXPR;
+	else if (strncmp(token, "cached_scalararrayopexpr", 24) == 0)
+		local_node->subexprtype = CACHED_SCALARARRAYOPEXPR;
+	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;
+		case CACHED_DISTINCTEXPR:
+			READ_NODE_FIELD(subexpr.distinctexpr);
+			break;
+		case CACHED_NULLIFEXPR:
+			READ_NODE_FIELD(subexpr.nullifexpr);
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			READ_NODE_FIELD(subexpr.saopexpr);
+			break;
+	}
+
+	READ_DONE();
+}
+
+/*
  * _readScalarArrayOpExpr
  */
 static ScalarArrayOpExpr *
@@ -2462,6 +2508,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..552b73d 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,261 @@ 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;
+		}
+	}
+	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,
+											NULL);
+		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->xpr))
+		{
+			/* return DistinctExpr, which will not be cached */
+			return (Node *) distinctexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexprtype = CACHED_DISTINCTEXPR;
+			new_node->subexpr.distinctexpr = 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,
+											NULL);
+		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->xpr))
+		{
+			/* return NullIfExpr, which will not be cached */
+			return (Node *) nullifexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexprtype = CACHED_NULLIFEXPR;
+			new_node->subexpr.nullifexpr = 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,
+											NULL);
+
+		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->xpr))
+		{
+			/* return ScalarArrayOpExpr, which will not be cached */
+			return (Node *) saopexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexprtype = CACHED_SCALARARRAYOPEXPR;
+			new_node->subexpr.saopexpr = saopexpr;
+
+			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..0dbfa12 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -76,5 +76,6 @@ extern bool raw_expression_tree_walker(Node *node, bool (*walker) (),
 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..3f89653 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1498,4 +1498,42 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*
+ * 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 */
+	CACHED_DISTINCTEXPR,		/* cached DistinctExpr */
+	CACHED_NULLIFEXPR,			/* cached NullIfExpr */
+	CACHED_SCALARARRAYOPEXPR	/* cached ScalarArrayOpExpr */
+} 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 */
+		DistinctExpr *distinctexpr; /* for CACHED_DISTINCTEXPR */
+		NullIfExpr *nullifexpr; /* for CACHED_NULLIFEXPR */
+		ScalarArrayOpExpr *saopexpr;	/* for CACHED_SCALARARRAYOPEXPR */
+	} subexpr;
+} CachedExpr;
+
 #endif   /* PRIMNODES_H */
-- 
1.9.1

v4-0002-Precalculate-stable-functions-planning-and-execut.patchtext/x-diff; name=v4-0002-Precalculate-stable-functions-planning-and-execut.patchDownload
From 537d8a2bb085efdfce695f148e614ed4611f9a6e Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 15 May 2017 15:31:21 +0300
Subject: [PATCH v4 2/3] Precalculate stable functions, planning and execution

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:
- replacement nonvolatile functions and operators by appropriate cached
expressions
- planning and execution cached expressions
- regression tests
---
 src/backend/executor/execExpr.c                    |   55 +
 src/backend/executor/execExprInterp.c              |   51 +
 src/backend/optimizer/path/allpaths.c              |    9 +-
 src/backend/optimizer/path/clausesel.c             |   13 +
 src/backend/optimizer/plan/planagg.c               |    1 +
 src/backend/optimizer/plan/planner.c               |   28 +
 src/backend/optimizer/util/clauses.c               |   55 +
 src/backend/utils/adt/ruleutils.c                  |    5 +
 src/include/executor/execExpr.h                    |   37 +
 src/include/optimizer/planner.h                    |    3 +
 src/include/optimizer/tlist.h                      |    8 +-
 src/pl/plpgsql/src/pl_exec.c                       |   10 +
 .../expected/precalculate_stable_functions.out     | 2625 ++++++++++++++++++++
 src/test/regress/serial_schedule                   |    1 +
 .../regress/sql/precalculate_stable_functions.sql  |  949 +++++++
 15 files changed, 3847 insertions(+), 3 deletions(-)
 create mode 100644 src/test/regress/expected/precalculate_stable_functions.out
 create mode 100644 src/test/regress/sql/precalculate_stable_functions.sql

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 5a34a46..dc84975 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -865,6 +865,61 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
 				break;
 			}
 
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+
+				/*
+				 * Allocate CachedExprState used by all steps of CachedExpr
+				 * evaluation.
+				 */
+				scratch.d.cachedexpr.state = (CachedExprState *) palloc(
+					sizeof(CachedExprState));
+				scratch.d.cachedexpr.state->isExecuted = false;
+				scratch.d.cachedexpr.state->resnull = false;
+				scratch.d.cachedexpr.state->resvalue = (Datum) 0;
+
+				switch(cachedexpr->subexprtype)
+				{
+					case CACHED_FUNCEXPR:
+						scratch.d.cachedexpr.state->restypid =
+							cachedexpr->subexpr.funcexpr->funcresulttype;
+						break;
+					case CACHED_OPEXPR:
+						scratch.d.cachedexpr.state->restypid =
+							cachedexpr->subexpr.opexpr->opresulttype;
+						break;
+					case CACHED_DISTINCTEXPR:
+						scratch.d.cachedexpr.state->restypid =
+							cachedexpr->subexpr.distinctexpr->opresulttype;
+						break;
+					case CACHED_NULLIFEXPR:
+						scratch.d.cachedexpr.state->restypid =
+							cachedexpr->subexpr.nullifexpr->opresulttype;
+						break;
+					case CACHED_SCALARARRAYOPEXPR:
+						scratch.d.cachedexpr.state->restypid = BOOLOID;
+						break;
+				}
+
+				/* add EEOP_CACHEDEXPR_IF_CACHED step */
+				scratch.opcode = EEOP_CACHEDEXPR_IF_CACHED;
+				ExprEvalPushStep(state, &scratch);
+
+				/* add subexpression steps */
+				ExecInitExprRec((Expr *) get_subexpr(cachedexpr), parent, state,
+								resv, resnull);
+
+				/* add EEOP_CACHEDEXPR_SUBEXPR_END step */
+				scratch.opcode = EEOP_CACHEDEXPR_SUBEXPR_END;
+				ExprEvalPushStep(state, &scratch);
+
+				/* adjust jump target */
+				scratch.d.cachedexpr.state->jumpdone = state->steps_len;
+
+				break;
+			}
+
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index fed0052..2cb10fd 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -70,6 +70,7 @@
 #include "pgstat.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
+#include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
@@ -309,6 +310,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_FUNCEXPR_STRICT,
 		&&CASE_EEOP_FUNCEXPR_FUSAGE,
 		&&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE,
+		&&CASE_EEOP_CACHEDEXPR_IF_CACHED,
+		&&CASE_EEOP_CACHEDEXPR_SUBEXPR_END,
 		&&CASE_EEOP_BOOL_AND_STEP_FIRST,
 		&&CASE_EEOP_BOOL_AND_STEP,
 		&&CASE_EEOP_BOOL_AND_STEP_LAST,
@@ -721,6 +724,54 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_CACHEDEXPR_IF_CACHED)
+		{
+			if (op->d.cachedexpr.state->isExecuted)
+			{
+				/* use saved result and skip subexpression evaluation */
+				*op->resnull = op->d.cachedexpr.state->resnull;
+				if (!(*op->resnull))
+					*op->resvalue = op->d.cachedexpr.state->resvalue;
+
+				EEO_JUMP(op->d.cachedexpr.state->jumpdone);
+			}
+
+			/* we are ready for subexpression evaluation */
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHEDEXPR_SUBEXPR_END)
+		{
+			int16		restyplen;
+			bool		restypbyval;
+
+			/* save result */
+			op->d.cachedexpr.state->resnull = *op->resnull;
+			if (!(*op->resnull))
+			{
+				get_typlenbyval(op->d.cachedexpr.state->restypid, &restyplen,
+								&restypbyval);
+
+				/*
+				 * Switch per-query memory context. It is necessary to save the
+				 * subexpression result value between all tuples if its datum is
+				 * a pointer.
+				 */
+				op->d.cachedexpr.state->oldContext = MemoryContextSwitchTo(
+					econtext->ecxt_per_query_memory);
+
+				op->d.cachedexpr.state->resvalue = datumCopy(*op->resvalue,
+															 restypbyval,
+															 restyplen);
+
+				/* switch memory context back */
+				MemoryContextSwitchTo(op->d.cachedexpr.state->oldContext);
+			}
+			op->d.cachedexpr.state->isExecuted = true;
+
+			EEO_NEXT();
+		}
+
 		/*
 		 * If any of its clauses is FALSE, an AND's result is FALSE regardless
 		 * of the states of the rest of the clauses, so we can stop evaluating
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b93b4fc..a322255 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -378,7 +378,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 				set_subquery_pathlist(root, rel, rti, rte);
 				break;
 			case RTE_FUNCTION:
-				set_function_size_estimates(root, rel);
+				{
+					rel->baserestrictinfo = replace_qual_cached_expressions(
+						rel->baserestrictinfo);
+					set_function_size_estimates(root, rel);
+				}
 				break;
 			case RTE_TABLEFUNC:
 				set_tablefunc_size_estimates(root, rel);
@@ -517,6 +521,9 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	 */
 	check_index_predicates(root, rel);
 
+	rel->baserestrictinfo = replace_qual_cached_expressions(
+		rel->baserestrictinfo);
+
 	/* Mark rel with estimated output rows, width, etc */
 	set_baserel_size_estimates(root, rel);
 }
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 758ddea..fc799f1 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
@@ -825,6 +826,18 @@ clause_selectivity(PlannerInfo *root,
 								jointype,
 								sjinfo);
 	}
+	else if (IsA(clause, CachedExpr))
+	{
+		/*
+		 * Not sure this case is needed, but it can't hurt.
+		 * Calculate selectivity of subexpression.
+		 */
+		s1 = clause_selectivity(root,
+								get_subexpr((CachedExpr *) clause),
+								varRelid,
+								jointype,
+								sjinfo);
+	}
 	else
 	{
 		/*
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 5565736..7a28764 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -38,6 +38,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "optimizer/planner.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 552b73d..7c68d6d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6088,6 +6088,34 @@ get_partitioned_child_rels(PlannerInfo *root, Index rti)
 	return result;
 }
 
+/*
+ * replace_pathtarget_cached_expressions
+ *		Replace cached expresisons in a PathTarget tlist.
+ *
+ * As a notational convenience, returns the same PathTarget pointer passed in.
+ */
+PathTarget *
+replace_pathtarget_cached_expressions(PathTarget *target)
+{
+	target->exprs = (List *) replace_cached_expressions_mutator(
+		(Node *) target->exprs);
+
+	return target;
+}
+
+/*
+ * replace_qual_cached_expressions
+ *		Replace cacehd expressions in a WHERE clause. The input can be either an
+ *		implicitly-ANDed list of boolean expressions, or a list of RestrictInfo
+ *		nodes.
+ */
+List *
+replace_qual_cached_expressions(List *quals)
+{
+	/* No setup needed for tree walk, so away we go */
+	return (List *) replace_cached_expressions_mutator((Node *) quals);
+}
+
 static Node *
 replace_cached_expressions_mutator(Node *node)
 {
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a1dafc8..0c0284a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2758,6 +2758,61 @@ eval_const_expressions_mutator(Node *node,
 				newexpr->location = expr->location;
 				return (Node *) newexpr;
 			}
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+				Node	   *new_subexpr = eval_const_expressions_mutator(
+					get_subexpr(cachedexpr), context);
+				CachedExpr *new_cachedexpr;
+
+				/*
+				 * If unsafe transformations are used cached expression should
+				 * be always simplified.
+				 */
+				if (context->estimate)
+					Assert(IsA(new_subexpr, Const));
+
+				if (IsA(new_subexpr, Const))
+				{
+					/* successfully simplified it */
+					return new_subexpr;	
+				}
+				else
+				{
+					/*
+					 * The expression cannot be simplified any further, so build
+					 * and return a replacement CachedExpr node using the
+					 * possibly-simplified arguments of subexpression.
+					 */
+					new_cachedexpr = makeNode(CachedExpr);
+					new_cachedexpr->subexprtype = cachedexpr->subexprtype;
+					switch (new_cachedexpr->subexprtype)
+					{
+						case CACHED_FUNCEXPR:
+							new_cachedexpr->subexpr.funcexpr = (FuncExpr *)
+								new_subexpr;
+							break;
+						case CACHED_OPEXPR:
+							new_cachedexpr->subexpr.opexpr = (OpExpr *)
+								new_subexpr;
+							break;
+						case CACHED_DISTINCTEXPR:
+							new_cachedexpr->subexpr.distinctexpr =
+								(DistinctExpr *) new_subexpr;
+							break;
+						case CACHED_NULLIFEXPR:
+							new_cachedexpr->subexpr.nullifexpr = (NullIfExpr *)
+								new_subexpr;
+							break;
+						case CACHED_SCALARARRAYOPEXPR:
+							new_cachedexpr->subexpr.saopexpr =
+								(ScalarArrayOpExpr *) new_subexpr;
+							break;
+					}
+
+					return (Node *) new_cachedexpr;
+				}
+			}
 		case T_BoolExpr:
 			{
 				BoolExpr   *expr = (BoolExpr *) node;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 43b1475..838389d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7720,6 +7720,11 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_CachedExpr:
+			get_rule_expr(get_subexpr((CachedExpr *) node), context,
+						  showimplicit);
+			break;
+
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 86fdb33..ea37a36 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -86,6 +86,16 @@ typedef enum ExprEvalOp
 	EEOP_FUNCEXPR_STRICT_FUSAGE,
 
 	/*
+	 * Evaluate CachedExpr.  EEOP_CACHEDEXPR_IF_CACHED is used before
+	 * subexpression evaluation (if subexpression was evaluated use cached value
+	 * and jump to next state or get prepared to subexpression evaluation
+	 * otherwise).  EEOP_CACHEDEXPR_SUBEXPR_END is used after subexpression
+	 * evaluation for caching its result.
+	 */
+	EEOP_CACHEDEXPR_IF_CACHED,
+	EEOP_CACHEDEXPR_SUBEXPR_END,
+
+	/*
 	 * Evaluate boolean AND expression, one step per subexpression. FIRST/LAST
 	 * subexpressions are special-cased for performance.  Since AND always has
 	 * at least two subexpressions, FIRST and LAST never apply to the same
@@ -298,6 +308,13 @@ typedef struct ExprEvalStep
 			int			nargs;	/* number of arguments */
 		}			func;
 
+		/* for EEOP_CACHEDEXPR_* */
+		struct
+		{
+			/* steps for evaluation the same CachedExpr have the same state */
+			struct CachedExprState *state;
+		}			cachedexpr;
+
 		/* for EEOP_BOOL_*_STEP */
 		struct
 		{
@@ -600,6 +617,26 @@ typedef struct ArrayRefState
 } ArrayRefState;
 
 
+/*
+ * Non-inline data for EEOP_CACHEDEXPR_* operations (steps for evaluation the
+ * same CachedExpr have the same state).
+ */
+typedef struct CachedExprState
+{
+	bool		isExecuted;
+	bool		resnull;
+	Datum		resvalue;
+	Oid 		restypid;		/* for copying resvalue of subexpression */
+	int			jumpdone;		/* jump here if result determined */
+
+	/*
+	 * For switching per-query memory context. It is necessary to save the
+	 * subexpression result between all tuples if its value datum is a pointer.
+	 */
+	MemoryContext oldContext;
+} CachedExprState;
+
+
 extern void ExecReadyInterpretedExpr(ExprState *state);
 
 extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index f3aaa23..bbadcdd 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -59,4 +59,7 @@ extern bool plan_cluster_use_sort(Oid tableOid, Oid indexOid);
 
 extern List *get_partitioned_child_rels(PlannerInfo *root, Index rti);
 
+extern PathTarget *replace_pathtarget_cached_expressions(PathTarget *target);
+extern List *replace_qual_cached_expressions(List *quals);
+
 #endif   /* PLANNER_H */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index ccb93d8..7488bd2 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -65,8 +65,12 @@ extern void split_pathtarget_at_srfs(PlannerInfo *root,
 						 PathTarget *target, PathTarget *input_target,
 						 List **targets, List **targets_contain_srfs);
 
-/* Convenience macro to get a PathTarget with valid cost/width fields */
+/*
+ * Convenience macro to get a PathTarget with valid cost/width fields and
+ * cached expressions.
+ */
 #define create_pathtarget(root, tlist) \
-	set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
+	set_pathtarget_cost_width(root, replace_pathtarget_cached_expressions( \
+		make_pathtarget_from_tlist(tlist)))
 
 #endif   /* TLIST_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 7a40c99..2e27052 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -6535,6 +6535,16 @@ exec_simple_check_node(Node *node)
 				return TRUE;
 			}
 
+		case T_CachedExpr:
+			{
+				/*
+				 * If CachedExpr will not be initialized by ExecInitCachedExpr
+				 * possibly it will use cached value when it shouldn't (for
+				 * example, snapshot has changed), so return false.
+				 */
+				return FALSE;
+			}
+
 		case T_ScalarArrayOpExpr:
 			{
 				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
new file mode 100644
index 0000000..093e6f8
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -0,0 +1,2625 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TABLE two (i integer);
+INSERT INTO two VALUES (1), (2);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_int (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_int (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(i) from two);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE operator === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ====== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE operator ==== (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- Simple functions testing
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- Functions with constant arguments and nested functions testing
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Nested and strict operators testing
+-- (also partly mixed functions and operators testing)
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl() ==== 2) ====== (x_stl() ===== 3) FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== x_stl2_boolean(NULL) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- IS DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(1 IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Nested IS DISTINCT FROM expression testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, x_stl2(NULL))) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(x_stl2(NULL), x_stl2(NULL))) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Nested NULLIF expression testing
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY('{2, 3}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL('{2, 3}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY('{2, NULL}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::int ==== ANY(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL('{2, NULL}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::int ==== ALL(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Nesting "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing (also partly mixed functions and "scalar op ANY/ALL
+-- (array)" / "scalar IN (2 or more values)" expressions testing)
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and operators testing
+-- (most of it was earlier in Nested and strict operators testing)
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and IS DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer),
+  (x_stl_my_integer() IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT equal_booleans_stl_strict(
+  (x_stl() IS DISTINCT FROM 1),
+  (x_stl() IS DISTINCT FROM 2)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF(x_stl_my_integer(), '(1)'::my_integer),
+  NULLIF(x_stl_my_integer(), '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT equal_integers_stl(NULLIF(x_stl(), 1), NULLIF(x_stl(), 2))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ equal_integers_stl 
+--------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing (partly in nesting "scalar op ANY/ALL (array)" /
+-- "scalar IN (2 or more values)" expressions testing)
+SELECT 1 ==== ANY(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY(x_stl_array_int()) FROM generate_series(1, 4) x;
+NOTICE:  s array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL(x_stl_array_int()) FROM generate_series(1, 4) x;
+NOTICE:  s array_int
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed operators and IS DISTINCT FROM expressions testing
+-- should not be precalculated
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed operators and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === 2, TRUE)) FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULLIF(1 ==== 2, TRUE)) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed operators and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT (1 === ANY('{2, 3}')) ====== (1 === ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== ANY('{2, 3}')) ====== (1 ==== ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed IS DISTINCT FROM and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed IS DISTINCT FROM and "scalar op ANY/ALL (array)" / "scalar IN (2 or
+-- more values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  (1 === ANY('{2, 3}')) IS DISTINCT FROM
+  (1 === ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  (1 ==== ANY('{2, 3}')) IS DISTINCT FROM
+  (1 ==== ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed NULLIF and "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === ANY('{2, 3}'), 1 === ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULLIF(1 ==== ANY('{2, 3}'), 1 ==== ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      2
+(1 row)
+
+INSERT INTO two VALUES (3);
+SELECT simple();
+ simple 
+--------
+      3
+(1 row)
+
+ROLLBACK;
+-- Drop tables for testing
+DROP TABLE two;
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 04206c3..f2710b9 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -179,3 +179,4 @@ test: with
 test: xml
 test: event_trigger
 test: stats
+test: precalculate_stable_functions
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
new file mode 100644
index 0000000..a59791d
--- /dev/null
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -0,0 +1,949 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+
+-- Create types and tables for testing
+
+CREATE TYPE my_integer AS (value integer);
+
+CREATE TABLE two (i integer);
+INSERT INTO two VALUES (1), (2);
+
+-- Create volatile functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_array_int (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create stable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_array_int (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(i) from two);
+END
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create immutable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create operators for testing
+
+CREATE operator === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ====== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+CREATE operator ==== (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- Simple functions testing
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+-- WHERE clause testing
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+-- Functions with constant arguments and nested functions testing
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions testing
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions with null arguments testing
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+-- Operators testing
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+
+-- Nested and strict operators testing
+-- (also partly mixed functions and operators testing)
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (x_stl() ==== 2) ====== (x_stl() ===== 3) FROM generate_series(1, 4) x;
+SELECT (1 ==== 2) ====== x_stl2_boolean(NULL) FROM generate_series(1, 4) x;
+
+-- IS DISTINCT FROM expression testing
+
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+
+-- IS DISTINCT FROM expressions with null arguments testing
+
+SELECT x_stl2_boolean(1 IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+
+-- Nested IS DISTINCT FROM expression testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+-- NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- NULLIF expressions with null arguments testing
+
+SELECT x_stl2(NULLIF(1, x_stl2(NULL))) FROM generate_series(1, 4) x;
+
+SELECT x_stl2(NULLIF(x_stl2(NULL), x_stl2(NULL))) FROM generate_series(1, 4) x;
+
+-- Nested NULLIF expression testing
+
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+
+SELECT 1 === ANY('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 === ALL('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT 1 ==== ANY('{2, 3}') FROM generate_series(1, 4) x;
+SELECT 1 ==== ALL('{2, 3}') FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+
+SELECT 1 ==== ANY('{2, NULL}') FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== ANY(NULL)) FROM generate_series(1, 4) x;
+SELECT NULL ==== ANY('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+SELECT NULL ==== ANY('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL::int ==== ANY(NULL)) FROM generate_series(1, 4) x;
+
+SELECT 1 ==== ALL('{2, NULL}') FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== ALL(NULL)) FROM generate_series(1, 4) x;
+SELECT NULL ==== ALL('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+SELECT NULL ==== ALL('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL::int ==== ALL(NULL)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM generate_series(1, 4) x;
+
+-- Nesting "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing (also partly mixed functions and "scalar op ANY/ALL
+-- (array)" / "scalar IN (2 or more values)" expressions testing)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and operators testing
+-- (most of it was earlier in Nested and strict operators testing)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+-- Mixed functions and IS DISTINCT FROM expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer),
+  (x_stl_my_integer() IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT equal_booleans_stl_strict(
+  (x_stl() IS DISTINCT FROM 1),
+  (x_stl() IS DISTINCT FROM 2)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and NULLIF expressions testing
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF(x_stl_my_integer(), '(1)'::my_integer),
+  NULLIF(x_stl_my_integer(), '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT equal_integers_stl(NULLIF(x_stl(), 1), NULLIF(x_stl(), 2))
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing (partly in nesting "scalar op ANY/ALL (array)" /
+-- "scalar IN (2 or more values)" expressions testing)
+
+SELECT 1 ==== ANY(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== ALL(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT 1 ==== ANY(x_stl_array_int()) FROM generate_series(1, 4) x;
+SELECT 1 ==== ALL(x_stl_array_int()) FROM generate_series(1, 4) x;
+
+-- Mixed operators and IS DISTINCT FROM expressions testing
+
+-- should not be precalculated
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+
+-- Mixed operators and NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === 2, TRUE)) FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(NULLIF(1 ==== 2, TRUE)) FROM generate_series(1, 4) x;
+
+-- Mixed operators and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT (1 === ANY('{2, 3}')) ====== (1 === ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT (1 ==== ANY('{2, 3}')) ====== (1 ==== ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- Mixed IS DISTINCT FROM and NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed IS DISTINCT FROM and "scalar op ANY/ALL (array)" / "scalar IN (2 or
+-- more values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  (1 === ANY('{2, 3}')) IS DISTINCT FROM
+  (1 === ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  (1 ==== ANY('{2, 3}')) IS DISTINCT FROM
+  (1 ==== ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed NULLIF and "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === ANY('{2, 3}'), 1 === ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(NULLIF(1 ==== ANY('{2, 3}'), 1 ==== ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- Tracking functions testing
+
+SET track_functions TO 'all';
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+SET track_functions TO DEFAULT;
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+
+BEGIN;
+SELECT simple();
+INSERT INTO two VALUES (3);
+SELECT simple();
+ROLLBACK;
+
+-- Drop tables for testing
+
+DROP TABLE two;
-- 
1.9.1

v4-0003-Precalculate-stable-functions-costs.patchtext/x-diff; name=v4-0003-Precalculate-stable-functions-costs.patchDownload
From 2382fa68414f6bbed42ff66c7abbc3c9b200d244 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 15 May 2017 16:05:38 +0300
Subject: [PATCH v4 3/3] Precalculate stable functions, costs

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:
- cost changes for cached expressions (according to their behaviour)
---
 src/backend/optimizer/path/costsize.c | 89 ++++++++++++++++++++++++++---------
 1 file changed, 67 insertions(+), 22 deletions(-)

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 52643d0..505772a 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -140,6 +140,7 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root,
 			   PathKey *pathkey);
 static void cost_rescan(PlannerInfo *root, Path *path,
 			Cost *rescan_startup_cost, Cost *rescan_total_cost);
+static double cost_eval_cacheable_expr_per_tuple(Node *node);
 static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
 static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
 						  ParamPathInfo *param_info,
@@ -3464,6 +3465,59 @@ cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root)
 	*cost = context.total;
 }
 
+/*
+ * cost_eval_cacheable_expr_per_tuple
+ *		Evaluate per tuple cost for expressions that can be cacheable.
+ *
+ * This function was created to not duplicate code for some expression and
+ * cached some expression.
+ */
+static double
+cost_eval_cacheable_expr_per_tuple(Node *node)
+{
+	double result;
+
+	/*
+	 * For each operator or function node in the given tree, we charge the
+	 * estimated execution cost given by pg_proc.procost (remember to multiply
+	 * this by cpu_operator_cost).
+	 */
+	if (IsA(node, FuncExpr))
+	{
+		result = get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+	}
+	else if (IsA(node, OpExpr) ||
+			 IsA(node, DistinctExpr) ||
+			 IsA(node, NullIfExpr))
+	{
+		OpExpr     *opexpr = (OpExpr *) node;
+
+		/* rely on struct equivalence to treat these all alike */
+		set_opfuncid(opexpr);
+
+		result = get_func_cost(opexpr->opfuncid) * cpu_operator_cost;
+	}
+	else if (IsA(node, ScalarArrayOpExpr))
+	{
+		/*
+		 * Estimate that the operator will be applied to about half of the
+		 * array elements before the answer is determined.
+		 */
+		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
+		Node	   *arraynode = (Node *) lsecond(saop->args);
+
+		set_sa_opfuncid(saop);
+		result = get_func_cost(saop->opfuncid) * cpu_operator_cost *
+			estimate_array_length(arraynode) * 0.5;
+	}
+	else
+	{
+		elog(ERROR, "non cacheable expression node type: %d", (int) nodeTag(node));
+	}
+
+	return result;
+}
+
 static bool
 cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 {
@@ -3537,32 +3591,23 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 	 * moreover, since our rowcount estimates for functions tend to be pretty
 	 * phony, the results would also be pretty phony.
 	 */
-	if (IsA(node, FuncExpr))
+	if (IsA(node, FuncExpr) ||
+		IsA(node, OpExpr) ||
+		IsA(node, DistinctExpr) ||
+		IsA(node, NullIfExpr) ||
+		IsA(node, ScalarArrayOpExpr))
 	{
-		context->total.per_tuple +=
-			get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+		context->total.per_tuple += cost_eval_cacheable_expr_per_tuple(node);
 	}
-	else if (IsA(node, OpExpr) ||
-			 IsA(node, DistinctExpr) ||
-			 IsA(node, NullIfExpr))
-	{
-		/* rely on struct equivalence to treat these all alike */
-		set_opfuncid((OpExpr *) node);
-		context->total.per_tuple +=
-			get_func_cost(((OpExpr *) node)->opfuncid) * cpu_operator_cost;
-	}
-	else if (IsA(node, ScalarArrayOpExpr))
-	{
+	else if (IsA(node, CachedExpr))
+	{	
 		/*
-		 * Estimate that the operator will be applied to about half of the
-		 * array elements before the answer is determined.
+		 * Calculate subexpression cost per tuple as usual and add it to startup
+		 * cost (because subexpression will be executed only once for all
+		 * tuples).
 		 */
-		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
-		Node	   *arraynode = (Node *) lsecond(saop->args);
-
-		set_sa_opfuncid(saop);
-		context->total.per_tuple += get_func_cost(saop->opfuncid) *
-			cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
+		context->total.startup += cost_eval_cacheable_expr_per_tuple(
+			get_subexpr((CachedExpr *) node));
 	}
 	else if (IsA(node, Aggref) ||
 			 IsA(node, WindowFunc))
-- 
1.9.1

#10Aleksander Alekseev
a.alekseev@postgrespro.ru
In reply to: Marina Polyakova (#9)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hi Marina,

I still don't see anything particularly wrong with your patch. It
applies, passes all test, it is well test-covered and even documented.
Also I've run `make installcheck` under Valgrind and didn't find any
memory-related errors.

Is there anything that you would like to change before we call it more
or less final?

Also I would advice to add your branch to our internal buildfarm just to
make sure everything is OK on exotic platforms like Windows ;)

On Mon, May 22, 2017 at 06:32:17PM +0300, Marina Polyakova wrote:

Hi,

Hello!

I've not followed this thread, but just scanned this quickly because it
affects execExpr* stuff.

Thank you very much for your comments! Thanks to them I have made v4 of the
patches (as in the previous one, only planning and execution part is
changed).

Looks like having something like struct CachedExprState would be better,
than these separate allocations? That also allows to aleviate some size
concerns when adding new fields (see below)

I'd rather not have this on function scope - a) the stack pressure in
ExecInterpExpr is quite noticeable in profiles already b) this is going
to trigger warnings because of unused vars, because the compiler doesn't
understand that EEOP_CACHEDEXPR_IF_CACHED always follows
EEOP_CACHEDEXPR_SUBEXPR_END.

How about instead storing oldcontext in the expression itself?

Thanks, in new version I did all of it in this way.

I'm also not sure how acceptable it is to just assume it's ok to leave
stuff in per_query_memory, in some cases that could prove to be
problematic.

I agree with you and in new version context is changed only for copying
datum of result value (if it's a pointer, its data should be allocated in
per_query_memory, or we will lost it for next tuples).

Is this actually a meaningful path? Shouldn't always have done const
evaluation before adding CachedExpr's?

eval_const_expressions_mutator is used several times, and one of them in
functions for selectivity evaluation (set_baserel_size_estimates ->
clauselist_selectivity -> clause_selectivity -> restriction_selectivity ->
... -> get_restriction_variable -> estimate_expression_value ->
eval_const_expressions_mutator). In set_baserel_size_estimates function
right after selectivity evaluation there's costs evaluation and cached
expressions should be replaced before costs. I'm not sure that it is a good
idea to insert cached expressions replacement in set_baserel_size_estimates,
because in comments to it it's said "The rel's targetlist and restrictinfo
list must have been constructed already, and rel->tuples must be set." and
its file costsize.c is entitled as "Routines to compute (and set) relation
sizes and path costs". So I have inserted cached expressions replacement
just before it (but I'm not sure that I have seen all places where it should
be inserted). What do you think about all of this?

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

From 02262b9f3a3215d3884b6ac188bafa6517ac543d Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 15 May 2017 14:24:36 +0300
Subject: [PATCH v4 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 functions' and operators' expressions by
appropriate cached expressions.
---
src/backend/nodes/copyfuncs.c | 31 +++++
src/backend/nodes/equalfuncs.c | 31 +++++
src/backend/nodes/nodeFuncs.c | 151 ++++++++++++++++++++
src/backend/nodes/outfuncs.c | 56 ++++++++
src/backend/nodes/readfuncs.c | 48 +++++++
src/backend/optimizer/plan/planner.c | 259 +++++++++++++++++++++++++++++++++++
src/include/nodes/nodeFuncs.h | 1 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/primnodes.h | 38 +++++
9 files changed, 616 insertions(+)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6ad3844..f9f69a1 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1527,6 +1527,34 @@ _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;
+		case CACHED_DISTINCTEXPR:
+			COPY_NODE_FIELD(subexpr.distinctexpr);
+			break;
+		case CACHED_NULLIFEXPR:
+			COPY_NODE_FIELD(subexpr.nullifexpr);
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			COPY_NODE_FIELD(subexpr.saopexpr);
+			break;
+	}
+
+	return newnode;
+}
+
/*
* _copyScalarArrayOpExpr
*/
@@ -4867,6 +4895,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 c9a8c34..8863759 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -384,6 +384,34 @@ _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;
+		case CACHED_DISTINCTEXPR:
+			COMPARE_NODE_FIELD(subexpr.distinctexpr);
+			break;
+		case CACHED_NULLIFEXPR:
+			COMPARE_NODE_FIELD(subexpr.nullifexpr);
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			COMPARE_NODE_FIELD(subexpr.saopexpr);
+			break;
+	}
+
+	return true;
+}
+
+static bool
_equalScalarArrayOpExpr(const ScalarArrayOpExpr *a, const ScalarArrayOpExpr *b)
{
COMPARE_SCALAR_FIELD(opno);
@@ -3031,6 +3059,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..e3dd576 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,54 @@ 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;
+					case CACHED_DISTINCTEXPR:
+						newnode->subexpr.distinctexpr = (DistinctExpr *)
+							expression_tree_mutator(
+								(Node *) expr->subexpr.distinctexpr, mutator,
+								context);
+						break;
+					case CACHED_NULLIFEXPR:
+						newnode->subexpr.nullifexpr = (NullIfExpr *)
+							expression_tree_mutator(
+								(Node *) expr->subexpr.nullifexpr, mutator,
+								context);
+						break;
+					case CACHED_SCALARARRAYOPEXPR:
+						newnode->subexpr.saopexpr = (ScalarArrayOpExpr *)
+							expression_tree_mutator(
+								(Node *) expr->subexpr.saopexpr, mutator,
+								context);
+						break;
+				}
+
+				return (Node *) newnode;
+			}
+			break;
case T_ScalarArrayOpExpr:
{
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
@@ -3838,3 +3935,57 @@ 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;
+		case CACHED_DISTINCTEXPR:
+			return (const Node *) cachedexpr->subexpr.distinctexpr;
+		case CACHED_NULLIFEXPR:
+			return (const Node *) cachedexpr->subexpr.nullifexpr;
+		case CACHED_SCALARARRAYOPEXPR:
+			return (const Node *) cachedexpr->subexpr.saopexpr;
+	}
+
+	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;
+		case CACHED_DISTINCTEXPR:
+			return (Node *) cachedexpr->subexpr.distinctexpr;
+		case CACHED_NULLIFEXPR:
+			return (Node *) cachedexpr->subexpr.nullifexpr;
+		case CACHED_SCALARARRAYOPEXPR:
+			return (Node *) cachedexpr->subexpr.saopexpr;
+	}
+
+	return NULL;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8d9ff63..c0c8363 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1237,6 +1237,59 @@ _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;
+		case CACHED_DISTINCTEXPR:
+			{
+				/* ... out subexprtype end */
+				outToken(str, "cached_distinctexpr");
+
+				WRITE_NODE_FIELD(subexpr.distinctexpr);
+			}
+			break;
+		case CACHED_NULLIFEXPR:
+			{
+				/* ... out subexprtype end */
+				outToken(str, "cached_nullifexpr");
+
+				WRITE_NODE_FIELD(subexpr.nullifexpr);
+			}
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			{
+				/* ... out subexprtype end */
+				outToken(str, "cached_scalararrayopexpr");
+
+				WRITE_NODE_FIELD(subexpr.saopexpr);
+			}
+			break;
+	}
+}
+
+static void
_outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
{
WRITE_NODE_TYPE("SCALARARRAYOPEXPR");
@@ -3767,6 +3820,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 e24f5d6..acb14f9 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -750,6 +750,52 @@ _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 if (strncmp(token, "cached_distinctexpr", 19) == 0)
+		local_node->subexprtype = CACHED_DISTINCTEXPR;
+	else if (strncmp(token, "cached_nullifexpr", 17) == 0)
+		local_node->subexprtype = CACHED_NULLIFEXPR;
+	else if (strncmp(token, "cached_scalararrayopexpr", 24) == 0)
+		local_node->subexprtype = CACHED_SCALARARRAYOPEXPR;
+	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;
+		case CACHED_DISTINCTEXPR:
+			READ_NODE_FIELD(subexpr.distinctexpr);
+			break;
+		case CACHED_NULLIFEXPR:
+			READ_NODE_FIELD(subexpr.nullifexpr);
+			break;
+		case CACHED_SCALARARRAYOPEXPR:
+			READ_NODE_FIELD(subexpr.saopexpr);
+			break;
+	}
+
+	READ_DONE();
+}
+
+/*
* _readScalarArrayOpExpr
*/
static ScalarArrayOpExpr *
@@ -2462,6 +2508,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..552b73d 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,261 @@ 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;
+		}
+	}
+	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,
+											NULL);
+		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->xpr))
+		{
+			/* return DistinctExpr, which will not be cached */
+			return (Node *) distinctexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexprtype = CACHED_DISTINCTEXPR;
+			new_node->subexpr.distinctexpr = 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,
+											NULL);
+		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->xpr))
+		{
+			/* return NullIfExpr, which will not be cached */
+			return (Node *) nullifexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexprtype = CACHED_NULLIFEXPR;
+			new_node->subexpr.nullifexpr = 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,
+											NULL);
+
+		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->xpr))
+		{
+			/* return ScalarArrayOpExpr, which will not be cached */
+			return (Node *) saopexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			CachedExpr *new_node = makeNode(CachedExpr);
+			new_node->subexprtype = CACHED_SCALARARRAYOPEXPR;
+			new_node->subexpr.saopexpr = saopexpr;
+
+			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..0dbfa12 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -76,5 +76,6 @@ extern bool raw_expression_tree_walker(Node *node, bool (*walker) (),
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..3f89653 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1498,4 +1498,42 @@ typedef struct OnConflictExpr
List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
} OnConflictExpr;
+/*
+ * 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 */
+	CACHED_DISTINCTEXPR,		/* cached DistinctExpr */
+	CACHED_NULLIFEXPR,			/* cached NullIfExpr */
+	CACHED_SCALARARRAYOPEXPR	/* cached ScalarArrayOpExpr */
+} 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 */
+		DistinctExpr *distinctexpr; /* for CACHED_DISTINCTEXPR */
+		NullIfExpr *nullifexpr; /* for CACHED_NULLIFEXPR */
+		ScalarArrayOpExpr *saopexpr;	/* for CACHED_SCALARARRAYOPEXPR */
+	} subexpr;
+} CachedExpr;
+
#endif   /* PRIMNODES_H */
-- 
1.9.1

From 537d8a2bb085efdfce695f148e614ed4611f9a6e Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 15 May 2017 15:31:21 +0300
Subject: [PATCH v4 2/3] Precalculate stable functions, planning and execution

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:
- replacement nonvolatile functions and operators by appropriate cached
expressions
- planning and execution cached expressions
- regression tests
---
src/backend/executor/execExpr.c | 55 +
src/backend/executor/execExprInterp.c | 51 +
src/backend/optimizer/path/allpaths.c | 9 +-
src/backend/optimizer/path/clausesel.c | 13 +
src/backend/optimizer/plan/planagg.c | 1 +
src/backend/optimizer/plan/planner.c | 28 +
src/backend/optimizer/util/clauses.c | 55 +
src/backend/utils/adt/ruleutils.c | 5 +
src/include/executor/execExpr.h | 37 +
src/include/optimizer/planner.h | 3 +
src/include/optimizer/tlist.h | 8 +-
src/pl/plpgsql/src/pl_exec.c | 10 +
.../expected/precalculate_stable_functions.out | 2625 ++++++++++++++++++++
src/test/regress/serial_schedule | 1 +
.../regress/sql/precalculate_stable_functions.sql | 949 +++++++
15 files changed, 3847 insertions(+), 3 deletions(-)
create mode 100644 src/test/regress/expected/precalculate_stable_functions.out
create mode 100644 src/test/regress/sql/precalculate_stable_functions.sql

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 5a34a46..dc84975 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -865,6 +865,61 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
break;
}
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+
+				/*
+				 * Allocate CachedExprState used by all steps of CachedExpr
+				 * evaluation.
+				 */
+				scratch.d.cachedexpr.state = (CachedExprState *) palloc(
+					sizeof(CachedExprState));
+				scratch.d.cachedexpr.state->isExecuted = false;
+				scratch.d.cachedexpr.state->resnull = false;
+				scratch.d.cachedexpr.state->resvalue = (Datum) 0;
+
+				switch(cachedexpr->subexprtype)
+				{
+					case CACHED_FUNCEXPR:
+						scratch.d.cachedexpr.state->restypid =
+							cachedexpr->subexpr.funcexpr->funcresulttype;
+						break;
+					case CACHED_OPEXPR:
+						scratch.d.cachedexpr.state->restypid =
+							cachedexpr->subexpr.opexpr->opresulttype;
+						break;
+					case CACHED_DISTINCTEXPR:
+						scratch.d.cachedexpr.state->restypid =
+							cachedexpr->subexpr.distinctexpr->opresulttype;
+						break;
+					case CACHED_NULLIFEXPR:
+						scratch.d.cachedexpr.state->restypid =
+							cachedexpr->subexpr.nullifexpr->opresulttype;
+						break;
+					case CACHED_SCALARARRAYOPEXPR:
+						scratch.d.cachedexpr.state->restypid = BOOLOID;
+						break;
+				}
+
+				/* add EEOP_CACHEDEXPR_IF_CACHED step */
+				scratch.opcode = EEOP_CACHEDEXPR_IF_CACHED;
+				ExprEvalPushStep(state, &scratch);
+
+				/* add subexpression steps */
+				ExecInitExprRec((Expr *) get_subexpr(cachedexpr), parent, state,
+								resv, resnull);
+
+				/* add EEOP_CACHEDEXPR_SUBEXPR_END step */
+				scratch.opcode = EEOP_CACHEDEXPR_SUBEXPR_END;
+				ExprEvalPushStep(state, &scratch);
+
+				/* adjust jump target */
+				scratch.d.cachedexpr.state->jumpdone = state->steps_len;
+
+				break;
+			}
+
case T_ScalarArrayOpExpr:
{
ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index fed0052..2cb10fd 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -70,6 +70,7 @@
#include "pgstat.h"
#include "utils/builtins.h"
#include "utils/date.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/timestamp.h"
#include "utils/typcache.h"
@@ -309,6 +310,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_FUNCEXPR_STRICT,
&&CASE_EEOP_FUNCEXPR_FUSAGE,
&&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE,
+		&&CASE_EEOP_CACHEDEXPR_IF_CACHED,
+		&&CASE_EEOP_CACHEDEXPR_SUBEXPR_END,
&&CASE_EEOP_BOOL_AND_STEP_FIRST,
&&CASE_EEOP_BOOL_AND_STEP,
&&CASE_EEOP_BOOL_AND_STEP_LAST,
@@ -721,6 +724,54 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+		EEO_CASE(EEOP_CACHEDEXPR_IF_CACHED)
+		{
+			if (op->d.cachedexpr.state->isExecuted)
+			{
+				/* use saved result and skip subexpression evaluation */
+				*op->resnull = op->d.cachedexpr.state->resnull;
+				if (!(*op->resnull))
+					*op->resvalue = op->d.cachedexpr.state->resvalue;
+
+				EEO_JUMP(op->d.cachedexpr.state->jumpdone);
+			}
+
+			/* we are ready for subexpression evaluation */
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHEDEXPR_SUBEXPR_END)
+		{
+			int16		restyplen;
+			bool		restypbyval;
+
+			/* save result */
+			op->d.cachedexpr.state->resnull = *op->resnull;
+			if (!(*op->resnull))
+			{
+				get_typlenbyval(op->d.cachedexpr.state->restypid, &restyplen,
+								&restypbyval);
+
+				/*
+				 * Switch per-query memory context. It is necessary to save the
+				 * subexpression result value between all tuples if its datum is
+				 * a pointer.
+				 */
+				op->d.cachedexpr.state->oldContext = MemoryContextSwitchTo(
+					econtext->ecxt_per_query_memory);
+
+				op->d.cachedexpr.state->resvalue = datumCopy(*op->resvalue,
+															 restypbyval,
+															 restyplen);
+
+				/* switch memory context back */
+				MemoryContextSwitchTo(op->d.cachedexpr.state->oldContext);
+			}
+			op->d.cachedexpr.state->isExecuted = true;
+
+			EEO_NEXT();
+		}
+
/*
* If any of its clauses is FALSE, an AND's result is FALSE regardless
* of the states of the rest of the clauses, so we can stop evaluating
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b93b4fc..a322255 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -378,7 +378,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
set_subquery_pathlist(root, rel, rti, rte);
break;
case RTE_FUNCTION:
-				set_function_size_estimates(root, rel);
+				{
+					rel->baserestrictinfo = replace_qual_cached_expressions(
+						rel->baserestrictinfo);
+					set_function_size_estimates(root, rel);
+				}
break;
case RTE_TABLEFUNC:
set_tablefunc_size_estimates(root, rel);
@@ -517,6 +521,9 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
*/
check_index_predicates(root, rel);
+	rel->baserestrictinfo = replace_qual_cached_expressions(
+		rel->baserestrictinfo);
+
/* Mark rel with estimated output rows, width, etc */
set_baserel_size_estimates(root, rel);
}
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 758ddea..fc799f1 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -15,6 +15,7 @@
#include "postgres.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
@@ -825,6 +826,18 @@ clause_selectivity(PlannerInfo *root,
jointype,
sjinfo);
}
+	else if (IsA(clause, CachedExpr))
+	{
+		/*
+		 * Not sure this case is needed, but it can't hurt.
+		 * Calculate selectivity of subexpression.
+		 */
+		s1 = clause_selectivity(root,
+								get_subexpr((CachedExpr *) clause),
+								varRelid,
+								jointype,
+								sjinfo);
+	}
else
{
/*
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 5565736..7a28764 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -38,6 +38,7 @@
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
+#include "optimizer/planner.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "parser/parsetree.h"
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 552b73d..7c68d6d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6088,6 +6088,34 @@ get_partitioned_child_rels(PlannerInfo *root, Index rti)
return result;
}
+/*
+ * replace_pathtarget_cached_expressions
+ *		Replace cached expresisons in a PathTarget tlist.
+ *
+ * As a notational convenience, returns the same PathTarget pointer passed in.
+ */
+PathTarget *
+replace_pathtarget_cached_expressions(PathTarget *target)
+{
+	target->exprs = (List *) replace_cached_expressions_mutator(
+		(Node *) target->exprs);
+
+	return target;
+}
+
+/*
+ * replace_qual_cached_expressions
+ *		Replace cacehd expressions in a WHERE clause. The input can be either an
+ *		implicitly-ANDed list of boolean expressions, or a list of RestrictInfo
+ *		nodes.
+ */
+List *
+replace_qual_cached_expressions(List *quals)
+{
+	/* No setup needed for tree walk, so away we go */
+	return (List *) replace_cached_expressions_mutator((Node *) quals);
+}
+
static Node *
replace_cached_expressions_mutator(Node *node)
{
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a1dafc8..0c0284a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2758,6 +2758,61 @@ eval_const_expressions_mutator(Node *node,
newexpr->location = expr->location;
return (Node *) newexpr;
}
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+				Node	   *new_subexpr = eval_const_expressions_mutator(
+					get_subexpr(cachedexpr), context);
+				CachedExpr *new_cachedexpr;
+
+				/*
+				 * If unsafe transformations are used cached expression should
+				 * be always simplified.
+				 */
+				if (context->estimate)
+					Assert(IsA(new_subexpr, Const));
+
+				if (IsA(new_subexpr, Const))
+				{
+					/* successfully simplified it */
+					return new_subexpr;	
+				}
+				else
+				{
+					/*
+					 * The expression cannot be simplified any further, so build
+					 * and return a replacement CachedExpr node using the
+					 * possibly-simplified arguments of subexpression.
+					 */
+					new_cachedexpr = makeNode(CachedExpr);
+					new_cachedexpr->subexprtype = cachedexpr->subexprtype;
+					switch (new_cachedexpr->subexprtype)
+					{
+						case CACHED_FUNCEXPR:
+							new_cachedexpr->subexpr.funcexpr = (FuncExpr *)
+								new_subexpr;
+							break;
+						case CACHED_OPEXPR:
+							new_cachedexpr->subexpr.opexpr = (OpExpr *)
+								new_subexpr;
+							break;
+						case CACHED_DISTINCTEXPR:
+							new_cachedexpr->subexpr.distinctexpr =
+								(DistinctExpr *) new_subexpr;
+							break;
+						case CACHED_NULLIFEXPR:
+							new_cachedexpr->subexpr.nullifexpr = (NullIfExpr *)
+								new_subexpr;
+							break;
+						case CACHED_SCALARARRAYOPEXPR:
+							new_cachedexpr->subexpr.saopexpr =
+								(ScalarArrayOpExpr *) new_subexpr;
+							break;
+					}
+
+					return (Node *) new_cachedexpr;
+				}
+			}
case T_BoolExpr:
{
BoolExpr   *expr = (BoolExpr *) node;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 43b1475..838389d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7720,6 +7720,11 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+		case T_CachedExpr:
+			get_rule_expr(get_subexpr((CachedExpr *) node), context,
+						  showimplicit);
+			break;
+
case T_ScalarArrayOpExpr:
{
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 86fdb33..ea37a36 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -86,6 +86,16 @@ typedef enum ExprEvalOp
EEOP_FUNCEXPR_STRICT_FUSAGE,
/*
+	 * Evaluate CachedExpr.  EEOP_CACHEDEXPR_IF_CACHED is used before
+	 * subexpression evaluation (if subexpression was evaluated use cached value
+	 * and jump to next state or get prepared to subexpression evaluation
+	 * otherwise).  EEOP_CACHEDEXPR_SUBEXPR_END is used after subexpression
+	 * evaluation for caching its result.
+	 */
+	EEOP_CACHEDEXPR_IF_CACHED,
+	EEOP_CACHEDEXPR_SUBEXPR_END,
+
+	/*
* Evaluate boolean AND expression, one step per subexpression. FIRST/LAST
* subexpressions are special-cased for performance.  Since AND always has
* at least two subexpressions, FIRST and LAST never apply to the same
@@ -298,6 +308,13 @@ typedef struct ExprEvalStep
int			nargs;	/* number of arguments */
}			func;
+		/* for EEOP_CACHEDEXPR_* */
+		struct
+		{
+			/* steps for evaluation the same CachedExpr have the same state */
+			struct CachedExprState *state;
+		}			cachedexpr;
+
/* for EEOP_BOOL_*_STEP */
struct
{
@@ -600,6 +617,26 @@ typedef struct ArrayRefState
} ArrayRefState;
+/*
+ * Non-inline data for EEOP_CACHEDEXPR_* operations (steps for evaluation the
+ * same CachedExpr have the same state).
+ */
+typedef struct CachedExprState
+{
+	bool		isExecuted;
+	bool		resnull;
+	Datum		resvalue;
+	Oid 		restypid;		/* for copying resvalue of subexpression */
+	int			jumpdone;		/* jump here if result determined */
+
+	/*
+	 * For switching per-query memory context. It is necessary to save the
+	 * subexpression result between all tuples if its value datum is a pointer.
+	 */
+	MemoryContext oldContext;
+} CachedExprState;
+
+
extern void ExecReadyInterpretedExpr(ExprState *state);
extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index f3aaa23..bbadcdd 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -59,4 +59,7 @@ extern bool plan_cluster_use_sort(Oid tableOid, Oid indexOid);

extern List *get_partitioned_child_rels(PlannerInfo *root, Index rti);

+extern PathTarget *replace_pathtarget_cached_expressions(PathTarget *target);
+extern List *replace_qual_cached_expressions(List *quals);
+
#endif   /* PLANNER_H */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index ccb93d8..7488bd2 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -65,8 +65,12 @@ extern void split_pathtarget_at_srfs(PlannerInfo *root,
PathTarget *target, PathTarget *input_target,
List **targets, List **targets_contain_srfs);
-/* Convenience macro to get a PathTarget with valid cost/width fields */
+/*
+ * Convenience macro to get a PathTarget with valid cost/width fields and
+ * cached expressions.
+ */
#define create_pathtarget(root, tlist) \
-	set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
+	set_pathtarget_cost_width(root, replace_pathtarget_cached_expressions( \
+		make_pathtarget_from_tlist(tlist)))
#endif   /* TLIST_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 7a40c99..2e27052 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -6535,6 +6535,16 @@ exec_simple_check_node(Node *node)
return TRUE;
}
+		case T_CachedExpr:
+			{
+				/*
+				 * If CachedExpr will not be initialized by ExecInitCachedExpr
+				 * possibly it will use cached value when it shouldn't (for
+				 * example, snapshot has changed), so return false.
+				 */
+				return FALSE;
+			}
+
case T_ScalarArrayOpExpr:
{
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
new file mode 100644
index 0000000..093e6f8
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -0,0 +1,2625 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TABLE two (i integer);
+INSERT INTO two VALUES (1), (2);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_int (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_int (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(i) from two);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE operator === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE operator ====== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE operator ==== (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- Simple functions testing
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- Functions with constant arguments and nested functions testing
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Nested and strict operators testing
+-- (also partly mixed functions and operators testing)
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl() ==== 2) ====== (x_stl() ===== 3) FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== x_stl2_boolean(NULL) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- IS DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(1 IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Nested IS DISTINCT FROM expression testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, x_stl2(NULL))) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(x_stl2(NULL), x_stl2(NULL))) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Nested NULLIF expression testing
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY('{2, 3}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL('{2, 3}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY('{2, NULL}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::int ==== ANY(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL('{2, NULL}') FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::int ==== ALL(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Nesting "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing (also partly mixed functions and "scalar op ANY/ALL
+-- (array)" / "scalar IN (2 or more values)" expressions testing)
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and operators testing
+-- (most of it was earlier in Nested and strict operators testing)
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and IS DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer),
+  (x_stl_my_integer() IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT equal_booleans_stl_strict(
+  (x_stl() IS DISTINCT FROM 1),
+  (x_stl() IS DISTINCT FROM 2)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF(x_stl_my_integer(), '(1)'::my_integer),
+  NULLIF(x_stl_my_integer(), '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  s my_integer
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT equal_integers_stl(NULLIF(x_stl(), 1), NULLIF(x_stl(), 2))
+FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ equal_integers_stl 
+--------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing (partly in nesting "scalar op ANY/ALL (array)" /
+-- "scalar IN (2 or more values)" expressions testing)
+SELECT 1 ==== ANY(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+NOTICE:  v array_int
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY(x_stl_array_int()) FROM generate_series(1, 4) x;
+NOTICE:  s array_int
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL(x_stl_array_int()) FROM generate_series(1, 4) x;
+NOTICE:  s array_int
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed operators and IS DISTINCT FROM expressions testing
+-- should not be precalculated
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed operators and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === 2, TRUE)) FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULLIF(1 ==== 2, TRUE)) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed operators and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT (1 === ANY('{2, 3}')) ====== (1 === ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== ANY('{2, 3}')) ====== (1 ==== ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean((1 ==== 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed IS DISTINCT FROM and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ t
+ t
+ t
+ t
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed IS DISTINCT FROM and "scalar op ANY/ALL (array)" / "scalar IN (2 or
+-- more values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  (1 === ANY('{2, 3}')) IS DISTINCT FROM
+  (1 === ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  (1 ==== ANY('{2, 3}')) IS DISTINCT FROM
+  (1 ==== ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed NULLIF and "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === ANY('{2, 3}'), 1 === ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULLIF(1 ==== ANY('{2, 3}'), 1 ==== ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      2
+(1 row)
+
+INSERT INTO two VALUES (3);
+SELECT simple();
+ simple 
+--------
+      3
+(1 row)
+
+ROLLBACK;
+-- Drop tables for testing
+DROP TABLE two;
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 04206c3..f2710b9 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -179,3 +179,4 @@ test: with
test: xml
test: event_trigger
test: stats
+test: precalculate_stable_functions
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
new file mode 100644
index 0000000..a59791d
--- /dev/null
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -0,0 +1,949 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+
+-- Create types and tables for testing
+
+CREATE TYPE my_integer AS (value integer);
+
+CREATE TABLE two (i integer);
+INSERT INTO two VALUES (1), (2);
+
+-- Create volatile functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_array_int (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create stable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_array_int (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_int';
+  RETURN '{2, 3}'::int[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(i) from two);
+END
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create immutable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create operators for testing
+
+CREATE operator === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE operator ====== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+CREATE operator ==== (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- Simple functions testing
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+-- WHERE clause testing
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+-- Functions with constant arguments and nested functions testing
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions testing
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions with null arguments testing
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+-- Operators testing
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+
+-- Nested and strict operators testing
+-- (also partly mixed functions and operators testing)
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (x_stl() ==== 2) ====== (x_stl() ===== 3) FROM generate_series(1, 4) x;
+SELECT (1 ==== 2) ====== x_stl2_boolean(NULL) FROM generate_series(1, 4) x;
+
+-- IS DISTINCT FROM expression testing
+
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer
+FROM generate_series(1, 4) x;
+
+-- IS DISTINCT FROM expressions with null arguments testing
+
+SELECT x_stl2_boolean(1 IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL))
+FROM generate_series(1, 4) x;
+
+-- Nested IS DISTINCT FROM expression testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+-- NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- NULLIF expressions with null arguments testing
+
+SELECT x_stl2(NULLIF(1, x_stl2(NULL))) FROM generate_series(1, 4) x;
+
+SELECT x_stl2(NULLIF(x_stl2(NULL), x_stl2(NULL))) FROM generate_series(1, 4) x;
+
+-- Nested NULLIF expression testing
+
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF(NULLIF(x_vlt_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(NULLIF(x_stl_my_integer(), '(2)'::my_integer), '(2)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+
+SELECT 1 === ANY('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 === ALL('{2, 3}') FROM generate_series(1, 4) x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+
+SELECT 1 ==== ANY('{2, 3}') FROM generate_series(1, 4) x;
+SELECT 1 ==== ALL('{2, 3}') FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+FROM generate_series(1, 4) x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+
+SELECT 1 ==== ANY('{2, NULL}') FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== ANY(NULL)) FROM generate_series(1, 4) x;
+SELECT NULL ==== ANY('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+SELECT NULL ==== ANY('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL::int ==== ANY(NULL)) FROM generate_series(1, 4) x;
+
+SELECT 1 ==== ALL('{2, NULL}') FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== ALL(NULL)) FROM generate_series(1, 4) x;
+SELECT NULL ==== ALL('{2, 3}'::int[]) FROM generate_series(1, 4) x;
+SELECT NULL ==== ALL('{2, NULL}'::int[]) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL::int ==== ALL(NULL)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM generate_series(1, 4) x;
+
+-- Nesting "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing (also partly mixed functions and "scalar op ANY/ALL
+-- (array)" / "scalar IN (2 or more values)" expressions testing)
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ==== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((x_stl() ===== ANY('{2, 3}')) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and operators testing
+-- (most of it was earlier in Nested and strict operators testing)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+-- Mixed functions and IS DISTINCT FROM expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer),
+  (x_stl_my_integer() IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT equal_booleans_stl_strict(
+  (x_stl() IS DISTINCT FROM 1),
+  (x_stl() IS DISTINCT FROM 2)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and NULLIF expressions testing
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF(x_stl_my_integer(), '(1)'::my_integer),
+  NULLIF(x_stl_my_integer(), '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT equal_integers_stl(NULLIF(x_stl(), 1), NULLIF(x_stl(), 2))
+FROM generate_series(1, 4) x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing (partly in nesting "scalar op ANY/ALL (array)" /
+-- "scalar IN (2 or more values)" expressions testing)
+
+SELECT 1 ==== ANY(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== ALL(x_vlt_array_int()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT 1 ==== ANY(x_stl_array_int()) FROM generate_series(1, 4) x;
+SELECT 1 ==== ALL(x_stl_array_int()) FROM generate_series(1, 4) x;
+
+-- Mixed operators and IS DISTINCT FROM expressions testing
+
+-- should not be precalculated
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) ======
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) IS DISTINCT FROM TRUE)
+FROM generate_series(1, 4) x;
+
+-- Mixed operators and NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === 2, TRUE)) FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(NULLIF(1 ==== 2, TRUE)) FROM generate_series(1, 4) x;
+
+-- Mixed operators and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT (1 === ANY('{2, 3}')) ====== (1 === ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean((1 === 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+SELECT (1 ==== ANY('{2, 3}')) ====== (1 ==== ALL('{2, 3}'))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) ====== TRUE
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) = ANY('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) = ALL('{TRUE}'))
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean((1 ==== 2) IN (TRUE, FALSE))
+FROM generate_series(1, 4) x;
+
+-- Mixed IS DISTINCT FROM and NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IS DISTINCT FROM
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT NULLIF(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer),
+  ('(2)'::my_integer IS DISTINCT FROM '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed IS DISTINCT FROM and "scalar op ANY/ALL (array)" / "scalar IN (2 or
+-- more values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  (1 === ANY('{2, 3}')) IS DISTINCT FROM
+  (1 === ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  (1 ==== ANY('{2, 3}')) IS DISTINCT FROM
+  (1 ==== ALL('{2, 3}'))
+)
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)) IS DISTINCT FROM
+  TRUE
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ANY('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) = ALL('{TRUE}')
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(
+  ('(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer) IN (TRUE, FALSE)
+)
+FROM generate_series(1, 4) x;
+
+-- Mixed NULLIF and "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)"
+-- expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(1 === ANY('{2, 3}'), 1 === ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+-- should not be precalculated
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+SELECT x_stl2_boolean(NULLIF(1 ==== ANY('{2, 3}'), 1 ==== ALL('{2, 3}')))
+FROM generate_series(1, 4) x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(NULLIF(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer),
+  TRUE
+))
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ANY('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) ====
+  ALL('{(3)}'::my_integer[])
+)
+FROM generate_series(1, 4) x;
+
+SELECT (
+  NULLIF('(1)'::my_integer, '(2)'::my_integer) IN
+  ('(3)'::my_integer, '(2)'::my_integer)
+)
+FROM generate_series(1, 4) x;
+
+-- Tracking functions testing
+
+SET track_functions TO 'all';
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+SET track_functions TO DEFAULT;
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+
+BEGIN;
+SELECT simple();
+INSERT INTO two VALUES (3);
+SELECT simple();
+ROLLBACK;
+
+-- Drop tables for testing
+
+DROP TABLE two;
-- 
1.9.1

From 2382fa68414f6bbed42ff66c7abbc3c9b200d244 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 15 May 2017 16:05:38 +0300
Subject: [PATCH v4 3/3] Precalculate stable functions, costs

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:
- cost changes for cached expressions (according to their behaviour)
---
src/backend/optimizer/path/costsize.c | 89 ++++++++++++++++++++++++++---------
1 file changed, 67 insertions(+), 22 deletions(-)

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 52643d0..505772a 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -140,6 +140,7 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root,
PathKey *pathkey);
static void cost_rescan(PlannerInfo *root, Path *path,
Cost *rescan_startup_cost, Cost *rescan_total_cost);
+static double cost_eval_cacheable_expr_per_tuple(Node *node);
static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
ParamPathInfo *param_info,
@@ -3464,6 +3465,59 @@ cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root)
*cost = context.total;
}
+/*
+ * cost_eval_cacheable_expr_per_tuple
+ *		Evaluate per tuple cost for expressions that can be cacheable.
+ *
+ * This function was created to not duplicate code for some expression and
+ * cached some expression.
+ */
+static double
+cost_eval_cacheable_expr_per_tuple(Node *node)
+{
+	double result;
+
+	/*
+	 * For each operator or function node in the given tree, we charge the
+	 * estimated execution cost given by pg_proc.procost (remember to multiply
+	 * this by cpu_operator_cost).
+	 */
+	if (IsA(node, FuncExpr))
+	{
+		result = get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+	}
+	else if (IsA(node, OpExpr) ||
+			 IsA(node, DistinctExpr) ||
+			 IsA(node, NullIfExpr))
+	{
+		OpExpr     *opexpr = (OpExpr *) node;
+
+		/* rely on struct equivalence to treat these all alike */
+		set_opfuncid(opexpr);
+
+		result = get_func_cost(opexpr->opfuncid) * cpu_operator_cost;
+	}
+	else if (IsA(node, ScalarArrayOpExpr))
+	{
+		/*
+		 * Estimate that the operator will be applied to about half of the
+		 * array elements before the answer is determined.
+		 */
+		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
+		Node	   *arraynode = (Node *) lsecond(saop->args);
+
+		set_sa_opfuncid(saop);
+		result = get_func_cost(saop->opfuncid) * cpu_operator_cost *
+			estimate_array_length(arraynode) * 0.5;
+	}
+	else
+	{
+		elog(ERROR, "non cacheable expression node type: %d", (int) nodeTag(node));
+	}
+
+	return result;
+}
+
static bool
cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
{
@@ -3537,32 +3591,23 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
* moreover, since our rowcount estimates for functions tend to be pretty
* phony, the results would also be pretty phony.
*/
-	if (IsA(node, FuncExpr))
+	if (IsA(node, FuncExpr) ||
+		IsA(node, OpExpr) ||
+		IsA(node, DistinctExpr) ||
+		IsA(node, NullIfExpr) ||
+		IsA(node, ScalarArrayOpExpr))
{
-		context->total.per_tuple +=
-			get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+		context->total.per_tuple += cost_eval_cacheable_expr_per_tuple(node);
}
-	else if (IsA(node, OpExpr) ||
-			 IsA(node, DistinctExpr) ||
-			 IsA(node, NullIfExpr))
-	{
-		/* rely on struct equivalence to treat these all alike */
-		set_opfuncid((OpExpr *) node);
-		context->total.per_tuple +=
-			get_func_cost(((OpExpr *) node)->opfuncid) * cpu_operator_cost;
-	}
-	else if (IsA(node, ScalarArrayOpExpr))
-	{
+	else if (IsA(node, CachedExpr))
+	{	
/*
-		 * Estimate that the operator will be applied to about half of the
-		 * array elements before the answer is determined.
+		 * Calculate subexpression cost per tuple as usual and add it to startup
+		 * cost (because subexpression will be executed only once for all
+		 * tuples).
*/
-		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
-		Node	   *arraynode = (Node *) lsecond(saop->args);
-
-		set_sa_opfuncid(saop);
-		context->total.per_tuple += get_func_cost(saop->opfuncid) *
-			cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
+		context->total.startup += cost_eval_cacheable_expr_per_tuple(
+			get_subexpr((CachedExpr *) node));
}
else if (IsA(node, Aggref) ||
IsA(node, WindowFunc))
-- 
1.9.1

--
Best regards,
Aleksander Alekseev

#11Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Aleksander Alekseev (#10)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hi Marina,

Hello again!

I still don't see anything particularly wrong with your patch. It
applies, passes all test, it is well test-covered and even documented.
Also I've run `make installcheck` under Valgrind and didn't find any
memory-related errors.

Thank you very much as usual!

Is there anything that you would like to change before we call it more
or less final?

I would like to add some primitive nodes for precalculation if their
behaviour allows to do it and their arguments/inputs are constant or
precalculated too; and regression tests for it, of course. Also I would
like to add some notes about precalculation of stable functions in
documentation, for example, here [1]https://www.postgresql.org/docs/10/static/xfunc-volatility.html and here [2]https://www.postgresql.org/docs/10/static/sql-createfunction.html -- Marina Polyakova Postgres Professional: http://www.postgrespro.com The Russian Postgres Company.

Also I would advice to add your branch to our internal buildfarm just
to
make sure everything is OK on exotic platforms like Windows ;)

Thanks! Done)

[1]: https://www.postgresql.org/docs/10/static/xfunc-volatility.html
[2]: https://www.postgresql.org/docs/10/static/sql-createfunction.html -- Marina Polyakova Postgres Professional: http://www.postgrespro.com The Russian Postgres Company
--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Marina Polyakova (#11)
3 attachment(s)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hello, hackers!

Here I have made the 5th version of the patches. I have added the
precalculation of all primitive nodes that don't return set, are not
volatile themselves and their arguments are constant or precalculated
expressions too. There're regression tests for all of them and little
notes in the documentation. Like for the previous patches it seems that
there is no obvious performance degradation too on regular queries
(according to pgbench).

About functionality: precalculation doesn't work for parameters nodes in
plan. So it doesn't work, for example, in the generic plans of prepared
statements. There'll be an another patch for them.

About code:
* Infrastructure patch changes: no enum and union for all 25
precalculated node types. Instead of them there is a new CacheableExpr
node which only contains a NodeTag.
* There're some changes for CoerceToDomain, which constraints now are
checked not only in the executor but in the planner too.

Patches are attached. Any suggestions are welcome!

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v5-0001-Precalculate-stable-functions-infrastructure.patchtext/x-diff; charset=us-ascii; name=v5-0001-Precalculate-stable-functions-infrastructure.patchDownload
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

v5-0002-Precalculate-stable-functions-planning-and-execut.patchtext/x-diff; charset=us-ascii; name=v5-0002-Precalculate-stable-functions-planning-and-execut.patchDownload
From 6cf52e350abf475901e52c3436f015e5020d17c9 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 17 Jul 2017 11:08:42 +0300
Subject: [PATCH v5 2/3] Precalculate stable functions, planning and execution

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:
- replacement nonvolatile expressions by appropriate cached expressions
- planning and execution cached expressions
- regression tests
- changed documentation
---
 doc/src/sgml/ref/create_function.sgml              |   14 +
 doc/src/sgml/xfunc.sgml                            |   14 +-
 src/backend/executor/execExpr.c                    |   32 +
 src/backend/executor/execExprInterp.c              |   52 +
 src/backend/optimizer/path/allpaths.c              |    9 +-
 src/backend/optimizer/path/clausesel.c             |   12 +
 src/backend/optimizer/plan/planagg.c               |    1 +
 src/backend/optimizer/plan/planner.c               |   40 +
 src/backend/optimizer/util/clauses.c               |   26 +
 src/backend/utils/adt/ruleutils.c                  |    5 +
 src/include/executor/execExpr.h                    |   31 +
 src/include/optimizer/planner.h                    |    5 +
 src/include/optimizer/tlist.h                      |    8 +-
 src/pl/plpgsql/src/pl_exec.c                       |   10 +
 .../expected/precalculate_stable_functions.out     | 6306 ++++++++++++++++++++
 src/test/regress/serial_schedule                   |    1 +
 .../regress/sql/precalculate_stable_functions.sql  | 1843 ++++++
 17 files changed, 8405 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/precalculate_stable_functions.out
 create mode 100644 src/test/regress/sql/precalculate_stable_functions.sql

diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 072e033..dfdc3b5 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -337,6 +337,20 @@ CREATE [ OR REPLACE ] FUNCTION
        <literal>setval()</>.
       </para>
 
+      <note>
+       <para>
+        Stable, immutable functions and other nonovolatile expressions are
+        precalculated (= calculated once for all output rows, but as many times
+        as expression is mentioned in query), if they don't return a set and
+        their arguments are constants or recursively precalculated expressions.
+       </para>
+
+       <para>
+        Now this feature is not supported for the generic plans of prepared
+        statements (see <xref linkend="SQL-PREPARE">).
+       </para>
+      </note>
+
       <para>
        For additional details see <xref linkend="xfunc-volatility">.
       </para>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index cd6dd84..f97b637 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1467,9 +1467,21 @@ CREATE FUNCTION test(int, int) RETURNS int
 
    <para>
     For best optimization results, you should label your functions with the
-    strictest volatility category that is valid for them.
+    strictest volatility category that is valid for them. Stable, immutable
+    functions and other nonovolatile expressions are precalculated (= calculated
+    once for all output rows, but as many times as expression is mentioned in
+    query), if they don't return a set and their arguments are constants or
+    recursively precalculated expressions.
    </para>
 
+   <note>
+    <para>
+     Now nonvolatile expressions with constant/precalculated arguments are not
+     precalculated in the generic plans of prepared statements (see <xref
+     linkend="SQL-PREPARE">).
+    </para>
+   </note>
+
    <para>
     Any function with side-effects <emphasis>must</> be labeled
     <literal>VOLATILE</>, so that calls to it cannot be optimized away.
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 3523543..247b43f 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -791,6 +791,38 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
 				break;
 			}
 
+		case T_CachedExpr:
+			{
+				/*
+				 * Allocate CachedExprState used by all steps of CachedExpr
+				 * evaluation.
+				 */
+				scratch.d.cachedexpr.state = (CachedExprState *) palloc(
+					sizeof(CachedExprState));
+				scratch.d.cachedexpr.state->isExecuted = false;
+				scratch.d.cachedexpr.state->resnull = false;
+				scratch.d.cachedexpr.state->resvalue = (Datum) 0;
+				scratch.d.cachedexpr.state->restypid = exprType(
+					(const Node *) node);
+
+				/* add EEOP_CACHEDEXPR_IF_CACHED step */
+				scratch.opcode = EEOP_CACHEDEXPR_IF_CACHED;
+				ExprEvalPushStep(state, &scratch);
+
+				/* add subexpression steps */
+				ExecInitExprRec((Expr *) ((CachedExpr *) node)->subexpr, parent,
+								state, resv, resnull);
+
+				/* add EEOP_CACHEDEXPR_SUBEXPR_END step */
+				scratch.opcode = EEOP_CACHEDEXPR_SUBEXPR_END;
+				ExprEvalPushStep(state, &scratch);
+
+				/* adjust jump target */
+				scratch.d.cachedexpr.state->jumpdone = state->steps_len;
+
+				break;
+			}
+
 		case T_ArrayRef:
 			{
 				ArrayRef   *aref = (ArrayRef *) node;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index c227d9b..1a810e9 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -70,6 +70,7 @@
 #include "pgstat.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
+#include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
@@ -363,6 +364,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_WINDOW_FUNC,
 		&&CASE_EEOP_SUBPLAN,
 		&&CASE_EEOP_ALTERNATIVE_SUBPLAN,
+		&&CASE_EEOP_CACHEDEXPR_IF_CACHED,
+		&&CASE_EEOP_CACHEDEXPR_SUBEXPR_END,
 		&&CASE_EEOP_LAST
 	};
 
@@ -1500,6 +1503,55 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_CACHEDEXPR_IF_CACHED)
+		{
+			if (op->d.cachedexpr.state->isExecuted)
+			{
+				/* use saved result and skip subexpression evaluation */
+				*op->resnull = op->d.cachedexpr.state->resnull;
+				if (!(*op->resnull))
+					*op->resvalue = op->d.cachedexpr.state->resvalue;
+
+				EEO_JUMP(op->d.cachedexpr.state->jumpdone);
+			}
+
+			/* we are ready for subexpression evaluation */
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHEDEXPR_SUBEXPR_END)
+		{
+			int16		restyplen;
+			bool		restypbyval;
+			MemoryContext oldContext;
+
+			/* save result */
+			op->d.cachedexpr.state->resnull = *op->resnull;
+			if (!(*op->resnull))
+			{
+				get_typlenbyval(op->d.cachedexpr.state->restypid, &restyplen,
+								&restypbyval);
+
+				/*
+				 * Switch per-query memory context. It is necessary to save the
+				 * subexpression result between all tuples if its value datum is
+				 * a pointer.
+				 */
+				oldContext = MemoryContextSwitchTo(
+					econtext->ecxt_per_query_memory);
+
+				op->d.cachedexpr.state->resvalue = datumCopy(*op->resvalue,
+															 restypbyval,
+															 restyplen);
+
+				/* switch memory context back */
+				MemoryContextSwitchTo(oldContext);
+			}
+			op->d.cachedexpr.state->isExecuted = true;
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_SUBPLAN)
 		{
 			/* too complex for an inline implementation */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index f087ddb..341b519 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -378,7 +378,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 				set_subquery_pathlist(root, rel, rti, rte);
 				break;
 			case RTE_FUNCTION:
-				set_function_size_estimates(root, rel);
+				{
+					rel->baserestrictinfo = replace_qual_cached_expressions(
+						rel->baserestrictinfo, root);
+					set_function_size_estimates(root, rel);
+				}
 				break;
 			case RTE_TABLEFUNC:
 				set_tablefunc_size_estimates(root, rel);
@@ -517,6 +521,9 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	 */
 	check_index_predicates(root, rel);
 
+	rel->baserestrictinfo = replace_qual_cached_expressions(
+		rel->baserestrictinfo, root);
+
 	/* Mark rel with estimated output rows, width, etc */
 	set_baserel_size_estimates(root, rel);
 }
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 9d34025..a504336 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -825,6 +825,18 @@ clause_selectivity(PlannerInfo *root,
 								jointype,
 								sjinfo);
 	}
+	else if (IsA(clause, CachedExpr))
+	{
+		/*
+		 * Not sure this case is needed, but it can't hurt.
+		 * Calculate selectivity of subexpression.
+		 */
+		s1 = clause_selectivity(root,
+								(Node *) ((CachedExpr *) clause)->subexpr,
+								varRelid,
+								jointype,
+								sjinfo);
+	}
 	else
 	{
 		/*
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index bba8a1f..a8b7991 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -38,6 +38,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "optimizer/planner.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bce4a41..0490d56 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6102,6 +6102,46 @@ get_partitioned_child_rels(PlannerInfo *root, Index rti)
 	return result;
 }
 
+/*
+ * replace_pathtarget_cached_expressions
+ *		Replace cached expresisons in a PathTarget tlist.
+ *
+ * As a notational convenience, returns the same PathTarget pointer passed in.
+ */
+PathTarget *
+replace_pathtarget_cached_expressions(PathTarget *target, PlannerInfo *root)
+{
+	replace_cached_expressions_context context;
+
+	context.root = root;
+	context.innermost_caseexpr_nonconst_or_noncached_testvalue = NULL;
+	context.innermost_coercetodomain_nonconst_or_noncached_value = NULL;
+
+	target->exprs = (List *) replace_cached_expressions_mutator(
+		(Node *) target->exprs, &context);
+
+	return target;
+}
+
+/*
+ * replace_qual_cached_expressions
+ *		Replace cacehd expressions in a WHERE clause. The input can be either an
+ *		implicitly-ANDed list of boolean expressions, or a list of RestrictInfo
+ *		nodes.
+ */
+List *
+replace_qual_cached_expressions(List *quals, PlannerInfo *root)
+{
+	replace_cached_expressions_context context;
+
+	context.root = root;
+	context.innermost_caseexpr_nonconst_or_noncached_testvalue = NULL;
+	context.innermost_coercetodomain_nonconst_or_noncached_value = NULL;
+
+	return (List *) replace_cached_expressions_mutator((Node *) quals,
+													   &context);
+}
+
 static Node *
 replace_cached_expressions_mutator(Node *node,
 								   replace_cached_expressions_context *context)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8961ed8..7b72335 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2553,6 +2553,32 @@ eval_const_expressions_mutator(Node *node,
 
 				return (Node *) newexpr;
 			}
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+				CacheableExpr *new_subexpr = (CacheableExpr *)
+					eval_const_expressions_mutator((Node *) cachedexpr->subexpr,
+												   context);
+				CachedExpr *new_cachedexpr;
+
+				if (IsA(new_subexpr, Const))
+				{
+					/* successfully simplified it */
+					return (Node *) new_subexpr;
+				}
+				else
+				{
+					/*
+					 * The expression cannot be simplified any further, so build
+					 * and return a replacement CachedExpr node using the
+					 * possibly-simplified arguments of subexpression.
+					 */
+					new_cachedexpr = makeNode(CachedExpr);
+					new_cachedexpr->subexpr = new_subexpr;
+
+					return (Node *) new_cachedexpr;
+				}
+			}
 		case T_FuncExpr:
 			{
 				FuncExpr   *expr = (FuncExpr *) node;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 5cfb916..80dd539 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7629,6 +7629,11 @@ get_rule_expr(Node *node, deparse_context *context,
 			get_windowfunc_expr((WindowFunc *) node, context);
 			break;
 
+		case T_CachedExpr:
+			get_rule_expr((Node *) ((CachedExpr *) node)->subexpr, context,
+						  showimplicit);
+			break;
+
 		case T_ArrayRef:
 			{
 				ArrayRef   *aref = (ArrayRef *) node;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 7a65339..da0c74e 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -212,6 +212,16 @@ typedef enum ExprEvalOp
 	EEOP_SUBPLAN,
 	EEOP_ALTERNATIVE_SUBPLAN,
 
+	/*
+	 * Evaluate CachedExpr.  EEOP_CACHEDEXPR_IF_CACHED is used before
+	 * subexpression evaluation (if subexpression was evaluated use cached value
+	 * and jump to next state or get prepared to subexpression evaluation
+	 * otherwise).  EEOP_CACHEDEXPR_SUBEXPR_END is used after subexpression
+	 * evaluation for caching its result.
+	 */
+	EEOP_CACHEDEXPR_IF_CACHED,
+	EEOP_CACHEDEXPR_SUBEXPR_END,
+
 	/* non-existent operation, used e.g. to check array lengths */
 	EEOP_LAST
 } ExprEvalOp;
@@ -560,6 +570,13 @@ typedef struct ExprEvalStep
 			/* out-of-line state, created by nodeSubplan.c */
 			AlternativeSubPlanState *asstate;
 		}			alternative_subplan;
+
+		/* for EEOP_CACHEDEXPR_* */
+		struct
+		{
+			/* steps for evaluation the same CachedExpr have the same state */
+			struct CachedExprState *state;
+		}			cachedexpr;
 	}			d;
 } ExprEvalStep;
 
@@ -600,6 +617,20 @@ typedef struct ArrayRefState
 } ArrayRefState;
 
 
+/*
+ * Non-inline data for EEOP_CACHEDEXPR_* operations (steps for evaluation the
+ * same CachedExpr have the same state).
+ */
+typedef struct CachedExprState
+{
+	bool		isExecuted;
+	bool		resnull;
+	Datum		resvalue;
+	Oid 		restypid;		/* for copying resvalue of subexpression */
+	int			jumpdone;		/* jump here if result determined */
+} CachedExprState;
+
+
 extern void ExecReadyInterpretedExpr(ExprState *state);
 
 extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 2a4cf71..1a64696 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -59,4 +59,9 @@ extern bool plan_cluster_use_sort(Oid tableOid, Oid indexOid);
 
 extern List *get_partitioned_child_rels(PlannerInfo *root, Index rti);
 
+extern PathTarget *replace_pathtarget_cached_expressions(PathTarget *target,
+														 PlannerInfo *root);
+
+extern List *replace_qual_cached_expressions(List *quals, PlannerInfo *root);
+
 #endif							/* PLANNER_H */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index 0d3ec92..1b4fd8f 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -65,8 +65,12 @@ extern void split_pathtarget_at_srfs(PlannerInfo *root,
 						 PathTarget *target, PathTarget *input_target,
 						 List **targets, List **targets_contain_srfs);
 
-/* Convenience macro to get a PathTarget with valid cost/width fields */
+/*
+ * Convenience macro to get a PathTarget with valid cost/width fields and
+ * cached expressions.
+ */
 #define create_pathtarget(root, tlist) \
-	set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
+	set_pathtarget_cost_width(root, replace_pathtarget_cached_expressions( \
+		make_pathtarget_from_tlist(tlist), root))
 
 #endif							/* TLIST_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index c98492b..16199f1 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -6471,6 +6471,16 @@ exec_simple_check_node(Node *node)
 		case T_Param:
 			return TRUE;
 
+		case T_CachedExpr:
+			{
+				/*
+				 * If CachedExpr will not be initialized by ExecInitCachedExpr
+				 * possibly it will use cached value when it shouldn't (for
+				 * example, snapshot has changed), so return false.
+				 */
+				return FALSE;
+			}
+
 		case T_ArrayRef:
 			{
 				ArrayRef   *expr = (ArrayRef *) node;
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
new file mode 100644
index 0000000..fd187f5
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -0,0 +1,6306 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+-- Create domains for testing
+CREATE DOMAIN my_integer_no_check AS integer;
+CREATE DOMAIN my_integer_not_null AS integer;
+CREATE DOMAIN my_integer_vlt_check AS integer CHECK (VALUE === 1);
+CREATE DOMAIN my_integer_stl_check AS integer CHECK (VALUE ==== 1);
+CREATE DOMAIN my_integer_imm_check AS integer CHECK (VALUE ===== 1);
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_no_check (
+  my_integer_no_check
+)
+RETURNS my_integer_no_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_no_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_not_null (
+  my_integer_not_null
+)
+RETURNS my_integer_not_null STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_not_null';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_vlt_check (
+  my_integer_vlt_check
+)
+RETURNS my_integer_vlt_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_stl_check (
+  my_integer_stl_check
+)
+RETURNS my_integer_stl_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_imm_check (
+  my_integer_imm_check
+)
+RETURNS my_integer_imm_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_imm_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Functions testing
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x; -- should not be precalculated
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x; -- should not be precalculated
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x; -- should not be precalculated
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x; -- should not be precalculated
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Boolean expressions testing
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- ARRAY[] expressions testing
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Multidimensional ARRAY[] expressions testing
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+-- Array subscripting operations testing
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- FieldSelect expressions testing
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- CoerceViaIO expressions testing
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x; -- should not be precalculated
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x; -- should not be precalculated
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- ArrayCoerce expressions testing
+-- Binary-coercible types:
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x; -- should not be precalculated
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+SELECT x_vlt_array_integer()::my_integer[] FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x; -- should not be precalculated
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x; -- should not be precalculated
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- CASE expressions testing
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- RowCompareExpr testing
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- COALESCE expressions testing
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- SQLValueFunction testing
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+SELECT x_stl2_name(current_role) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(current_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(session_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+(4 rows)
+
+SELECT x_stl2_name(current_catalog) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ regression
+ regression
+ regression
+ regression
+(4 rows)
+
+SELECT x_stl2_name(current_schema) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ public
+ public
+ public
+ public
+(4 rows)
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+-- Xml expressions testing
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+NOTICE:  s2 xml
+          x_stl2_xml          
+------------------------------
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+NOTICE:  s2 xml
+          x_stl2_xml          
+------------------------------
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+NOTICE:  s2 xml
+                          x_stl2_xml                          
+--------------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+NOTICE:  s2 text
+                       x_stl2_text                       
+---------------------------------------------------------
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+NOTICE:  s text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- NullTest expressions testing
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- BooleanTest expressions testing
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- CoerceToDomain expressions testing
+SELECT x_stl2_my_integer_no_check(1::my_integer_no_check) FROM x;
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(1::my_integer_not_null) FROM x;
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_vlt_check(1::my_integer_vlt_check) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+ x_stl2_my_integer_vlt_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(1::my_integer_stl_check) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(1::my_integer_imm_check) FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- Mixed functions and CoerceToDomain expressions testing
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and boolean expressions testing
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and ARRAY[] expressions testing
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x; -- should not be precalculated
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x; -- should not be precalculated
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x; -- should not be precalculated
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+SELECT x_vlt_array_integer()::my_integer[] FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x; -- should not be precalculated
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x; -- should not be precalculated
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and CASE expressions testing
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and COALESCE expressions testing
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+NOTICE:  s text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and NullTest expressions testing
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and BooleanTest expressions testing
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x; -- should not be precalculated
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and CoerceToDomain expressions testing
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- ROW() expressions with dropped columns testing
+ALTER TABLE wxyz DROP COLUMN z;
+VACUUM FULL;
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      4
+(1 row)
+
+INSERT INTO x VALUES (5);
+SELECT simple();
+ simple 
+--------
+      5
+(1 row)
+
+ROLLBACK;
+-- Drop tables and domains for testing
+DROP TABLE x;
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
+DROP FUNCTION x_stl2_my_integer_no_check;
+DROP DOMAIN my_integer_no_check;
+DROP FUNCTION x_stl2_my_integer_not_null;
+DROP DOMAIN my_integer_not_null;
+DROP FUNCTION x_stl2_my_integer_vlt_check;
+DROP DOMAIN my_integer_vlt_check;
+DROP FUNCTION x_stl2_my_integer_stl_check;
+DROP DOMAIN my_integer_stl_check;
+DROP FUNCTION x_stl2_my_integer_imm_check;
+DROP DOMAIN my_integer_imm_check;
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 5e8b7e9..f73a64f 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -180,3 +180,4 @@ test: with
 test: xml
 test: event_trigger
 test: stats
+test: precalculate_stable_functions
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
new file mode 100644
index 0000000..43444b8
--- /dev/null
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -0,0 +1,1843 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+
+-- Create volatile functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create stable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create immutable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create operators for testing
+
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+-- Create domains for testing
+
+CREATE DOMAIN my_integer_no_check AS integer;
+CREATE DOMAIN my_integer_not_null AS integer;
+CREATE DOMAIN my_integer_vlt_check AS integer CHECK (VALUE === 1);
+CREATE DOMAIN my_integer_stl_check AS integer CHECK (VALUE ==== 1);
+CREATE DOMAIN my_integer_imm_check AS integer CHECK (VALUE ===== 1);
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_no_check (
+  my_integer_no_check
+)
+RETURNS my_integer_no_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_no_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_not_null (
+  my_integer_not_null
+)
+RETURNS my_integer_not_null STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_not_null';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_vlt_check (
+  my_integer_vlt_check
+)
+RETURNS my_integer_vlt_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_stl_check (
+  my_integer_stl_check
+)
+RETURNS my_integer_stl_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_imm_check (
+  my_integer_imm_check
+)
+RETURNS my_integer_imm_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_imm_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Functions testing
+
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+SELECT x_stl() FROM x;
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM x WHERE x_stl() < x;
+
+-- JOIN/ON clause testing
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x; -- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+SELECT x_stl2(1) FROM x;
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2(x_stl()) FROM x;
+SELECT x_imm2(x_stl()) FROM x;
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+
+-- Operators testing
+
+SELECT 1 === 2 FROM x; -- should not be precalculated
+SELECT 1 ==== 2 FROM x;
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+SELECT x_stl() ==== 1 FROM x;
+
+-- IS (NOT) DISTINCT FROM expression testing
+
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x; -- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x; -- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x; -- should not be precalculated
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x; -- should not be precalculated
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x; -- should not be precalculated
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x; -- should not be precalculated
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- Boolean expressions testing
+
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+
+-- ARRAY[] expressions testing
+
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x; -- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+
+-- Multidimensional ARRAY[] expressions testing
+
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x; -- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+
+-- Array subscripting operations testing
+
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+
+-- Mixed functions and array subscripting operations testing
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x; -- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x; -- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+
+-- FieldSelect expressions testing
+
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+
+-- ROW() expressions testing
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x; -- should not be precalculated
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+
+-- RelabelType expressions testing
+
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+
+-- CoerceViaIO expressions testing
+
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x; -- should not be precalculated
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+
+-- Mixed functions and CoerceViaIO expressions testing
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x; -- should not be precalculated
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+
+-- ArrayCoerce expressions testing
+
+-- Binary-coercible types:
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x; -- should not be precalculated
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x; -- should not be precalculated
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+SELECT x_vlt_array_integer()::my_integer[] FROM x; -- should not be precalculated
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+
+-- ConvertRowtypeExpr testing
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+
+-- Mixed functions and ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x; -- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x; -- should not be precalculated
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+-- CASE expressions testing
+
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x; -- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x; -- should not be precalculated
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+
+-- RowCompareExpr testing
+
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+
+-- Mixed functions and RowCompareExpr testing
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+
+-- COALESCE expressions testing
+
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x; -- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+
+-- GREATEST and LEAST functions testing
+
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+
+-- SQLValueFunction testing
+
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+
+SELECT x_stl2_name(current_role) FROM x;
+SELECT x_stl2_name(current_user) FROM x;
+SELECT x_stl2_name(user) FROM x;
+SELECT x_stl2_name(session_user) FROM x;
+SELECT x_stl2_name(current_catalog) FROM x;
+SELECT x_stl2_name(current_schema) FROM x;
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+
+-- Xml expressions testing
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+
+-- NullTest expressions testing
+
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x; -- should not be precalculated
+
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x; -- should not be precalculated
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+
+-- BooleanTest expressions testing
+
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x; -- should not be precalculated
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+
+-- CoerceToDomain expressions testing
+
+SELECT x_stl2_my_integer_no_check(1::my_integer_no_check) FROM x;
+SELECT x_stl2_my_integer_not_null(1::my_integer_not_null) FROM x;
+
+SELECT x_stl2_my_integer_vlt_check(1::my_integer_vlt_check) FROM x; -- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(1::my_integer_stl_check) FROM x;
+SELECT x_stl2_my_integer_imm_check(1::my_integer_imm_check) FROM x;
+
+-- Mixed functions and CoerceToDomain expressions testing
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x; -- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x; -- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x; -- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x; -- should not be precalculated
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+
+-- Tracking functions testing
+
+SET track_functions TO 'all';
+
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+SELECT x_stl() FROM x;
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM x WHERE x_stl() < x;
+
+-- JOIN/ON clause testing
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x; -- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+SELECT x_stl2(1) FROM x;
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2(x_stl()) FROM x;
+SELECT x_imm2(x_stl()) FROM x;
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+SELECT 1 ==== 2 FROM x;
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+SELECT x_stl() ==== 1 FROM x;
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x; -- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x; -- should not be precalculated
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x; -- should not be precalculated
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x; -- should not be precalculated
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- Mixed functions and boolean expressions testing
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+
+-- Mixed functions and ARRAY[] expressions testing
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x; -- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+
+-- Mixed functions and array subscripting operations testing
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x; -- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x; -- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x; -- should not be precalculated
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+
+-- Mixed functions and CoerceViaIO expressions testing
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x; -- should not be precalculated
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x; -- should not be precalculated
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x; -- should not be precalculated
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x; -- should not be precalculated
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+SELECT x_vlt_array_integer()::my_integer[] FROM x; -- should not be precalculated
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+
+-- Mixed functions and ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x; -- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x; -- should not be precalculated
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+-- Mixed functions and CASE expressions testing
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x; -- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x; -- should not be precalculated
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+
+-- Mixed functions and RowCompareExpr testing
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+
+-- Mixed functions and COALESCE expressions testing
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x; -- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+
+-- Mixed functions and NullTest expressions testing
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x; -- should not be precalculated
+
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x; -- should not be precalculated
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+
+-- Mixed functions and BooleanTest expressions testing
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x; -- should not be precalculated
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+
+-- Mixed functions and CoerceToDomain expressions testing
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x; -- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x; -- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x; -- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x; -- should not be precalculated
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+
+SET track_functions TO DEFAULT;
+
+-- ROW() expressions with dropped columns testing
+
+ALTER TABLE wxyz DROP COLUMN z;
+VACUUM FULL;
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x; -- should not be precalculated
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+
+BEGIN;
+SELECT simple();
+INSERT INTO x VALUES (5);
+SELECT simple();
+ROLLBACK;
+
+-- Drop tables and domains for testing
+
+DROP TABLE x;
+
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
+
+DROP FUNCTION x_stl2_my_integer_no_check;
+DROP DOMAIN my_integer_no_check;
+
+DROP FUNCTION x_stl2_my_integer_not_null;
+DROP DOMAIN my_integer_not_null;
+
+DROP FUNCTION x_stl2_my_integer_vlt_check;
+DROP DOMAIN my_integer_vlt_check;
+
+DROP FUNCTION x_stl2_my_integer_stl_check;
+DROP DOMAIN my_integer_stl_check;
+
+DROP FUNCTION x_stl2_my_integer_imm_check;
+DROP DOMAIN my_integer_imm_check;
-- 
1.9.1

v5-0003-Precalculate-stable-functions-costs.patchtext/x-diff; charset=us-ascii; name=v5-0003-Precalculate-stable-functions-costs.patchDownload
From 68af739040a57f35de1e0084067b76218befcd22 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 17 Jul 2017 13:08:36 +0300
Subject: [PATCH v5 3/3] Precalculate stable functions, costs

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:
- cost changes for cached expressions (according to their behaviour)
---
 src/backend/optimizer/path/costsize.c | 194 +++++++++++++++++++++++-----------
 1 file changed, 134 insertions(+), 60 deletions(-)

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index eb653cf..0ce57d5 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -140,6 +140,7 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root,
 			   PathKey *pathkey);
 static void cost_rescan(PlannerInfo *root, Path *path,
 			Cost *rescan_startup_cost, Cost *rescan_total_cost);
+static double cost_eval_cacheable_expr_per_tuple(CacheableExpr *node);
 static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
 static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
 						  ParamPathInfo *param_info,
@@ -3474,6 +3475,123 @@ cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root)
 	*cost = context.total;
 }
 
+/*
+ * cost_eval_cacheable_expr_per_tuple
+ *		Evaluate per tuple cost for expressions that can be cacheable.
+ *
+ * This function was created to not duplicate code for some expression and
+ * cached some expression.
+ */
+static double
+cost_eval_cacheable_expr_per_tuple(CacheableExpr *node)
+{
+	double		result;
+
+	/*
+	 * For each operator or function node in the given tree, we charge the
+	 * estimated execution cost given by pg_proc.procost (remember to multiply
+	 * this by cpu_operator_cost).
+	 *
+	 * Vars and Consts are charged zero, and so are boolean operators (AND,
+	 * OR, NOT). Simplistic, but a lot better than no model at all.
+	 */
+	if (IsA(node, ArrayRef) ||
+		IsA(node, BoolExpr) ||
+		IsA(node, FieldSelect) ||
+		IsA(node, RelabelType) ||
+		IsA(node, ConvertRowtypeExpr) ||
+		IsA(node, CaseExpr) ||
+		IsA(node, CaseTestExpr) ||
+		IsA(node, ArrayExpr) ||
+		IsA(node, RowExpr) ||
+		IsA(node, CoalesceExpr) ||
+		IsA(node, MinMaxExpr) ||
+		IsA(node, SQLValueFunction) ||
+		IsA(node, XmlExpr) ||
+		IsA(node, NullTest) ||
+		IsA(node, BooleanTest) ||
+		IsA(node, CoerceToDomain) ||
+		IsA(node, CoerceToDomainValue))
+	{
+		result = 0;
+	}
+	else if (IsA(node, FuncExpr))
+	{
+		result = get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+	}
+	else if (IsA(node, OpExpr) ||
+			 IsA(node, DistinctExpr) ||
+			 IsA(node, NullIfExpr))
+	{
+		OpExpr	   *opexpr = (OpExpr *) node;
+
+		/* rely on struct equivalence to treat these all alike */
+		set_opfuncid(opexpr);
+
+		result = get_func_cost(opexpr->opfuncid) * cpu_operator_cost;
+	}
+	else if (IsA(node, ScalarArrayOpExpr))
+	{
+		/*
+		 * Estimate that the operator will be applied to about half of the
+		 * array elements before the answer is determined.
+		 */
+		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
+		Node	   *arraynode = (Node *) lsecond(saop->args);
+
+		set_sa_opfuncid(saop);
+		result = get_func_cost(saop->opfuncid) * cpu_operator_cost *
+			estimate_array_length(arraynode) * 0.5;
+	}
+	else if (IsA(node, CoerceViaIO))
+	{
+		CoerceViaIO *iocoerce = (CoerceViaIO *) node;
+		Oid			iofunc;
+		Oid			typioparam;
+		bool		typisvarlena;
+
+		/* check the result type's input function */
+		getTypeInputInfo(iocoerce->resulttype,
+						 &iofunc, &typioparam);
+		result = get_func_cost(iofunc) * cpu_operator_cost;
+		/* check the input type's output function */
+		getTypeOutputInfo(exprType((Node *) iocoerce->arg),
+						  &iofunc, &typisvarlena);
+		result += get_func_cost(iofunc) * cpu_operator_cost;
+	}
+	else if (IsA(node, ArrayCoerceExpr))
+	{
+		ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
+		Node	   *arraynode = (Node *) acoerce->arg;
+
+		if (OidIsValid(acoerce->elemfuncid))
+			result = get_func_cost(acoerce->elemfuncid) * cpu_operator_cost *
+				estimate_array_length(arraynode);
+		else
+			result = 0;
+	}
+	else if (IsA(node, RowCompareExpr))
+	{
+		/* Conservatively assume we will check all the columns */
+		RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+		ListCell   *lc;
+
+		result = 0;
+		foreach(lc, rcexpr->opnos)
+		{
+			Oid			opid = lfirst_oid(lc);
+
+			result += get_func_cost(get_opcode(opid)) * cpu_operator_cost;
+		}
+	}
+	else
+	{
+		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
+	}
+
+	return result;
+}
+
 static bool
 cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 {
@@ -3547,32 +3665,27 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 	 * moreover, since our rowcount estimates for functions tend to be pretty
 	 * phony, the results would also be pretty phony.
 	 */
-	if (IsA(node, FuncExpr))
+	if (IsA(node, FuncExpr) ||
+		IsA(node, OpExpr) ||
+		IsA(node, DistinctExpr) ||
+		IsA(node, NullIfExpr) ||
+		IsA(node, ScalarArrayOpExpr) ||
+		IsA(node, CoerceViaIO) ||
+		IsA(node, ArrayCoerceExpr) ||
+		IsA(node, RowCompareExpr))
 	{
-		context->total.per_tuple +=
-			get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+		context->total.per_tuple += cost_eval_cacheable_expr_per_tuple(
+			(CacheableExpr *) node);
 	}
-	else if (IsA(node, OpExpr) ||
-			 IsA(node, DistinctExpr) ||
-			 IsA(node, NullIfExpr))
-	{
-		/* rely on struct equivalence to treat these all alike */
-		set_opfuncid((OpExpr *) node);
-		context->total.per_tuple +=
-			get_func_cost(((OpExpr *) node)->opfuncid) * cpu_operator_cost;
-	}
-	else if (IsA(node, ScalarArrayOpExpr))
+	else if (IsA(node, CachedExpr))
 	{
 		/*
-		 * Estimate that the operator will be applied to about half of the
-		 * array elements before the answer is determined.
+		 * Calculate subexpression cost per tuple as usual and add it to startup
+		 * cost (because subexpression will be executed only once for all
+		 * tuples).
 		 */
-		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
-		Node	   *arraynode = (Node *) lsecond(saop->args);
-
-		set_sa_opfuncid(saop);
-		context->total.per_tuple += get_func_cost(saop->opfuncid) *
-			cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
+		context->total.startup += cost_eval_cacheable_expr_per_tuple(
+			((CachedExpr *) node)->subexpr);
 	}
 	else if (IsA(node, Aggref) ||
 			 IsA(node, WindowFunc))
@@ -3588,45 +3701,6 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 		 */
 		return false;			/* don't recurse into children */
 	}
-	else if (IsA(node, CoerceViaIO))
-	{
-		CoerceViaIO *iocoerce = (CoerceViaIO *) node;
-		Oid			iofunc;
-		Oid			typioparam;
-		bool		typisvarlena;
-
-		/* check the result type's input function */
-		getTypeInputInfo(iocoerce->resulttype,
-						 &iofunc, &typioparam);
-		context->total.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
-		/* check the input type's output function */
-		getTypeOutputInfo(exprType((Node *) iocoerce->arg),
-						  &iofunc, &typisvarlena);
-		context->total.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
-	}
-	else if (IsA(node, ArrayCoerceExpr))
-	{
-		ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
-		Node	   *arraynode = (Node *) acoerce->arg;
-
-		if (OidIsValid(acoerce->elemfuncid))
-			context->total.per_tuple += get_func_cost(acoerce->elemfuncid) *
-				cpu_operator_cost * estimate_array_length(arraynode);
-	}
-	else if (IsA(node, RowCompareExpr))
-	{
-		/* Conservatively assume we will check all the columns */
-		RowCompareExpr *rcexpr = (RowCompareExpr *) node;
-		ListCell   *lc;
-
-		foreach(lc, rcexpr->opnos)
-		{
-			Oid			opid = lfirst_oid(lc);
-
-			context->total.per_tuple += get_func_cost(get_opcode(opid)) *
-				cpu_operator_cost;
-		}
-	}
 	else if (IsA(node, CurrentOfExpr))
 	{
 		/* Report high cost to prevent selection of anything but TID scan */
-- 
1.9.1

#13Robert Haas
robertmhaas@gmail.com
In reply to: Marina Polyakova (#12)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

On Tue, Jul 18, 2017 at 9:16 AM, Marina Polyakova
<m.polyakova@postgrespro.ru> wrote:

Here I have made the 5th version of the patches. I have added the
precalculation of all primitive nodes that don't return set, are not
volatile themselves and their arguments are constant or precalculated
expressions too. There're regression tests for all of them and little notes
in the documentation. Like for the previous patches it seems that there is
no obvious performance degradation too on regular queries (according to
pgbench).

pgbench probably isn't a very good test for this sort of thing - it
only issues very short-running queries where the cost of evaluating
expressions is a relatively small part of the total cost. Even if
things get worse, I'm not sure if you'd see it. I'm not sure exactly
how you could construct a test case that could be harmed by this patch
- I guess you'd want to initialize lots of CacheExprs but never make
use of the caching usefully?

It could also be useful to test things like TPC-H to see if you get an
improvement.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Robert Haas (#13)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Like for the previous patches it seems that there is
no obvious performance degradation too on regular queries (according
to
pgbench).

pgbench probably isn't a very good test for this sort of thing - it
only issues very short-running queries where the cost of evaluating
expressions is a relatively small part of the total cost. Even if
things get worse, I'm not sure if you'd see it.

If there's a mistake, for example, more than 1 try to replace cached
expressions in the whole query tree, results of "in buffer test" or
"mostly cache test" can different a little..

I'm not sure exactly
how you could construct a test case that could be harmed by this patch
- I guess you'd want to initialize lots of CacheExprs but never make
use of the caching usefully?

As I mentioned in the first letter about this feature it will be useful
for such text search queries [1]/messages/by-id/ba261b9fc25dea4069d8ba9a8fcadf35@postgrespro.ru:

SELECT COUNT(*) FROM messages WHERE body_tsvector @@
to_tsquery('postgres');

And I'm not sure that it is logical to precalculate stable and immutable
functions themselves, but not to precalculate expressions that behave
like stable/immutable functions; precalculate some types of operators
and not to precalculate others (ScalarArrayOpExpr, RowCompareExpr). My
patch solves the problem that not all nodes are simplified in
eval_const_expressions_mutator (for example, ScalarArrayOpExpr) and
consts of other types now behave more like ordinary consts (for example,
composite types, coerce expressions, ConvertRowtypeExpr).

It could also be useful to test things like TPC-H to see if you get an
improvement.

I saw the examples of queries in TPC-H tests. If I'm not wrong they are
not the target tests for this functionality (nothing will be
precalculated). But it's a good idea to check that there's no a
performance degradation on them too.

[1]: /messages/by-id/ba261b9fc25dea4069d8ba9a8fcadf35@postgrespro.ru
/messages/by-id/ba261b9fc25dea4069d8ba9a8fcadf35@postgrespro.ru

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Aleksander Alekseev
a.alekseev@postgrespro.ru
In reply to: Marina Polyakova (#14)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

The following review has been posted through the commitfest application:
make installcheck-world: not tested
Implements feature: not tested
Spec compliant: not tested
Documentation: not tested

Hi Marina,

I'm sorry to inform you that the v5 path set become a little outdated:

```
$ git apply v5-0002-Precalculate-stable-functions-planning-and-execut.patch
error: patch failed: src/pl/plpgsql/src/pl_exec.c:6471
error: src/pl/plpgsql/src/pl_exec.c: patch does not apply
```

If it's not too much trouble could you please fix the conflicts with the current master branch?

The new status of this patch is: Waiting on Author

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Robert Haas (#13)
1 attachment(s)
Re: [HACKERS] WIP Patch: Precalculate stable functions, infrastructure v1

Hello, hackers!

First of all, happy New Year!

Secondly, here there's a sixth version of the patch for the
precalculation of stable or immutable functions, stable or immutable
operators and other nonvolatile expressions.

The basic idea: the expression is precalculated (= calculated once for
all output rows, but as many times as the expression is mentioned in the
query) if:
1) it doesn't return a set,
2) it's not volatile itself,
3) its arguments are also constants or precalculated expressions.

Differences from the previous version:
* rebased, including changes for ArrayCoerce expressions;
* support for prepared statements (including tests, but only for
immutable functions);
* fix the caching of SQLValueFunctions (all of them are stable even
date/time functions);
* added the expected output for the tests in case the xml functions are
not supported;
* the tests are also performed in make check, not just in make
check-world;
* code cleanup.

Like for the previous patches it seems that there is no obvious
performance degradation too on regular queries (according to pgbench).

pgbench probably isn't a very good test for this sort of thing - it
only issues very short-running queries where the cost of evaluating
expressions is a relatively small part of the total cost. Even if
things get worse, I'm not sure if you'd see it. I'm not sure exactly
how you could construct a test case that could be harmed by this patch
- I guess you'd want to initialize lots of CacheExprs but never make
use of the caching usefully?

It could also be useful to test things like TPC-H to see if you get an
improvement.

I'm sorry, the TPC-H comparative tests will be later..

Patch is attached. Any suggestions are welcome!

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v6-0001-Precalculate-stable-and-immutable-functions.patchtext/plain; name=v6-0001-Precalculate-stable-and-immutable-functions.patchDownload
From a172312064c91ca49bd9a5bbb7fc7dd6924dcd1d Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Sun, 31 Dec 2017 08:44:08 +0300
Subject: [PATCH v6] Precalculate stable and immutable functions

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.

In this patch the function / operator / another expression is precalculated (=
calculated once for all output rows, but as many times as the expression is
mentioned in the query) if:
1) it doesn't return a set,
2) it's not volatile itself,
3) its arguments are also constants or precalculated expressions.

Costs are changed to reflect the changed behaviour.
Tests, small notes in the documentation and support for the prepared statements
are included.
---
 doc/src/sgml/ref/create_function.sgml              |    9 +
 doc/src/sgml/xfunc.sgml                            |    6 +-
 src/backend/commands/createas.c                    |    3 +-
 src/backend/commands/explain.c                     |    3 +-
 src/backend/commands/foreigncmds.c                 |    1 +
 src/backend/commands/portalcmds.c                  |    2 +-
 src/backend/commands/prepare.c                     |    7 +-
 src/backend/commands/schemacmds.c                  |    1 +
 src/backend/commands/trigger.c                     |    1 +
 src/backend/executor/execExpr.c                    |   39 +-
 src/backend/executor/execExprInterp.c              |   52 +
 src/backend/executor/execParallel.c                |    1 +
 src/backend/executor/functions.c                   |    2 +
 src/backend/executor/spi.c                         |    9 +-
 src/backend/nodes/copyfuncs.c                      |   17 +
 src/backend/nodes/equalfuncs.c                     |   11 +
 src/backend/nodes/nodeFuncs.c                      |   76 +
 src/backend/nodes/outfuncs.c                       |   12 +
 src/backend/nodes/params.c                         |    2 +
 src/backend/nodes/readfuncs.c                      |   16 +
 src/backend/optimizer/path/allpaths.c              |    9 +-
 src/backend/optimizer/path/clausesel.c             |   12 +
 src/backend/optimizer/path/costsize.c              |  218 +-
 src/backend/optimizer/plan/planagg.c               |    2 +
 src/backend/optimizer/plan/planner.c               | 1226 +++-
 src/backend/optimizer/prep/prepjointree.c          |    1 +
 src/backend/optimizer/util/clauses.c               |   41 +-
 src/backend/parser/parse_agg.c                     |    1 +
 src/backend/tcop/postgres.c                        |   11 +-
 src/backend/tcop/utility.c                         |    2 +
 src/backend/utils/adt/domains.c                    |    5 +-
 src/backend/utils/adt/ruleutils.c                  |    5 +
 src/backend/utils/cache/plancache.c                |  180 +-
 src/backend/utils/cache/typcache.c                 |   56 +-
 src/include/executor/execExpr.h                    |   31 +
 src/include/nodes/execnodes.h                      |   19 +-
 src/include/nodes/nodes.h                          |    3 +
 src/include/nodes/params.h                         |   22 +
 src/include/nodes/plannodes.h                      |    2 +
 src/include/nodes/primnodes.h                      |   96 +-
 src/include/nodes/relation.h                       |    4 +-
 src/include/optimizer/planner.h                    |   11 +-
 src/include/optimizer/tlist.h                      |    8 +-
 src/include/tcop/tcopprot.h                        |    4 +-
 src/include/utils/plancache.h                      |   12 +-
 src/include/utils/typcache.h                       |    2 +
 src/pl/plpgsql/src/pl_exec.c                       |   18 +
 .../expected/precalculate_stable_functions.out     | 6645 ++++++++++++++++++++
 .../expected/precalculate_stable_functions_1.out   | 6291 ++++++++++++++++++
 src/test/regress/parallel_schedule                 |    2 +-
 src/test/regress/serial_schedule                   |    1 +
 .../regress/sql/precalculate_stable_functions.sql  | 2117 +++++++
 52 files changed, 17162 insertions(+), 165 deletions(-)
 create mode 100644 src/test/regress/expected/precalculate_stable_functions.out
 create mode 100644 src/test/regress/expected/precalculate_stable_functions_1.out
 create mode 100644 src/test/regress/sql/precalculate_stable_functions.sql

diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index fd229d1..7079ec2 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -337,6 +337,15 @@ CREATE [ OR REPLACE ] FUNCTION
        <literal>setval()</literal>.
       </para>
 
+      <note>
+       <para>
+        Stable, immutable functions and other nonovolatile expressions are
+        precalculated (= calculated once for all output rows, but as many times
+        as expression is mentioned in query), if they don't return a set and
+        their arguments are constants or recursively precalculated expressions.
+       </para>
+      </note>
+
       <para>
        For additional details see <xref linkend="xfunc-volatility"/>.
       </para>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index bbc3766..4825d98 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1533,7 +1533,11 @@ CREATE FUNCTION test(int, int) RETURNS int
 
    <para>
     For best optimization results, you should label your functions with the
-    strictest volatility category that is valid for them.
+    strictest volatility category that is valid for them. Stable, immutable
+    functions and other nonovolatile expressions are precalculated (= calculated
+    once for all output rows, but as many times as expression is mentioned in
+    query), if they don't return a set and their arguments are constants or
+    recursively precalculated expressions.
    </para>
 
    <para>
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 4d77411..9f2b2e5 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -327,7 +327,8 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		Assert(query->commandType == CMD_SELECT);
 
 		/* plan the query */
-		plan = pg_plan_query(query, CURSOR_OPT_PARALLEL_OK, params);
+		plan = pg_plan_query(query, CURSOR_OPT_PARALLEL_OK,
+							 (ParamListInfoCommon) params);
 
 		/*
 		 * Use a snapshot with an updated command ID to ensure this query sees
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7e4fbaf..b8baae3 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -361,7 +361,8 @@ ExplainOneQuery(Query *query, int cursorOptions,
 		INSTR_TIME_SET_CURRENT(planstart);
 
 		/* plan the query */
-		plan = pg_plan_query(query, cursorOptions, params);
+		plan = pg_plan_query(query, cursorOptions,
+							 (ParamListInfoCommon) params);
 
 		INSTR_TIME_SET_CURRENT(planduration);
 		INSTR_TIME_SUBTRACT(planduration, planstart);
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 9ad9915..4435ab5 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -1619,6 +1619,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
 			pstmt->utilityStmt = (Node *) cstmt;
 			pstmt->stmt_location = rs->stmt_location;
 			pstmt->stmt_len = rs->stmt_len;
+			pstmt->hasCachedExpr = false;
 
 			/* Execute statement */
 			ProcessUtility(pstmt,
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 76d6cf1..e66bead 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -89,7 +89,7 @@ PerformCursorOpen(DeclareCursorStmt *cstmt, ParamListInfo params,
 		elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
 
 	/* Plan the query, applying the specified options */
-	plan = pg_plan_query(query, cstmt->options, params);
+	plan = pg_plan_query(query, cstmt->options, (ParamListInfoCommon) params);
 
 	/*
 	 * Create a portal and copy the plan and queryString into its memory.
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 6e90912..0ad37b3 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -243,7 +243,8 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
 									   entry->plansource->query_string);
 
 	/* Replan if needed, and increment plan refcount for portal */
-	cplan = GetCachedPlan(entry->plansource, paramLI, false, NULL);
+	cplan = GetCachedPlan(entry->plansource, (ParamListInfoCommon) paramLI,
+						  false, NULL, true);
 	plan_list = cplan->stmt_list;
 
 	/*
@@ -397,6 +398,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params,
 		palloc(offsetof(ParamListInfoData, params) +
 			   num_params * sizeof(ParamExternData));
 	/* we have static list of params, so no hooks needed */
+	paramLI->common.type = PARAM_LIST_INFO_DATA;
 	paramLI->paramFetch = NULL;
 	paramLI->paramFetchArg = NULL;
 	paramLI->paramCompile = NULL;
@@ -670,7 +672,8 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	}
 
 	/* Replan if needed, and acquire a transient refcount */
-	cplan = GetCachedPlan(entry->plansource, paramLI, true, queryEnv);
+	cplan = GetCachedPlan(entry->plansource, (ParamListInfoCommon) paramLI,
+						  true, queryEnv, true);
 
 	INSTR_TIME_SET_CURRENT(planduration);
 	INSTR_TIME_SUBTRACT(planduration, planstart);
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index f9ea73f..0a20ad7 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -188,6 +188,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 		wrapper->utilityStmt = stmt;
 		wrapper->stmt_location = stmt_location;
 		wrapper->stmt_len = stmt_len;
+		wrapper->hasCachedExpr = false;
 
 		/* do this step */
 		ProcessUtility(wrapper,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 92ae382..7515860 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1254,6 +1254,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
 		wrapper->utilityStmt = (Node *) atstmt;
 		wrapper->stmt_location = -1;
 		wrapper->stmt_len = -1;
+		wrapper->hasCachedExpr = false;
 
 		/* ... and execute it */
 		ProcessUtility(wrapper,
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 55bb925..fc68782 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -858,6 +858,38 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		case T_CachedExpr:
+			{
+				/*
+				 * Allocate CachedExprState used by all steps of CachedExpr
+				 * evaluation.
+				 */
+				scratch.d.cachedexpr.state = (CachedExprState *) palloc(
+					sizeof(CachedExprState));
+				scratch.d.cachedexpr.state->isExecuted = false;
+				scratch.d.cachedexpr.state->resnull = false;
+				scratch.d.cachedexpr.state->resvalue = (Datum) 0;
+				scratch.d.cachedexpr.state->restypid = exprType(
+					(const Node *) node);
+
+				/* add EEOP_CACHEDEXPR_IF_CACHED step */
+				scratch.opcode = EEOP_CACHEDEXPR_IF_CACHED;
+				ExprEvalPushStep(state, &scratch);
+
+				/* add subexpression steps */
+				ExecInitExprRec((Expr *) ((CachedExpr *) node)->subexpr, state,
+								resv, resnull);
+
+				/* add EEOP_CACHEDEXPR_SUBEXPR_END step */
+				scratch.opcode = EEOP_CACHEDEXPR_SUBEXPR_END;
+				ExprEvalPushStep(state, &scratch);
+
+				/* adjust jump target */
+				scratch.d.cachedexpr.state->jumpdone = state->steps_len;
+
+				break;
+			}
+
 		case T_ArrayRef:
 			{
 				ArrayRef   *aref = (ArrayRef *) node;
@@ -2057,6 +2089,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		/* note that DomainConstraintExpr nodes are handled within this block */
 		case T_CoerceToDomain:
 			{
 				CoerceToDomain *ctest = (CoerceToDomain *) node;
@@ -2648,6 +2681,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;
@@ -2685,15 +2719,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/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0c3f668..39c9bc5 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -68,6 +68,7 @@
 #include "pgstat.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
+#include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
@@ -368,6 +369,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_WINDOW_FUNC,
 		&&CASE_EEOP_SUBPLAN,
 		&&CASE_EEOP_ALTERNATIVE_SUBPLAN,
+		&&CASE_EEOP_CACHEDEXPR_IF_CACHED,
+		&&CASE_EEOP_CACHEDEXPR_SUBEXPR_END,
 		&&CASE_EEOP_LAST
 	};
 
@@ -1543,6 +1546,55 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_CACHEDEXPR_IF_CACHED)
+		{
+			if (op->d.cachedexpr.state->isExecuted)
+			{
+				/* use saved result and skip subexpression evaluation */
+				*op->resnull = op->d.cachedexpr.state->resnull;
+				if (!(*op->resnull))
+					*op->resvalue = op->d.cachedexpr.state->resvalue;
+
+				EEO_JUMP(op->d.cachedexpr.state->jumpdone);
+			}
+
+			/* we are ready for subexpression evaluation */
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHEDEXPR_SUBEXPR_END)
+		{
+			int16		restyplen;
+			bool		restypbyval;
+			MemoryContext oldContext;
+
+			/* save result */
+			op->d.cachedexpr.state->resnull = *op->resnull;
+			if (!(*op->resnull))
+			{
+				get_typlenbyval(op->d.cachedexpr.state->restypid, &restyplen,
+								&restypbyval);
+
+				/*
+				 * Switch per-query memory context. It is necessary to save the
+				 * subexpression result between all tuples if its value datum is
+				 * a pointer.
+				 */
+				oldContext = MemoryContextSwitchTo(
+					econtext->ecxt_per_query_memory);
+
+				op->d.cachedexpr.state->resvalue = datumCopy(*op->resvalue,
+															 restypbyval,
+															 restyplen);
+
+				/* switch memory context back */
+				MemoryContextSwitchTo(oldContext);
+			}
+			op->d.cachedexpr.state->isExecuted = true;
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_LAST)
 		{
 			/* unreachable */
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index b344d4b..4f09bc1 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -207,6 +207,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->utilityStmt = NULL;
 	pstmt->stmt_location = -1;
 	pstmt->stmt_len = -1;
+	pstmt->hasCachedExpr = false;
 
 	/* Return serialized copy of our dummy PlannedStmt. */
 	return nodeToString(pstmt);
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 527f7d8..f477d8d 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -502,6 +502,7 @@ init_execution_state(List *queryTree_list,
 				stmt->utilityStmt = queryTree->utilityStmt;
 				stmt->stmt_location = queryTree->stmt_location;
 				stmt->stmt_len = queryTree->stmt_len;
+				stmt->hasCachedExpr = false;
 			}
 			else
 				stmt = pg_plan_query(queryTree,
@@ -912,6 +913,7 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
 				palloc(offsetof(ParamListInfoData, params) +
 					   nargs * sizeof(ParamExternData));
 			/* we have static list of params, so no hooks needed */
+			paramLI->common.type = PARAM_LIST_INFO_DATA;
 			paramLI->paramFetch = NULL;
 			paramLI->paramFetchArg = NULL;
 			paramLI->paramCompile = NULL;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 977f317..5888ba1 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1202,7 +1202,8 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 	 */
 
 	/* Replan if needed, and increment plan refcount for portal */
-	cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv);
+	cplan = GetCachedPlan(plansource, (ParamListInfoCommon) paramLI, false,
+						  _SPI_current->queryEnv, false);
 	stmt_list = cplan->stmt_list;
 
 	if (!plan->saved)
@@ -1636,7 +1637,7 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
 
 	/* Get the generic plan for the query */
 	cplan = GetCachedPlan(plansource, NULL, plan->saved,
-						  _SPI_current->queryEnv);
+						  _SPI_current->queryEnv, false);
 	Assert(cplan == plansource->gplan);
 
 	/* Pop the error context stack */
@@ -2026,7 +2027,8 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 		 * Replan if needed, and increment plan refcount.  If it's a saved
 		 * plan, the refcount must be backed by the CurrentResourceOwner.
 		 */
-		cplan = GetCachedPlan(plansource, paramLI, plan->saved, _SPI_current->queryEnv);
+		cplan = GetCachedPlan(plansource, (ParamListInfoCommon) paramLI,
+							  plan->saved, _SPI_current->queryEnv, false);
 		stmt_list = cplan->stmt_list;
 
 		/*
@@ -2257,6 +2259,7 @@ _SPI_convert_params(int nargs, Oid *argtypes,
 		paramLI = (ParamListInfo) palloc(offsetof(ParamListInfoData, params) +
 										 nargs * sizeof(ParamExternData));
 		/* we have static list of params, so no hooks needed */
+		paramLI->common.type = PARAM_LIST_INFO_DATA;
 		paramLI->paramFetch = NULL;
 		paramLI->paramFetchArg = NULL;
 		paramLI->paramCompile = NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 84d7171..c72f91d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -101,6 +101,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_LOCATION_FIELD(stmt_location);
 	COPY_LOCATION_FIELD(stmt_len);
+	COPY_SCALAR_FIELD(hasCachedExpr);
 
 	return newnode;
 }
@@ -1417,6 +1418,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 *
@@ -4884,6 +4898,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 2e869a9..cb66eaa 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);
@@ -3035,6 +3043,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 c2a93b2..5e4d2f7 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;
@@ -1910,6 +1955,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;
@@ -2528,6 +2585,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 e468d7c..80084d5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -286,6 +286,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_NODE_FIELD(utilityStmt);
 	WRITE_LOCATION_FIELD(stmt_location);
 	WRITE_LOCATION_FIELD(stmt_len);
+	WRITE_BOOL_FIELD(hasCachedExpr);
 }
 
 /*
@@ -1180,6 +1181,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");
@@ -3793,6 +3802,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/params.c b/src/backend/nodes/params.c
index 94acdf4..37451e8 100644
--- a/src/backend/nodes/params.c
+++ b/src/backend/nodes/params.c
@@ -46,6 +46,7 @@ copyParamList(ParamListInfo from)
 		from->numParams * sizeof(ParamExternData);
 
 	retval = (ParamListInfo) palloc(size);
+	retval->common.type = PARAM_LIST_INFO_DATA;
 	retval->paramFetch = NULL;
 	retval->paramFetchArg = NULL;
 	retval->paramCompile = NULL;
@@ -222,6 +223,7 @@ RestoreParamList(char **start_address)
 		nparams * sizeof(ParamExternData);
 
 	paramLI = (ParamListInfo) palloc(size);
+	paramLI->common.type = PARAM_LIST_INFO_DATA;
 	paramLI->paramFetch = NULL;
 	paramLI->paramFetchArg = NULL;
 	paramLI->paramCompile = NULL;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 1133c70..7fb9aef 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -640,6 +640,19 @@ _readWindowFunc(void)
 }
 
 /*
+ * _readCachedExpr
+ */
+static CachedExpr *
+_readCachedExpr(void)
+{
+	READ_LOCALS(CachedExpr);
+
+	READ_NODE_FIELD(subexpr);
+
+	READ_DONE();
+}
+
+/*
  * _readArrayRef
  */
 static ArrayRef *
@@ -1484,6 +1497,7 @@ _readPlannedStmt(void)
 	READ_NODE_FIELD(utilityStmt);
 	READ_LOCATION_FIELD(stmt_location);
 	READ_LOCATION_FIELD(stmt_len);
+	READ_BOOL_FIELD(hasCachedExpr);
 
 	READ_DONE();
 }
@@ -2482,6 +2496,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/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 0e8463e..109591e 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -380,7 +380,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 				set_subquery_pathlist(root, rel, rti, rte);
 				break;
 			case RTE_FUNCTION:
-				set_function_size_estimates(root, rel);
+				{
+					rel->baserestrictinfo = replace_qual_cached_expressions(
+						rel->baserestrictinfo, root);
+					set_function_size_estimates(root, rel);
+				}
 				break;
 			case RTE_TABLEFUNC:
 				set_tablefunc_size_estimates(root, rel);
@@ -519,6 +523,9 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	 */
 	check_index_predicates(root, rel);
 
+	rel->baserestrictinfo = replace_qual_cached_expressions(
+		rel->baserestrictinfo, root);
+
 	/* Mark rel with estimated output rows, width, etc */
 	set_baserel_size_estimates(root, rel);
 }
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index b4cbc34..ba7e03e 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -827,6 +827,18 @@ clause_selectivity(PlannerInfo *root,
 								jointype,
 								sjinfo);
 	}
+	else if (IsA(clause, CachedExpr))
+	{
+		/*
+		 * Not sure this case is needed, but it can't hurt.
+		 * Calculate selectivity of subexpression.
+		 */
+		s1 = clause_selectivity(root,
+								(Node *) ((CachedExpr *) clause)->subexpr,
+								varRelid,
+								jointype,
+								sjinfo);
+	}
 	else
 	{
 		/*
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index c3daacd..405a903 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -143,6 +143,8 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root,
 			   PathKey *pathkey);
 static void cost_rescan(PlannerInfo *root, Path *path,
 			Cost *rescan_startup_cost, Cost *rescan_total_cost);
+static QualCost cost_eval_cacheable_expr(CacheableExpr *node,
+										 PlannerInfo *root);
 static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
 static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
 						  ParamPathInfo *param_info,
@@ -3729,6 +3731,129 @@ cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root)
 	*cost = context.total;
 }
 
+/*
+ * cost_eval_cacheable_expr
+ *		Evaluate the cost for expressions that can be cached.
+ *
+ * This function was created to not duplicate code for some expression and
+ * cached some expression.
+ */
+static QualCost
+cost_eval_cacheable_expr(CacheableExpr *node, PlannerInfo *root)
+{
+	QualCost	node_cost;
+
+	node_cost.startup = node_cost.per_tuple = 0;
+
+	/*
+	 * For each operator or function node in the given tree, we charge the
+	 * estimated execution cost given by pg_proc.procost (remember to multiply
+	 * this by cpu_operator_cost).
+	 *
+	 * Vars and Consts are charged zero, and so are boolean operators (AND,
+	 * OR, NOT). Simplistic, but a lot better than no model at all.
+	 */
+	if (IsA(node, FuncExpr))
+	{
+		node_cost.per_tuple = get_func_cost(((FuncExpr *) node)->funcid) *
+			cpu_operator_cost;
+	}
+	else if (IsA(node, OpExpr) ||
+			 IsA(node, DistinctExpr) ||
+			 IsA(node, NullIfExpr))
+	{
+		OpExpr	   *opexpr = (OpExpr *) node;
+
+		/* rely on struct equivalence to treat these all alike */
+		set_opfuncid(opexpr);
+
+		node_cost.per_tuple = get_func_cost(opexpr->opfuncid) *
+			cpu_operator_cost;
+	}
+	else if (IsA(node, ScalarArrayOpExpr))
+	{
+		/*
+		 * Estimate that the operator will be applied to about half of the
+		 * array elements before the answer is determined.
+		 */
+		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
+		Node	   *arraynode = (Node *) lsecond(saop->args);
+
+		set_sa_opfuncid(saop);
+		node_cost.per_tuple = get_func_cost(saop->opfuncid) *
+			cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
+	}
+	else if (IsA(node, CoerceViaIO))
+	{
+		CoerceViaIO *iocoerce = (CoerceViaIO *) node;
+		Oid			iofunc;
+		Oid			typioparam;
+		bool		typisvarlena;
+
+		/* check the result type's input function */
+		getTypeInputInfo(iocoerce->resulttype,
+						 &iofunc, &typioparam);
+		node_cost.per_tuple = get_func_cost(iofunc) * cpu_operator_cost;
+		/* check the input type's output function */
+		getTypeOutputInfo(exprType((Node *) iocoerce->arg),
+						  &iofunc, &typisvarlena);
+		node_cost.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
+	}
+	else if (IsA(node, ArrayCoerceExpr))
+	{
+		ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
+		QualCost	perelemcost;
+
+		cost_qual_eval_node(&perelemcost, (Node *) acoerce->elemexpr, root);
+		node_cost.startup = perelemcost.startup;
+		if (perelemcost.per_tuple > 0)
+			node_cost.per_tuple += perelemcost.per_tuple *
+				estimate_array_length((Node *) acoerce->arg);
+	}
+	else if (IsA(node, RowCompareExpr))
+	{
+		/* Conservatively assume we will check all the columns */
+		RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+		ListCell   *lc;
+
+		foreach(lc, rcexpr->opnos)
+		{
+			Oid			opid = lfirst_oid(lc);
+
+			node_cost.per_tuple += get_func_cost(get_opcode(opid)) *
+				cpu_operator_cost;
+		}
+	}
+	else if (IsA(node, MinMaxExpr) ||
+			 IsA(node, SQLValueFunction) ||
+			 IsA(node, XmlExpr) ||
+			 IsA(node, CoerceToDomain))
+	{
+		/* Treat all these as having cost 1 */
+		node_cost.per_tuple = cpu_operator_cost;
+	}
+	else if (!(IsA(node, ArrayRef) ||
+			   IsA(node, BoolExpr) ||
+			   IsA(node, FieldSelect) ||
+			   IsA(node, RelabelType) ||
+			   IsA(node, ConvertRowtypeExpr) ||
+			   IsA(node, CaseExpr) ||
+			   IsA(node, CaseTestExpr) ||
+			   IsA(node, ArrayExpr) ||
+			   IsA(node, RowExpr) ||
+			   IsA(node, CoalesceExpr) ||
+			   IsA(node, NullTest) ||
+			   IsA(node, BooleanTest) ||
+			   IsA(node, CoerceToDomainValue)))
+	{
+		elog(ERROR,
+			 "unrecognized type of cacheable node: %d",
+			 (int) nodeTag(node));
+	}
+
+	return node_cost;
+}
+
 static bool
 cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 {
@@ -3802,32 +3927,35 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 	 * moreover, since our rowcount estimates for functions tend to be pretty
 	 * phony, the results would also be pretty phony.
 	 */
-	if (IsA(node, FuncExpr))
-	{
-		context->total.per_tuple +=
-			get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
-	}
-	else if (IsA(node, OpExpr) ||
-			 IsA(node, DistinctExpr) ||
-			 IsA(node, NullIfExpr))
+	if (IsA(node, FuncExpr) ||
+		IsA(node, OpExpr) ||
+		IsA(node, DistinctExpr) ||
+		IsA(node, NullIfExpr) ||
+		IsA(node, ScalarArrayOpExpr) ||
+		IsA(node, CoerceViaIO) ||
+		IsA(node, ArrayCoerceExpr) ||
+		IsA(node, RowCompareExpr) ||
+		IsA(node, MinMaxExpr) ||
+		IsA(node, SQLValueFunction) ||
+		IsA(node, XmlExpr) ||
+		IsA(node, CoerceToDomain))
 	{
-		/* rely on struct equivalence to treat these all alike */
-		set_opfuncid((OpExpr *) node);
-		context->total.per_tuple +=
-			get_func_cost(((OpExpr *) node)->opfuncid) * cpu_operator_cost;
+		QualCost	node_cost = cost_eval_cacheable_expr(
+			(CacheableExpr *) node, context->root);
+
+		context->total.startup += node_cost.startup;
+		context->total.per_tuple += node_cost.per_tuple;
 	}
-	else if (IsA(node, ScalarArrayOpExpr))
+	else if (IsA(node, CachedExpr))
 	{
 		/*
-		 * Estimate that the operator will be applied to about half of the
-		 * array elements before the answer is determined.
+		 * Calculate subexpression cost as usual and add it to startup cost
+		 * (because subexpression will be executed only once for all tuples).
 		 */
-		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
-		Node	   *arraynode = (Node *) lsecond(saop->args);
+		QualCost	node_cost = cost_eval_cacheable_expr(
+			((CachedExpr *) node)->subexpr, context->root);
 
-		set_sa_opfuncid(saop);
-		context->total.per_tuple += get_func_cost(saop->opfuncid) *
-			cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
+		context->total.startup += node_cost.startup + node_cost.per_tuple;
 	}
 	else if (IsA(node, Aggref) ||
 			 IsA(node, WindowFunc))
@@ -3843,55 +3971,9 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 		 */
 		return false;			/* don't recurse into children */
 	}
-	else if (IsA(node, CoerceViaIO))
-	{
-		CoerceViaIO *iocoerce = (CoerceViaIO *) node;
-		Oid			iofunc;
-		Oid			typioparam;
-		bool		typisvarlena;
-
-		/* check the result type's input function */
-		getTypeInputInfo(iocoerce->resulttype,
-						 &iofunc, &typioparam);
-		context->total.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
-		/* check the input type's output function */
-		getTypeOutputInfo(exprType((Node *) iocoerce->arg),
-						  &iofunc, &typisvarlena);
-		context->total.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
-	}
-	else if (IsA(node, ArrayCoerceExpr))
-	{
-		ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
-		QualCost	perelemcost;
-
-		cost_qual_eval_node(&perelemcost, (Node *) acoerce->elemexpr,
-							context->root);
-		context->total.startup += perelemcost.startup;
-		if (perelemcost.per_tuple > 0)
-			context->total.per_tuple += perelemcost.per_tuple *
-				estimate_array_length((Node *) acoerce->arg);
-	}
-	else if (IsA(node, RowCompareExpr))
+	else if (IsA(node, NextValueExpr))
 	{
-		/* Conservatively assume we will check all the columns */
-		RowCompareExpr *rcexpr = (RowCompareExpr *) node;
-		ListCell   *lc;
-
-		foreach(lc, rcexpr->opnos)
-		{
-			Oid			opid = lfirst_oid(lc);
-
-			context->total.per_tuple += get_func_cost(get_opcode(opid)) *
-				cpu_operator_cost;
-		}
-	}
-	else if (IsA(node, MinMaxExpr) ||
-			 IsA(node, SQLValueFunction) ||
-			 IsA(node, XmlExpr) ||
-			 IsA(node, CoerceToDomain) ||
-			 IsA(node, NextValueExpr))
-	{
-		/* Treat all these as having cost 1 */
+		/* Treat this as having cost 1 */
 		context->total.per_tuple += cpu_operator_cost;
 	}
 	else if (IsA(node, CurrentOfExpr))
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 889e8af..f2670b2 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -38,6 +38,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "optimizer/planner.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
@@ -368,6 +369,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
 	subroot->plan_params = NIL;
 	subroot->outer_params = NULL;
 	subroot->init_plans = NIL;
+	subroot->hasCachedExpr = false;
 
 	subroot->parse = parse = copyObject(root->parse);
 	IncrementVarSublevelsUp((Node *) parse, 1, 1);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 382791f..88b3a84 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 */
@@ -109,6 +110,18 @@ typedef struct
 	int		   *tleref_to_colnum_map;
 } grouping_sets_data;
 
+typedef struct replace_cached_expressions_context
+{
+	PlannerInfo *root;
+
+	/*
+	 * Pointers are nulls if the parent nodes don't use CaseTestExpr /
+	 * CoerceToDomainValue nodes respectively.
+	 */
+	bool		*innermost_nonconst_or_noncached_casetestexpr;
+	bool		*innermost_nonconst_or_noncached_coercetodomainvalue;
+} 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);
@@ -185,6 +198,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);
 
 
 /*****************************************************************************
@@ -201,7 +216,7 @@ static void adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
  *
  *****************************************************************************/
 PlannedStmt *
-planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
+planner(Query *parse, int cursorOptions, ParamListInfoCommon boundParams)
 {
 	PlannedStmt *result;
 
@@ -213,7 +228,8 @@ planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 }
 
 PlannedStmt *
-standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
+standard_planner(Query *parse, int cursorOptions,
+				 ParamListInfoCommon boundParams)
 {
 	PlannedStmt *result;
 	PlannerGlobal *glob;
@@ -480,6 +496,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->utilityStmt = parse->utilityStmt;
 	result->stmt_location = parse->stmt_location;
 	result->stmt_len = parse->stmt_len;
+	result->hasCachedExpr = root->hasCachedExpr;
 
 	return result;
 }
@@ -554,6 +571,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	else
 		root->wt_param_id = -1;
 	root->non_recursive_path = NULL;
+	root->hasCachedExpr = false;
 
 	/*
 	 * If there is a WITH list, process each WITH query and build an initplan
@@ -1220,6 +1238,7 @@ inheritance_planner(PlannerInfo *root)
 		 */
 		subroot = makeNode(PlannerInfo);
 		memcpy(subroot, parent_root, sizeof(PlannerInfo));
+		subroot->hasCachedExpr = false;
 
 		/*
 		 * Generate modified query with this rel as target.  We first apply
@@ -6083,6 +6102,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	root->query_level = 1;
 	root->planner_cxt = CurrentMemoryContext;
 	root->wt_param_id = -1;
+	root->hasCachedExpr = false;
 
 	/* Build a minimal RTE for the rel */
 	rte = makeNode(RangeTblEntry);
@@ -6201,3 +6221,1205 @@ get_partitioned_child_rels_for_join(PlannerInfo *root, Relids join_relids)
 
 	return result;
 }
+
+/*
+ * replace_pathtarget_cached_expressions
+ *		Replace cached expresisons in a PathTarget tlist.
+ *
+ * As a notational convenience, returns the same PathTarget pointer passed in.
+ */
+PathTarget *
+replace_pathtarget_cached_expressions(PathTarget *target, PlannerInfo *root)
+{
+	replace_cached_expressions_context context;
+
+	context.root = root;
+	context.innermost_nonconst_or_noncached_casetestexpr = NULL;
+	context.innermost_nonconst_or_noncached_coercetodomainvalue = NULL;
+
+	target->exprs = (List *) replace_cached_expressions_mutator(
+		(Node *) target->exprs, &context);
+
+	return target;
+}
+
+/*
+ * replace_qual_cached_expressions
+ *		Replace cacehd expressions in a WHERE clause. The input can be either an
+ *		implicitly-ANDed list of boolean expressions, or a list of RestrictInfo
+ *		nodes.
+ */
+List *
+replace_qual_cached_expressions(List *quals, PlannerInfo *root)
+{
+	replace_cached_expressions_context context;
+
+	context.root = root;
+	context.innermost_nonconst_or_noncached_casetestexpr = NULL;
+	context.innermost_nonconst_or_noncached_coercetodomainvalue = NULL;
+
+	return (List *) replace_cached_expressions_mutator((Node *) quals,
+													   &context);
+}
+
+/*
+ * Return true if node is null or const or CachedExpr or extern const parameter.
+ */
+static bool
+is_cached_or_const(Expr *node, ParamListInfoCommon boundParams)
+{
+	if (node == NULL || IsA(node, Const) || IsA(node, CachedExpr))
+		return true;
+
+	if (IsA(node, Param))
+	{
+		Param	   *param = (Param *) node;
+		int			paramid = param->paramid;
+		ParamListInfoDataType boundParamsType;
+
+		if (param->paramkind != PARAM_EXTERN ||
+			paramid <= 0 ||
+			boundParams == NULL)
+			return false;
+
+		boundParamsType = boundParams->type;
+
+		switch (boundParamsType)
+		{
+			case PARAM_LIST_INFO_DATA:
+				{
+					ParamListInfo params = (ParamListInfo) boundParams;
+
+					return (paramid <= params->numParams &&
+							params->params[paramid - 1].pflags &
+							PARAM_FLAG_CONST);
+				}
+			case PARAM_LIST_INFO_PRECALCULATION_DATA:
+				{
+					ParamListInfoPrecalculation params =
+						(ParamListInfoPrecalculation) boundParams;
+
+					return (paramid <= params->numParams &&
+							params->isConstParam[paramid - 1]);
+				}
+			default:
+				elog(ERROR, "unrecognized ParamListInfoData type: %d",
+					 (int) boundParamsType);
+				break;
+		}
+	}
+
+	return false;
+}
+
+static Node *
+replace_cached_expressions_mutator(Node *node,
+								   replace_cached_expressions_context *context)
+{
+	ParamListInfoCommon boundParams = context->root->glob->boundParams;
+
+	if (node == NULL)
+		return NULL;
+
+	/* mutate certain types of nodes */
+	if (IsA(node, CachedExpr))
+	{
+		/* nothing to mutate */
+		return node;
+	}
+	else 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;
+		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)
+		{
+			if (!is_cached_or_const(lfirst(indexpr), boundParams))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		/* check expressions of lower array indexes */
+		foreach(indexpr, aref->reflowerindexpr)
+		{
+			if (!is_cached_or_const(lfirst(indexpr), boundParams))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		/* check expression of array value */
+		if (!is_cached_or_const(aref->refexpr, boundParams))
+			has_nonconst_or_noncached_input = true;
+
+		/* check expression of source value */
+		if (!is_cached_or_const(aref->refassgnexpr, boundParams))
+			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;
+
+			context->root->hasCachedExpr = true;
+
+			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)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			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)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			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)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			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)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			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)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			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)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			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;
+
+		/* firstly recurse into children */
+		fselect = (FieldSelect *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(fselect->arg, boundParams))
+		{
+			/* 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;
+
+			context->root->hasCachedExpr = true;
+
+			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;
+
+		/* firstly recurse into children */
+		relabel = (RelabelType *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(relabel->arg, boundParams))
+		{
+			/* 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;
+
+			context->root->hasCachedExpr = true;
+
+			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;
+
+		/* firstly recurse into children */
+		iocoerce = (CoerceViaIO *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(iocoerce->arg, boundParams) ||
+			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;
+
+			context->root->hasCachedExpr = true;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, ArrayCoerceExpr))
+	{
+		/*
+		 * ArrayCoerceExpr is cached if:
+		 * 1) its element-type coercion expression can be cached,
+		 * 2) its argument is const or cached expression too.
+		 * (it returns its argument with a type coercion so we don't need to
+		 * check if it returns set)
+		 */
+		ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
+		Expr	   *elemexpr;
+		bool		has_nonconst_or_noncached_input = false;
+		bool		nonconst_or_noncached_arg;
+		replace_cached_expressions_context new_context;
+
+		/* firstly recurse into children */
+		acoerce = (ArrayCoerceExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		/* check arg and fill new context */
+		nonconst_or_noncached_arg = !is_cached_or_const(acoerce->arg,
+														boundParams);
+		has_nonconst_or_noncached_input = nonconst_or_noncached_arg;
+
+		new_context.root = context->root;
+		new_context.innermost_nonconst_or_noncached_casetestexpr =
+			&nonconst_or_noncached_arg;
+		new_context.innermost_nonconst_or_noncached_coercetodomainvalue =
+			context->innermost_nonconst_or_noncached_coercetodomainvalue;
+
+		/*
+		 * Recurse into element-type coercion expression with new context (it
+		 * will be used by CaseTestExpr if it's used here).
+		 */
+		elemexpr = (Expr *) replace_cached_expressions_mutator(
+													(Node *) acoerce->elemexpr,
+													(void *) &new_context);
+
+		/* check elemexpr */
+		if (!is_cached_or_const(elemexpr, boundParams))
+			has_nonconst_or_noncached_input = true;
+
+		if (has_nonconst_or_noncached_input)
+		{
+			/* 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;
+
+			context->root->hasCachedExpr = true;
+
+			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;
+
+		/* firstly recurse into children */
+		convexpr = (ConvertRowtypeExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(convexpr->arg, boundParams))
+		{
+			/* 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;
+
+			context->root->hasCachedExpr = true;
+
+			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;
+		bool		has_nonconst_or_noncached_input = false;
+		bool		nonconst_or_noncached_testvalue;
+		replace_cached_expressions_context new_context;
+		ListCell   *cell;
+
+		/*
+		 * 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);
+
+		/* check arg and fill new context */
+		nonconst_or_noncached_testvalue = !is_cached_or_const(new_caseexpr->arg,
+															  boundParams);
+		has_nonconst_or_noncached_input = nonconst_or_noncached_testvalue;
+
+		new_context.root = context->root;
+		new_context.innermost_nonconst_or_noncached_casetestexpr =
+			&nonconst_or_noncached_testvalue;
+		new_context.innermost_nonconst_or_noncached_coercetodomainvalue =
+			context->innermost_nonconst_or_noncached_coercetodomainvalue;
+
+		/*
+		 * 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 *) replace_cached_expressions_mutator(
+													(Node *) new_caseexpr->args,
+													(void *) &new_context);
+
+		/* check args */
+		foreach(cell, new_caseexpr->args)
+		{
+			CaseWhen   *casewhen = lfirst(cell);
+
+			if (!is_cached_or_const(casewhen->expr, boundParams) ||
+				!is_cached_or_const(casewhen->result, boundParams))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		/* recurse into defresult */
+		new_caseexpr->defresult = (Expr	*) replace_cached_expressions_mutator(
+											(Node *) new_caseexpr->defresult,
+											context);
+
+		/* check defresult */
+		if (!is_cached_or_const(new_caseexpr->defresult, boundParams))
+			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;
+
+			context->root->hasCachedExpr = true;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, CaseTestExpr))
+	{
+		/*
+		 * CaseTestExpr is cached if we got in context that it is in CaseExpr/
+		 * ArrayCoerceExpr and arg of innermost CaseExpr/ArrayCoerceExpr is
+		 * const or cached expression too. (it is a placeholder node for the
+		 * test value of its CaseExpr/source element of its ArrayCoerceExpr 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_nonconst_or_noncached_casetestexpr ||
+			*(context->innermost_nonconst_or_noncached_casetestexpr))
+		{
+			/* 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;
+
+			context->root->hasCachedExpr = true;
+
+			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)
+		{
+			if (!is_cached_or_const(lfirst(element), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			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)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			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)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		foreach(arg, rcexpr->rargs)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			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)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			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)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			return (Node *) new_node;
+		}
+	}
+	else if (IsA(node, SQLValueFunction))
+	{
+		/*
+		 * SQLValueFunction is always cached because all of these functions
+		 * don't return a set and are stable.
+		 */
+		CachedExpr *new_node = makeNode(CachedExpr);
+		new_node->subexpr = (CacheableExpr *) node;
+
+		context->root->hasCachedExpr = true;
+
+		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)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		foreach(arg, xexpr->args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+				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;
+
+			context->root->hasCachedExpr = true;
+
+			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;
+
+		/* firstly recurse into children */
+		nulltest = (NullTest *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(nulltest->arg, boundParams))
+		{
+			/* 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;
+
+			context->root->hasCachedExpr = true;
+
+			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;
+
+		/* firstly recurse into children */
+		btest = (BooleanTest *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(btest->arg, boundParams))
+		{
+			/* 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;
+
+			context->root->hasCachedExpr = true;
+
+			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;
+		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 children */
+		ctest = (CoerceToDomain *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		/* check arg and fill new context */
+		nonconst_or_noncached_value = !is_cached_or_const(ctest->arg,
+														  boundParams);
+		has_nonconst_or_noncached_input = nonconst_or_noncached_value;
+
+		new_context.root = context->root;
+		new_context.innermost_nonconst_or_noncached_casetestexpr =
+			context->innermost_nonconst_or_noncached_casetestexpr;
+		new_context.innermost_nonconst_or_noncached_coercetodomainvalue =
+			&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 (!is_cached_or_const(check_expr, boundParams))
+						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;
+
+			context->root->hasCachedExpr = true;
+
+			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_nonconst_or_noncached_coercetodomainvalue ||
+			*(context->innermost_nonconst_or_noncached_coercetodomainvalue))
+		{
+			/* 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;
+
+			context->root->hasCachedExpr = true;
+
+			return (Node *) new_node;
+		}
+	}
+
+	/* otherwise recurse into children */
+	return expression_tree_mutator(node, replace_cached_expressions_mutator,
+								   (void *) context);
+}
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 1d7e499..099d7fb 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -918,6 +918,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->hasRecursion = false;
 	subroot->wt_param_id = -1;
 	subroot->non_recursive_path = NULL;
+	subroot->hasCachedExpr = false;
 
 	/* No CTEs to worry about */
 	Assert(subquery->cteList == NIL);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 9ca384d..5cacff9 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -62,7 +62,7 @@ typedef struct
 
 typedef struct
 {
-	ParamListInfo boundParams;
+	ParamListInfoCommon boundParams;
 	PlannerInfo *root;
 	List	   *active_fns;
 	Node	   *case_val;
@@ -980,6 +980,11 @@ contain_volatile_functions_walker(Node *node, void *context)
 		/* NextValueExpr is volatile */
 		return true;
 	}
+	else if (IsA(node, CachedExpr))
+	{
+		/* CachedExpr is not volatile */
+		return false;
+	}
 
 	/*
 	 * See notes in contain_mutable_functions_walker about why we treat
@@ -2513,7 +2518,13 @@ eval_const_expressions_mutator(Node *node,
 		case T_Param:
 			{
 				Param	   *param = (Param *) node;
-				ParamListInfo paramLI = context->boundParams;
+				ParamListInfo paramLI;
+
+				if (context->boundParams != NULL &&
+					context->boundParams->type == PARAM_LIST_INFO_DATA)
+					paramLI = (ParamListInfo) context->boundParams;
+				else
+					paramLI = NULL;
 
 				/* Look to see if we've been given a value for this Param */
 				if (param->paramkind == PARAM_EXTERN &&
@@ -2625,6 +2636,32 @@ eval_const_expressions_mutator(Node *node,
 
 				return (Node *) newexpr;
 			}
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+				CacheableExpr *new_subexpr = (CacheableExpr *)
+					eval_const_expressions_mutator((Node *) cachedexpr->subexpr,
+												   context);
+				CachedExpr *new_cachedexpr;
+
+				if (IsA(new_subexpr, Const))
+				{
+					/* successfully simplified it */
+					return (Node *) new_subexpr;
+				}
+				else
+				{
+					/*
+					 * The expression cannot be simplified any further, so build
+					 * and return a replacement CachedExpr node using the
+					 * possibly-simplified arguments of subexpression.
+					 */
+					new_cachedexpr = makeNode(CachedExpr);
+					new_cachedexpr->subexpr = new_subexpr;
+
+					return (Node *) new_cachedexpr;
+				}
+			}
 		case T_FuncExpr:
 			{
 				FuncExpr   *expr = (FuncExpr *) node;
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 4c4f4cd..9c51285 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -1111,6 +1111,7 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
 		root->parse = qry;
 		root->planner_cxt = CurrentMemoryContext;
 		root->hasJoinRTEs = true;
+		root->hasCachedExpr = false;
 
 		groupClauses = (List *) flatten_join_alias_vars(root,
 														(Node *) groupClauses);
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 1b24ddd..d7477d4 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -787,7 +787,8 @@ pg_rewrite_query(Query *query)
  * This is a thin wrapper around planner() and takes the same parameters.
  */
 PlannedStmt *
-pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
+pg_plan_query(Query *querytree, int cursorOptions,
+			  ParamListInfoCommon boundParams)
 {
 	PlannedStmt *plan;
 
@@ -848,7 +849,8 @@ pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
  * The result is a list of PlannedStmt nodes.
  */
 List *
-pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams)
+pg_plan_queries(List *querytrees, int cursorOptions,
+				ParamListInfoCommon boundParams)
 {
 	List	   *stmt_list = NIL;
 	ListCell   *query_list;
@@ -867,6 +869,7 @@ pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams)
 			stmt->utilityStmt = query->utilityStmt;
 			stmt->stmt_location = query->stmt_location;
 			stmt->stmt_len = query->stmt_len;
+			stmt->hasCachedExpr = false;
 		}
 		else
 		{
@@ -1644,6 +1647,7 @@ exec_bind_message(StringInfo input_message)
 		params = (ParamListInfo) palloc(offsetof(ParamListInfoData, params) +
 										numParams * sizeof(ParamExternData));
 		/* we have static list of params, so no hooks needed */
+		params->common.type = PARAM_LIST_INFO_DATA;
 		params->paramFetch = NULL;
 		params->paramFetchArg = NULL;
 		params->paramCompile = NULL;
@@ -1794,7 +1798,8 @@ exec_bind_message(StringInfo input_message)
 	 * will be generated in MessageContext.  The plan refcount will be
 	 * assigned to the Portal, so it will be released at portal destruction.
 	 */
-	cplan = GetCachedPlan(psrc, params, false, NULL);
+	cplan = GetCachedPlan(psrc, (ParamListInfoCommon) params, false, NULL,
+						  false);
 
 	/*
 	 * Now we can define the portal.
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4da1f8f..db31839 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1068,6 +1068,7 @@ ProcessUtilitySlow(ParseState *pstate,
 							wrapper->utilityStmt = stmt;
 							wrapper->stmt_location = pstmt->stmt_location;
 							wrapper->stmt_len = pstmt->stmt_len;
+							wrapper->hasCachedExpr = false;
 
 							ProcessUtility(wrapper,
 										   queryString,
@@ -1148,6 +1149,7 @@ ProcessUtilitySlow(ParseState *pstate,
 								wrapper->utilityStmt = stmt;
 								wrapper->stmt_location = pstmt->stmt_location;
 								wrapper->stmt_len = pstmt->stmt_len;
+								wrapper->hasCachedExpr = false;
 								ProcessUtility(wrapper,
 											   queryString,
 											   PROCESS_UTILITY_SUBCOMMAND,
diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c
index 86f916f..ff7586a 100644
--- a/src/backend/utils/adt/domains.c
+++ b/src/backend/utils/adt/domains.c
@@ -138,7 +138,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)
 		{
@@ -178,7 +179,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/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8514c21..2e72e74 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7661,6 +7661,11 @@ get_rule_expr(Node *node, deparse_context *context,
 			get_windowfunc_expr((WindowFunc *) node, context);
 			break;
 
+		case T_CachedExpr:
+			get_rule_expr((Node *) ((CachedExpr *) node)->subexpr, context,
+						  showimplicit);
+			break;
+
 		case T_ArrayRef:
 			{
 				ArrayRef   *aref = (ArrayRef *) node;
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 853c1f6..25e4c7e 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -90,11 +90,12 @@ static CachedPlanSource *first_saved_plan = NULL;
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
 static List *RevalidateCachedQuery(CachedPlanSource *plansource,
 					  QueryEnvironment *queryEnv);
-static bool CheckCachedPlan(CachedPlanSource *plansource);
+static bool CheckCachedPlan(CachedPlanSource *plansource,
+							ParamListInfoPrecalculation boundParams);
 static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
-				ParamListInfo boundParams, QueryEnvironment *queryEnv);
+				ParamListInfoCommon boundParams, QueryEnvironment *queryEnv);
 static bool choose_custom_plan(CachedPlanSource *plansource,
-				   ParamListInfo boundParams);
+				   ParamListInfoCommon boundParams);
 static double cached_plan_cost(CachedPlan *plan, bool include_planner);
 static Query *QueryListGetPrimaryStmt(List *stmts);
 static void AcquireExecutorLocks(List *stmt_list, bool acquire);
@@ -785,6 +786,65 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
 }
 
 /*
+ * CheckNotConst: check if the bound params are not constants.
+ */
+static bool
+CheckNotConst(ParamListInfoPrecalculation boundParams, int start_index)
+{
+	int			index;
+
+	if (boundParams == NULL)
+		return true;
+
+	if (start_index < 0)
+		start_index = 0;
+
+	for (index = start_index; index < boundParams->numParams; ++index)
+	{
+		if (boundParams->isConstParam[index])
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * CheckBoundParams: check if bound params are compatible in the generic plan.
+ */
+static bool
+CheckBoundParams(ParamListInfoPrecalculation firstBoundParams,
+				 ParamListInfoPrecalculation secondBoundParams)
+{
+	int			numCommonParams,
+				index;
+
+	if (firstBoundParams == NULL || secondBoundParams == NULL)
+		numCommonParams = 0;
+	else if (firstBoundParams->numParams <= secondBoundParams->numParams)
+		numCommonParams = firstBoundParams->numParams;
+	else
+		numCommonParams = secondBoundParams->numParams;
+
+	/*
+	 * Check that the common parameters are equal (both are constants or both
+	 * are not).
+	 */
+	for (index = 0; index < numCommonParams; ++index)
+	{
+		if (firstBoundParams->isConstParam[index] !=
+			secondBoundParams->isConstParam[index])
+			return false;
+	}
+
+	/*
+	 * Check that the other parameters are not constants, so they have not
+	 * previously been precalculated and will not be precalculated.
+	 */
+	return (CheckNotConst(firstBoundParams, numCommonParams) &&
+			CheckNotConst(secondBoundParams, numCommonParams));
+}
+
+/*
  * CheckCachedPlan: see if the CachedPlanSource's generic plan is valid.
  *
  * Caller must have already called RevalidateCachedQuery to verify that the
@@ -794,7 +854,8 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
  * (We must do this for the "true" result to be race-condition-free.)
  */
 static bool
-CheckCachedPlan(CachedPlanSource *plansource)
+CheckCachedPlan(CachedPlanSource *plansource,
+				ParamListInfoPrecalculation boundParams)
 {
 	CachedPlan *plan = plansource->gplan;
 
@@ -845,8 +906,10 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		 */
 		if (plan->is_valid)
 		{
-			/* Successfully revalidated and locked the query. */
-			return true;
+			/*
+			 * Successfully revalidated and locked the query. Check boundParams.
+			 */
+			return CheckBoundParams(plan->boundParams, boundParams);
 		}
 
 		/* Oops, the race case happened.  Release useless locks. */
@@ -879,7 +942,7 @@ CheckCachedPlan(CachedPlanSource *plansource)
  */
 static CachedPlan *
 BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
-				ParamListInfo boundParams, QueryEnvironment *queryEnv)
+				ParamListInfoCommon boundParams, QueryEnvironment *queryEnv)
 {
 	CachedPlan *plan;
 	List	   *plist;
@@ -1002,6 +1065,35 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_saved = false;
 	plan->is_valid = true;
 
+	/*
+	 * Specify the precalculation parameters. If the plan is not one-shot, make
+	 * a deep copy.
+	 */
+	if (boundParams != NULL &&
+		boundParams->type == PARAM_LIST_INFO_PRECALCULATION_DATA)
+	{
+		ParamListInfoPrecalculation params =
+			(ParamListInfoPrecalculation) boundParams;
+
+		if (!plansource->is_oneshot)
+		{
+			plan->boundParams = (ParamListInfoPrecalculation) palloc(
+				offsetof(ParamListInfoPrecalculationData, isConstParam) +
+				params->numParams * sizeof(bool));
+
+			memcpy(plan->boundParams, params,
+				   sizeof(ParamListInfoPrecalculationData));
+		}
+		else
+		{
+			plan->boundParams = params;
+		}
+	}
+	else
+	{
+		plan->boundParams = NULL;
+	}
+
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
 
@@ -1016,7 +1108,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
  * This defines the policy followed by GetCachedPlan.
  */
 static bool
-choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
+choose_custom_plan(CachedPlanSource *plansource,
+				   ParamListInfoCommon boundParams)
 {
 	double		avg_custom_cost;
 
@@ -1025,7 +1118,8 @@ choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
 		return true;
 
 	/* Otherwise, never any point in a custom plan if there's no parameters */
-	if (boundParams == NULL)
+	if (boundParams == NULL ||
+		boundParams->type != PARAM_LIST_INFO_DATA)
 		return false;
 	/* ... nor for transaction control statements */
 	if (IsTransactionStmtPlan(plansource))
@@ -1114,6 +1208,50 @@ cached_plan_cost(CachedPlan *plan, bool include_planner)
 }
 
 /*
+ * GetPrecalculationData: get ParamListInfoPrecalculationData from the source.
+ */
+static ParamListInfoPrecalculation
+GetPrecalculationData(ParamListInfoCommon boundParams)
+{
+	ParamListInfoPrecalculation result;
+	ParamListInfoDataType boundParamsType;
+
+	if (boundParams == NULL)
+		return NULL;
+
+	boundParamsType = boundParams->type;
+
+	switch (boundParamsType)
+	{
+		case PARAM_LIST_INFO_DATA:
+			{
+				ParamListInfo params = (ParamListInfo) boundParams;
+				int 		index;
+
+				result = (ParamListInfoPrecalculation) palloc(
+					offsetof(ParamListInfoPrecalculationData, isConstParam) +
+					params->numParams * sizeof(bool));
+
+				result->common.type = PARAM_LIST_INFO_PRECALCULATION_DATA;
+				result->numParams = params->numParams;
+				for (index = 0; index < params->numParams; ++index)
+					result->isConstParam[index] =
+						params->params[index].pflags & PARAM_FLAG_CONST;
+			}
+			break;
+		case PARAM_LIST_INFO_PRECALCULATION_DATA:
+			result = (ParamListInfoPrecalculation) boundParams;
+			break;
+		default:
+			elog(ERROR, "unrecognized ParamListInfoData type: %d",
+				 (int) boundParamsType);
+			break;
+	}
+
+	return result;
+}
+
+/*
  * GetCachedPlan: get a cached plan from a CachedPlanSource.
  *
  * This function hides the logic that decides whether to use a generic
@@ -1130,14 +1268,22 @@ cached_plan_cost(CachedPlan *plan, bool include_planner)
  *
  * Note: if any replanning activity is required, the caller's memory context
  * is used for that work.
+ *
+ * Note: set genericPlanPrecalculateConstBoundParams to true only if you are
+ * sure that the bound parameters will remain (non)constant quite often for the
+ * next calls to this function.  Otherwise the generic plan will be rebuilt each
+ * time when there's a bound parameter that was constant for the previous
+ * generic plan and is not constant now or vice versa.
  */
 CachedPlan *
-GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
-			  bool useResOwner, QueryEnvironment *queryEnv)
+GetCachedPlan(CachedPlanSource *plansource, ParamListInfoCommon boundParams,
+			  bool useResOwner, QueryEnvironment *queryEnv,
+			  bool genericPlanPrecalculateConstBoundParams)
 {
 	CachedPlan *plan = NULL;
 	List	   *qlist;
 	bool		customplan;
+	ParamListInfoPrecalculation genericPlanBoundParams;
 
 	/* Assert caller is doing things in a sane order */
 	Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
@@ -1154,7 +1300,13 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	if (!customplan)
 	{
-		if (CheckCachedPlan(plansource))
+		/* set the parameters for the generic plan */
+		if (genericPlanPrecalculateConstBoundParams)
+			genericPlanBoundParams = GetPrecalculationData(boundParams);
+		else
+			genericPlanBoundParams = NULL;
+
+		if (CheckCachedPlan(plansource, genericPlanBoundParams))
 		{
 			/* We want a generic plan, and we already have a valid one */
 			plan = plansource->gplan;
@@ -1163,7 +1315,9 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 		else
 		{
 			/* Build a new generic plan */
-			plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv);
+			plan = BuildCachedPlan(plansource, qlist,
+								   (ParamListInfoCommon) genericPlanBoundParams,
+								   queryEnv);
 			/* Just make real sure plansource->gplan is clear */
 			ReleaseGenericPlan(plansource);
 			/* Link the new generic plan into the plansource */
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index f6450c4..b40bed9 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -946,7 +946,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)
@@ -985,11 +986,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);
 
@@ -1006,7 +1010,7 @@ load_domaintype_info(TypeCacheEntry *typentry)
 				ccons = (DomainConstraintState **)
 					repalloc(ccons, cconslen * sizeof(DomainConstraintState *));
 			}
-			ccons[nccons++] = r;
+			ccons[nccons++] = r_state;
 		}
 
 		systable_endscan(scan);
@@ -1043,7 +1047,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)
@@ -1063,15 +1068,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);
 	}
@@ -1100,7 +1107,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);
 }
 
 /*
@@ -1150,16 +1157,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);
@@ -1278,6 +1290,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/executor/execExpr.h b/src/include/executor/execExpr.h
index 080252f..aedefdb 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -219,6 +219,16 @@ typedef enum ExprEvalOp
 	EEOP_SUBPLAN,
 	EEOP_ALTERNATIVE_SUBPLAN,
 
+	/*
+	 * Evaluate CachedExpr.  EEOP_CACHEDEXPR_IF_CACHED is used before
+	 * subexpression evaluation (if subexpression was evaluated use cached value
+	 * and jump to next state or get prepared to subexpression evaluation
+	 * otherwise).  EEOP_CACHEDEXPR_SUBEXPR_END is used after subexpression
+	 * evaluation for caching its result.
+	 */
+	EEOP_CACHEDEXPR_IF_CACHED,
+	EEOP_CACHEDEXPR_SUBEXPR_END,
+
 	/* non-existent operation, used e.g. to check array lengths */
 	EEOP_LAST
 } ExprEvalOp;
@@ -574,6 +584,13 @@ typedef struct ExprEvalStep
 			/* out-of-line state, created by nodeSubplan.c */
 			AlternativeSubPlanState *asstate;
 		}			alternative_subplan;
+
+		/* for EEOP_CACHEDEXPR_* */
+		struct
+		{
+			/* steps for evaluation the same CachedExpr have the same state */
+			struct CachedExprState *state;
+		}			cachedexpr;
 	}			d;
 } ExprEvalStep;
 
@@ -614,6 +631,20 @@ typedef struct ArrayRefState
 } ArrayRefState;
 
 
+/*
+ * Non-inline data for EEOP_CACHEDEXPR_* operations (steps for evaluation the
+ * same CachedExpr have the same state).
+ */
+typedef struct CachedExprState
+{
+	bool		isExecuted;
+	bool		resnull;
+	Datum		resvalue;
+	Oid 		restypid;		/* for copying resvalue of subexpression */
+	int			jumpdone;		/* jump here if result determined */
+} CachedExprState;
+
+
 /* functions in execExpr.c */
 extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c9a5279..d21d5cb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -801,25 +801,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 c5b5115..31039be 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/params.h b/src/include/nodes/params.h
index b198db5..ce45e3e 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -96,6 +96,8 @@ typedef struct ParamExternData
 } ParamExternData;
 
 typedef struct ParamListInfoData *ParamListInfo;
+typedef struct ParamListInfoCommonData *ParamListInfoCommon;
+typedef struct ParamListInfoPrecalculationData *ParamListInfoPrecalculation;
 
 typedef ParamExternData *(*ParamFetchHook) (ParamListInfo params,
 											int paramid, bool speculative,
@@ -107,8 +109,20 @@ typedef void (*ParamCompileHook) (ParamListInfo params, struct Param *param,
 
 typedef void (*ParserSetupHook) (struct ParseState *pstate, void *arg);
 
+typedef enum ParamListInfoDataType
+{
+	PARAM_LIST_INFO_DATA,		/* for ParamListInfoData */
+	PARAM_LIST_INFO_PRECALCULATION_DATA	/* for ParamListInfoPrecalculationData */
+}	ParamListInfoDataType;
+
+typedef struct ParamListInfoCommonData
+{
+	ParamListInfoDataType type;
+}	ParamListInfoCommonData;
+
 typedef struct ParamListInfoData
 {
+	ParamListInfoCommonData common;
 	ParamFetchHook paramFetch;	/* parameter fetch hook */
 	void	   *paramFetchArg;
 	ParamCompileHook paramCompile;	/* parameter compile hook */
@@ -150,6 +164,14 @@ typedef struct ParamExecData
 } ParamExecData;
 
 
+typedef struct ParamListInfoPrecalculationData
+{
+	ParamListInfoCommonData common;
+	int			numParams;		/* number of elements in isConstParam array */
+	bool		isConstParam[FLEXIBLE_ARRAY_MEMBER];
+}	ParamListInfoPrecalculationData;
+
+
 /* Functions found in src/backend/nodes/params.c */
 extern ParamListInfo copyParamList(ParamListInfo from);
 extern Size EstimateParamListSpace(ParamListInfo paramLI);
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index d763da6..7a25c15 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -96,6 +96,8 @@ 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" */
+
+	bool		hasCachedExpr;	/* true if any expressions are cached */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 074ae0a..5fa1dff 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
@@ -790,7 +822,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) */
@@ -810,7 +842,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 */
@@ -834,7 +866,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) */
@@ -859,7 +891,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 */
@@ -906,7 +938,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 */
@@ -936,7 +968,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 */
@@ -952,7 +984,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 */
@@ -986,7 +1018,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 */
 
@@ -1034,7 +1066,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 */
@@ -1048,7 +1080,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 */
@@ -1066,7 +1098,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 */
@@ -1107,7 +1139,7 @@ typedef enum SQLValueFunctionOp
 
 typedef struct SQLValueFunction
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	SQLValueFunctionOp op;		/* which function this is */
 	Oid			type;			/* result type/typmod */
 	int32		typmod;
@@ -1145,7 +1177,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 */
@@ -1183,7 +1215,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 */
@@ -1206,7 +1238,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 */
@@ -1223,7 +1255,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) */
@@ -1233,6 +1265,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.
@@ -1243,7 +1293,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/nodes/relation.h b/src/include/nodes/relation.h
index 3b9d303..4c3b85f 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -93,7 +93,7 @@ typedef struct PlannerGlobal
 {
 	NodeTag		type;
 
-	ParamListInfo boundParams;	/* Param values provided to planner() */
+	ParamListInfoCommon boundParams;	/* Param values provided to planner() */
 
 	List	   *subplans;		/* Plans for SubPlan nodes */
 
@@ -317,6 +317,8 @@ typedef struct PlannerInfo
 
 	/* optional private data for join_search_hook, e.g., GEQO */
 	void	   *join_search_private;
+
+	bool		hasCachedExpr;	/* true if any expressions are cached */
 } PlannerInfo;
 
 
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 2801bfd..c77ce78 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -21,7 +21,7 @@
 /* Hook for plugins to get control in planner() */
 typedef PlannedStmt *(*planner_hook_type) (Query *parse,
 										   int cursorOptions,
-										   ParamListInfo boundParams);
+										   ParamListInfoCommon boundParams);
 extern PGDLLIMPORT planner_hook_type planner_hook;
 
 /* Hook for plugins to get control when grouping_planner() plans upper rels */
@@ -33,9 +33,9 @@ extern PGDLLIMPORT create_upper_paths_hook_type create_upper_paths_hook;
 
 
 extern PlannedStmt *planner(Query *parse, int cursorOptions,
-		ParamListInfo boundParams);
+		ParamListInfoCommon boundParams);
 extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
-				 ParamListInfo boundParams);
+				 ParamListInfoCommon boundParams);
 
 extern PlannerInfo *subquery_planner(PlannerGlobal *glob, Query *parse,
 				 PlannerInfo *parent_root,
@@ -61,4 +61,9 @@ extern List *get_partitioned_child_rels(PlannerInfo *root, Index rti);
 extern List *get_partitioned_child_rels_for_join(PlannerInfo *root,
 									Relids join_relids);
 
+extern PathTarget *replace_pathtarget_cached_expressions(PathTarget *target,
+														 PlannerInfo *root);
+
+extern List *replace_qual_cached_expressions(List *quals, PlannerInfo *root);
+
 #endif							/* PLANNER_H */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index 0d3ec92..1b4fd8f 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -65,8 +65,12 @@ extern void split_pathtarget_at_srfs(PlannerInfo *root,
 						 PathTarget *target, PathTarget *input_target,
 						 List **targets, List **targets_contain_srfs);
 
-/* Convenience macro to get a PathTarget with valid cost/width fields */
+/*
+ * Convenience macro to get a PathTarget with valid cost/width fields and
+ * cached expressions.
+ */
 #define create_pathtarget(root, tlist) \
-	set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
+	set_pathtarget_cost_width(root, replace_pathtarget_cached_expressions( \
+		make_pathtarget_from_tlist(tlist), root))
 
 #endif							/* TLIST_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 62c7f6c..24ab0c8 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -58,9 +58,9 @@ extern List *pg_analyze_and_rewrite_params(RawStmt *parsetree,
 							  void *parserSetupArg,
 							  QueryEnvironment *queryEnv);
 extern PlannedStmt *pg_plan_query(Query *querytree, int cursorOptions,
-			  ParamListInfo boundParams);
+			  ParamListInfoCommon boundParams);
 extern List *pg_plan_queries(List *querytrees, int cursorOptions,
-				ParamListInfo boundParams);
+				ParamListInfoCommon boundParams);
 
 extern bool check_max_stack_depth(int *newval, void **extra, GucSource source);
 extern void assign_max_stack_depth(int newval, void *extra);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 87fab19..2dadb88 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -138,6 +138,13 @@ typedef struct CachedPlan
 	bool		dependsOnRole;	/* is plan specific to that role? */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
+
+	/*
+	 * Used to check whether the generic plan is valid for the new
+	 * boundParams; NULL for the custom plans.
+	 */
+	ParamListInfoPrecalculation boundParams;
+
 	int			generation;		/* parent's generation number for this plan */
 	int			refcount;		/* count of live references to this struct */
 	MemoryContext context;		/* context containing this CachedPlan */
@@ -177,9 +184,10 @@ extern List *CachedPlanGetTargetList(CachedPlanSource *plansource,
 						QueryEnvironment *queryEnv);
 
 extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
-			  ParamListInfo boundParams,
+			  ParamListInfoCommon boundParams,
 			  bool useResOwner,
-			  QueryEnvironment *queryEnv);
+			  QueryEnvironment *queryEnv,
+			  bool genericPlanPrecalculateConstBoundParams);
 extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
 
 #endif							/* PLANCACHE_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index c203dab..6760c8c 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -167,6 +167,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,
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index dd575e7..97fc003 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3453,6 +3453,7 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 	/* initialize our ParamListInfo with appropriate hook functions */
 	estate->paramLI = (ParamListInfo)
 		palloc(offsetof(ParamListInfoData, params));
+	estate->paramLI->common.type = PARAM_LIST_INFO_DATA;
 	estate->paramLI->paramFetch = plpgsql_param_fetch;
 	estate->paramLI->paramFetchArg = (void *) estate;
 	estate->paramLI->paramCompile = plpgsql_param_compile;
@@ -6521,6 +6522,7 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
 	Query	   *query;
 	CachedPlan *cplan;
 	MemoryContext oldcontext;
+	ListCell   *cell;
 
 	/*
 	 * Initialize to "not simple".
@@ -6602,6 +6604,22 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
 	/* Can't fail, because we checked for a single CachedPlanSource above */
 	Assert(cplan != NULL);
 
+	/* Check that there're no cached expressions in the plan.
+	 *
+	 * If CachedExpr will not be initialized by ExecInitCachedExpr possibly it
+	 * will use cached value when it shouldn't (for example, snapshot has
+	 * changed).
+	 */
+	foreach(cell, cplan->stmt_list)
+	{
+		if (((PlannedStmt *) cell->data.ptr_value)->hasCachedExpr)
+		{
+			/* Oops, release refcount and fail */
+			ReleaseCachedPlan(cplan, true);
+			return;
+		}
+	}
+
 	/* Share the remaining work with replan code path */
 	exec_save_simple_expr(expr, cplan);
 
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
new file mode 100644
index 0000000..6bb0c90
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -0,0 +1,6645 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+-- Create domains for testing
+CREATE DOMAIN my_integer_no_check AS integer;
+CREATE DOMAIN my_integer_not_null AS integer;
+CREATE DOMAIN my_integer_vlt_check AS integer CHECK (VALUE === 1);
+CREATE DOMAIN my_integer_stl_check AS integer CHECK (VALUE ==== 1);
+CREATE DOMAIN my_integer_imm_check AS integer CHECK (VALUE ===== 1);
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_no_check (
+  my_integer_no_check
+)
+RETURNS my_integer_no_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_no_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_not_null (
+  my_integer_not_null
+)
+RETURNS my_integer_not_null STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_not_null';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_vlt_check (
+  my_integer_vlt_check
+)
+RETURNS my_integer_vlt_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_stl_check (
+  my_integer_stl_check
+)
+RETURNS my_integer_stl_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_imm_check (
+  my_integer_imm_check
+)
+RETURNS my_integer_imm_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_imm_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_vlt_check (
+  my_integer_vlt_check[]
+)
+RETURNS my_integer_vlt_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_stl_check (
+  my_integer_stl_check[]
+)
+RETURNS my_integer_stl_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Functions testing
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Multidimensional ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+-- Array subscripting operations testing
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- FieldSelect expressions testing
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- RelabelType expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- CoerceViaIO expressions testing
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- RowCompareExpr testing
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- SQLValueFunction testing
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_name(current_role) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(current_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(session_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+(4 rows)
+
+SELECT x_stl2_name(current_catalog) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ regression
+ regression
+ regression
+ regression
+(4 rows)
+
+SELECT x_stl2_name(current_schema) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ public
+ public
+ public
+ public
+(4 rows)
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+-- Xml expressions testing
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+NOTICE:  s2 xml
+          x_stl2_xml          
+------------------------------
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+NOTICE:  s2 xml
+          x_stl2_xml          
+------------------------------
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+NOTICE:  s2 xml
+                          x_stl2_xml                          
+--------------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+NOTICE:  s2 text
+                       x_stl2_text                       
+---------------------------------------------------------
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+NOTICE:  s text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- CoerceToDomain expressions testing
+SELECT x_stl2_my_integer_no_check(1::my_integer_no_check) FROM x;
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(1::my_integer_not_null) FROM x;
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_vlt_check(1::my_integer_vlt_check) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+ x_stl2_my_integer_vlt_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(1::my_integer_stl_check) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(1::my_integer_imm_check) FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- Mixed functions and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- Mixed ArrayCoerce and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_my_integer_vlt_check(
+  '{1, 1}'::integer[]::my_integer_vlt_check[]
+)
+FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+ x_stl2_array_my_integer_vlt_check 
+-----------------------------------
+ {1,1}
+ {1,1}
+ {1,1}
+ {1,1}
+(4 rows)
+
+SELECT x_stl2_array_my_integer_stl_check(
+  '{1, 1}'::integer[]::my_integer_stl_check[]
+)
+FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 array_my_integer_stl_check
+ x_stl2_array_my_integer_stl_check 
+-----------------------------------
+ {1,1}
+ {1,1}
+ {1,1}
+ {1,1}
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+NOTICE:  s text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- ROW() expressions with dropped columns testing
+ALTER TABLE wxyz DROP COLUMN z;
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      4
+(1 row)
+
+INSERT INTO x VALUES (5);
+SELECT simple();
+ simple 
+--------
+      5
+(1 row)
+
+ROLLBACK;
+-- Prepared statements testing
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+-- Drop tables and domains for testing
+DROP TABLE x;
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
+DROP FUNCTION x_stl2_my_integer_no_check;
+DROP DOMAIN my_integer_no_check;
+DROP FUNCTION x_stl2_my_integer_not_null;
+DROP DOMAIN my_integer_not_null;
+DROP FUNCTION x_stl2_my_integer_vlt_check;
+DROP FUNCTION x_stl2_array_my_integer_vlt_check;
+DROP DOMAIN my_integer_vlt_check;
+DROP FUNCTION x_stl2_my_integer_stl_check;
+DROP FUNCTION x_stl2_array_my_integer_stl_check;
+DROP DOMAIN my_integer_stl_check;
+DROP FUNCTION x_stl2_my_integer_imm_check;
+DROP DOMAIN my_integer_imm_check;
diff --git a/src/test/regress/expected/precalculate_stable_functions_1.out b/src/test/regress/expected/precalculate_stable_functions_1.out
new file mode 100644
index 0000000..b640bd9
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions_1.out
@@ -0,0 +1,6291 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+-- Create domains for testing
+CREATE DOMAIN my_integer_no_check AS integer;
+CREATE DOMAIN my_integer_not_null AS integer;
+CREATE DOMAIN my_integer_vlt_check AS integer CHECK (VALUE === 1);
+CREATE DOMAIN my_integer_stl_check AS integer CHECK (VALUE ==== 1);
+CREATE DOMAIN my_integer_imm_check AS integer CHECK (VALUE ===== 1);
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_no_check (
+  my_integer_no_check
+)
+RETURNS my_integer_no_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_no_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_not_null (
+  my_integer_not_null
+)
+RETURNS my_integer_not_null STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_not_null';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_vlt_check (
+  my_integer_vlt_check
+)
+RETURNS my_integer_vlt_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_stl_check (
+  my_integer_stl_check
+)
+RETURNS my_integer_stl_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_imm_check (
+  my_integer_imm_check
+)
+RETURNS my_integer_imm_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_imm_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_vlt_check (
+  my_integer_vlt_check[]
+)
+RETURNS my_integer_vlt_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_stl_check (
+  my_integer_stl_check[]
+)
+RETURNS my_integer_stl_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Functions testing
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Multidimensional ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+-- Array subscripting operations testing
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- FieldSelect expressions testing
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- RelabelType expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- CoerceViaIO expressions testing
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- RowCompareExpr testing
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- SQLValueFunction testing
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_name(current_role) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(current_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(session_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+(4 rows)
+
+SELECT x_stl2_name(current_catalog) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ regression
+ regression
+ regression
+ regression
+(4 rows)
+
+SELECT x_stl2_name(current_schema) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ public
+ public
+ public
+ public
+(4 rows)
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+-- Xml expressions testing
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FRO...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   '<?xml version="1.0"?><content>abc</content>',
+          ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   DOCUMENT '<?xml version="1.0"?><book><title>Manual</title>...
+                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+                  ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS D...
+                              ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+-- NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- CoerceToDomain expressions testing
+SELECT x_stl2_my_integer_no_check(1::my_integer_no_check) FROM x;
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(1::my_integer_not_null) FROM x;
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_vlt_check(1::my_integer_vlt_check) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+ x_stl2_my_integer_vlt_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(1::my_integer_stl_check) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(1::my_integer_imm_check) FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- Mixed functions and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- Mixed ArrayCoerce and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_my_integer_vlt_check(
+  '{1, 1}'::integer[]::my_integer_vlt_check[]
+)
+FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+ x_stl2_array_my_integer_vlt_check 
+-----------------------------------
+ {1,1}
+ {1,1}
+ {1,1}
+ {1,1}
+(4 rows)
+
+SELECT x_stl2_array_my_integer_stl_check(
+  '{1, 1}'::integer[]::my_integer_stl_check[]
+)
+FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 array_my_integer_stl_check
+ x_stl2_array_my_integer_stl_check 
+-----------------------------------
+ {1,1}
+ {1,1}
+ {1,1}
+ {1,1}
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+-- Mixed functions and NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- ROW() expressions with dropped columns testing
+ALTER TABLE wxyz DROP COLUMN z;
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      4
+(1 row)
+
+INSERT INTO x VALUES (5);
+SELECT simple();
+ simple 
+--------
+      5
+(1 row)
+
+ROLLBACK;
+-- Prepared statements testing
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+-- Drop tables and domains for testing
+DROP TABLE x;
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
+DROP FUNCTION x_stl2_my_integer_no_check;
+DROP DOMAIN my_integer_no_check;
+DROP FUNCTION x_stl2_my_integer_not_null;
+DROP DOMAIN my_integer_not_null;
+DROP FUNCTION x_stl2_my_integer_vlt_check;
+DROP FUNCTION x_stl2_array_my_integer_vlt_check;
+DROP DOMAIN my_integer_vlt_check;
+DROP FUNCTION x_stl2_my_integer_stl_check;
+DROP FUNCTION x_stl2_array_my_integer_stl_check;
+DROP DOMAIN my_integer_stl_check;
+DROP FUNCTION x_stl2_my_integer_imm_check;
+DROP DOMAIN my_integer_imm_check;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e224977..0e893df 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -116,7 +116,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid c
 # ----------
 # Another group of parallel tests
 # ----------
-test: identity partition_join partition_prune reloptions hash_part
+test: identity partition_join partition_prune reloptions hash_part precalculate_stable_functions
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 9fc5f1a..54470ea 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -184,5 +184,6 @@ test: partition_join
 test: partition_prune
 test: reloptions
 test: hash_part
+test: precalculate_stable_functions
 test: event_trigger
 test: stats
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
new file mode 100644
index 0000000..9437f24
--- /dev/null
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -0,0 +1,2117 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+
+-- Create volatile functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create stable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create immutable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create operators for testing
+
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+-- Create domains for testing
+
+CREATE DOMAIN my_integer_no_check AS integer;
+CREATE DOMAIN my_integer_not_null AS integer;
+CREATE DOMAIN my_integer_vlt_check AS integer CHECK (VALUE === 1);
+CREATE DOMAIN my_integer_stl_check AS integer CHECK (VALUE ==== 1);
+CREATE DOMAIN my_integer_imm_check AS integer CHECK (VALUE ===== 1);
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_no_check (
+  my_integer_no_check
+)
+RETURNS my_integer_no_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_no_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_not_null (
+  my_integer_not_null
+)
+RETURNS my_integer_not_null STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_not_null';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_vlt_check (
+  my_integer_vlt_check
+)
+RETURNS my_integer_vlt_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_stl_check (
+  my_integer_stl_check
+)
+RETURNS my_integer_stl_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_imm_check (
+  my_integer_imm_check
+)
+RETURNS my_integer_imm_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_imm_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_vlt_check (
+  my_integer_vlt_check[]
+)
+RETURNS my_integer_vlt_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_stl_check (
+  my_integer_stl_check[]
+)
+RETURNS my_integer_stl_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Functions testing
+
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+SELECT x_stl() FROM x;
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM x WHERE x_stl() < x;
+
+-- JOIN/ON clause testing
+
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+SELECT x_stl2(1) FROM x;
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2(x_stl()) FROM x;
+SELECT x_imm2(x_stl()) FROM x;
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+
+-- Operators testing
+
+SELECT 1 === 2 FROM x; -- should not be precalculated
+SELECT 1 ==== 2 FROM x;
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+SELECT x_stl() ==== 1 FROM x;
+
+-- IS (NOT) DISTINCT FROM expression testing
+
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- Boolean expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+
+-- ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+
+-- Multidimensional ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+
+-- Array subscripting operations testing
+
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+
+-- Mixed functions and array subscripting operations testing
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+
+-- FieldSelect expressions testing
+
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+
+-- ROW() expressions testing
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+
+-- RelabelType expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+
+-- CoerceViaIO expressions testing
+
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+
+-- Mixed functions and CoerceViaIO expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+
+-- ArrayCoerce expressions testing
+
+-- Binary-coercible types:
+
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+
+-- ConvertRowtypeExpr testing
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+
+-- Mixed functions and ConvertRowtypeExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+-- CASE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+
+-- RowCompareExpr testing
+
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+
+-- Mixed functions and RowCompareExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+
+-- COALESCE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+
+-- GREATEST and LEAST functions testing
+
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+
+-- Mixed functions and GREATEST and LEAST functions testing
+
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+
+-- SQLValueFunction testing
+
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+
+SELECT x_stl2_name(current_role) FROM x;
+SELECT x_stl2_name(current_user) FROM x;
+SELECT x_stl2_name(user) FROM x;
+SELECT x_stl2_name(session_user) FROM x;
+SELECT x_stl2_name(current_catalog) FROM x;
+SELECT x_stl2_name(current_schema) FROM x;
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+
+-- Xml expressions testing
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+
+-- NullTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+
+-- BooleanTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+
+-- CoerceToDomain expressions testing
+
+SELECT x_stl2_my_integer_no_check(1::my_integer_no_check) FROM x;
+SELECT x_stl2_my_integer_not_null(1::my_integer_not_null) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_vlt_check(1::my_integer_vlt_check) FROM x;
+
+SELECT x_stl2_my_integer_stl_check(1::my_integer_stl_check) FROM x;
+SELECT x_stl2_my_integer_imm_check(1::my_integer_imm_check) FROM x;
+
+-- Mixed functions and CoerceToDomain expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+
+-- Mixed ArrayCoerce and CoerceToDomain expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_my_integer_vlt_check(
+  '{1, 1}'::integer[]::my_integer_vlt_check[]
+)
+FROM x;
+
+SELECT x_stl2_array_my_integer_stl_check(
+  '{1, 1}'::integer[]::my_integer_stl_check[]
+)
+FROM x;
+
+-- Tracking functions testing
+
+SET track_functions TO 'all';
+
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+SELECT x_stl() FROM x;
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM x WHERE x_stl() < x;
+
+-- JOIN/ON clause testing
+
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+SELECT x_stl2(1) FROM x;
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2(x_stl()) FROM x;
+SELECT x_imm2(x_stl()) FROM x;
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+SELECT 1 ==== 2 FROM x;
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+SELECT x_stl() ==== 1 FROM x;
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- Mixed functions and boolean expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+
+-- Mixed functions and ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+
+-- Mixed functions and array subscripting operations testing
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+
+-- Mixed functions and CoerceViaIO expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+
+-- Mixed functions and ConvertRowtypeExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+-- Mixed functions and CASE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+
+-- Mixed functions and RowCompareExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+
+-- Mixed functions and COALESCE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+
+-- Mixed functions and NullTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+
+-- Mixed functions and BooleanTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+
+-- Mixed functions and CoerceToDomain expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+
+SET track_functions TO DEFAULT;
+
+-- ROW() expressions with dropped columns testing
+
+ALTER TABLE wxyz DROP COLUMN z;
+
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+
+BEGIN;
+SELECT simple();
+INSERT INTO x VALUES (5);
+SELECT simple();
+ROLLBACK;
+
+-- Prepared statements testing
+
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+
+-- Drop tables and domains for testing
+
+DROP TABLE x;
+
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
+
+DROP FUNCTION x_stl2_my_integer_no_check;
+DROP DOMAIN my_integer_no_check;
+
+DROP FUNCTION x_stl2_my_integer_not_null;
+DROP DOMAIN my_integer_not_null;
+
+DROP FUNCTION x_stl2_my_integer_vlt_check;
+DROP FUNCTION x_stl2_array_my_integer_vlt_check;
+DROP DOMAIN my_integer_vlt_check;
+
+DROP FUNCTION x_stl2_my_integer_stl_check;
+DROP FUNCTION x_stl2_array_my_integer_stl_check;
+DROP DOMAIN my_integer_stl_check;
+
+DROP FUNCTION x_stl2_my_integer_imm_check;
+DROP DOMAIN my_integer_imm_check;
-- 
2.7.4

#17Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Marina Polyakova (#16)
Re: [HACKERS] WIP Patch: Precalculate stable functions, infrastructure v1

On 31 December 2017 at 06:55, Marina Polyakova <m.polyakova@postgrespro.ru>

wrote:

Secondly, here there's a sixth version of the patch for the

precalculation of

stable or immutable functions, stable or immutable operators and other
nonvolatile expressions.

Thanks for your patch, looks quite interesting!

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:

Yeah, but it's still 18k lines :) After the first quick glance I have a few
small questions.

If I call a stable function from a query and subquery, looks like it's
cached:

```
=# select stable_with_int(1) from (select stable_with_int(1) from x) q;
NOTICE: 00000: stable with int
LOCATION: exec_stmt_raise, pl_exec.c:3353
stable_with_int
-----------------
1
1
1
1
(4 rows)
```

But the same from CTE works different, is it supposed to be like that?

```
=# with data as (select stable_with_int(1) from x) select
stable_with_int(1) from data;
NOTICE: 00000: stable with int
LOCATION: exec_stmt_raise, pl_exec.c:3353
NOTICE: 00000: stable with int
LOCATION: exec_stmt_raise, pl_exec.c:3353
stable_with_int
-----------------
1
1
1
1
(4 rows)
```

Also I see this pattern quite some time, maybe it makes sense to move it to
a function?

```
+ /* create and return CachedExpr */
+ CachedExpr *new_node = makeNode(CachedExpr);
+ new_node->subexpr = (CacheableExpr *) current_node;
+
+ context->root->hasCachedExpr = true;
+
+ return (Node *) new_node;
```
#18Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Dmitry Dolgov (#17)
Re: [HACKERS] WIP Patch: Precalculate stable functions, infrastructure v1

Thanks for your patch, looks quite interesting!

Glad to hear it :)

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:

Yeah, but it's still 18k lines :)

Here 13k lines - 2 sets of expected results for regression tests..)

After the first quick glance I have
a few
small questions.

If I call a stable function from a query and subquery, looks like it's
cached:

```
=# select stable_with_int(1) from (select stable_with_int(1) from x)
q;
NOTICE: 00000: stable with int
LOCATION: exec_stmt_raise, pl_exec.c:3353
stable_with_int
-----------------
1
1
1
1
(4 rows)
```

But the same from CTE works different, is it supposed to be like that?

```
=# with data as (select stable_with_int(1) from x) select
stable_with_int(1) from data;
NOTICE: 00000: stable with int
LOCATION: exec_stmt_raise, pl_exec.c:3353
NOTICE: 00000: stable with int
LOCATION: exec_stmt_raise, pl_exec.c:3353
stable_with_int
-----------------
1
1
1
1
(4 rows)
```

The function is always cached, but in the first example the plan is
simplified so you only get one call of the function in the entire plan.
(In the function subquery_planner, CTE are processed separately by
calling the function SS_process_ctes. Subqueries are simplified a little
later by calling the function pull_up_subqueries; in our case the
function pull_up_simple_subquery is used.)

Also I see this pattern quite some time, maybe it makes sense to move
it to a function?

```
+ /* create and return CachedExpr */
+ CachedExpr *new_node = makeNode(CachedExpr);
+ new_node->subexpr = (CacheableExpr *) current_node;
+
+ context->root->hasCachedExpr = true;
+
+ return (Node *) new_node;
```

Thanks, I agree with you and I'll change it accordingly.

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#19Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Marina Polyakova (#18)
2 attachment(s)
Re: [HACKERS] WIP Patch: Precalculate stable functions, infrastructure v1

Hello, hackers!

Here there's a seventh version of the patch for the precalculation of
stable or immutable functions, stable or immutable operators and other
nonvolatile expressions. It is rebased on the top of master and has some
code cleanup [1]Fixed:. See attached patch.

About TPC-H tests using dbt3 (see the attached small archieve):
* they were made based on commit
255f14183ac7bc6a83a5bb00d67d5ac7e8b645f1;
* they take some time so I have used only scales 1 and 4;
* 3 attempts are used to eliminate some noise; so you can see the
minimum, median and maximum execution time for each query on the graphs.
About their results:
* as expected, cached expressions were not used in queries;
* it seems that there is no obvious degradation of performance.

Any suggestions are welcome!

[1]: Fixed:

Also I see this pattern quite some time, maybe it makes sense to move
it to a function?

```
+ /* create and return CachedExpr */
+ CachedExpr *new_node = makeNode(CachedExpr);
+ new_node->subexpr = (CacheableExpr *) current_node;
+
+ context->root->hasCachedExpr = true;
+
+ return (Node *) new_node;
```

Thanks, I agree with you and I'll change it accordingly.

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

dbt3.tar.gzapplication/x-gzip; name=dbt3.tar.gzDownload
���\Z��PT��6�JhD��Z$(IriP$��,H��3

��(Q$I$�%K���H��g7����{3�}S������Z�>����Z����N�����������������������3scv{[3�����{���x��ON~^������������������������x91����,��pvt2t��:X��_��_?��?~������4�U���W|�GRO00.kb`\r�\��M���`����PuY_TQ��X�8j:�9�M�W����6���.m(�m&g��^�kVeZ�Mq�V�
D�����U
d����������n��c����������v���I%-uc�J���y���!��z2`��2�}�%:���a` �p���S������(g��e����j����<D����dTT��;�Q.�+o������2)%���������ql�IIA�|��B�t���{�����(�T5T�����;yw�=nn��i��n2��A�PX$���:###��kn"[���{�GGF�����n<�n���m���gF�h��L=�]������G4�#�Z�A-M���������GWi$89����=~<�!�����	wX��e��
`��B�!HKsh��[@b��F�������9t+������`���_������ul���
*(.��K����t(��~+��+/�����e��p�I��������@_?��;())i�H�b�@�S��;�g����&j����ih��-������MN�g����u����I��'��?�K\)�W������G��_�4���=\�=ab�&��u���?���f������������_�4
��Yyy?
2�����h������A\ZZZB%��������E����dpA�pfAA�����V^�������_��9�~��S�.�2=;�6V���"��q�-����t�����>��r��t+�c/� I��q�b�f�Y�G,Z7C��
cm�_�x��C%�"?GP������e�gahmo�z��<v`899��
��S}X4FP5z���v�0%Mr��z66��DQ�����#���6���jQ�99��jyR]������C��:����3�:�[���*%�|����N~O���������������	��S����n~�J1�8���M�.��YkGG�`4��O{	H������XrS��&�}���G;�����%�qh��0o��M���U�o}f}o�c�Nf2hg�� ��~�w^��{,��Qf]�Z���8������,��=���\�V9!�^�<<i��5�k]N]��[EP�Ufe(%Y�:L���S6�6���h�d����xN	s]����`����o�������^8����s�-�Ez�{�J��`��K-v7.�[A�A^l�A�oci	��.��DD�Jm�81�����AN~����^�L�'Q�O�v�q(-��l����5��U�����/��@�E��	5%e�ho/l����Z	�-�b*����������I�^����:!<��F^��C��y�}JE`���7�����nc!A��;8���������"���Q���Z������'*�"�mb��q|)b��+=�+�#������Z�I])���	��K.�7�;��t"""�]��.���+��T���Dw���L7�j������;{q���e:Wi��/	��D���d�7}�[�3���_Z�oT��>00H��{o �����g�rE�99%����Z�������X�������!�$Z@A/;������l�.<��������4pm�!H������hk�� 5�7�;;;�Y-��Pvd^�d����'��,��O�����yC���@H���^T�bf����3-+/���1*�H�90�J.,�d��!4TW��=���g�����"��m�9�b�����"�d��W�R��HU��a��z[�,,,���?�i���x���[�<�%{�J#�.������J��`��dY����#X���^��(����+�*+3��eh��%��l$=E��t�.���v3Nz
��������F�/����+�����������?�b�t�0��:�J�<���_������������&'�F���?9�/e F���U������Q��.��9F{�f�L����:j�����8$���9�HR�yN-ek��JNM�������p��y��		��������A��n��(�l�����=
���zA�8��'�S�����-3���X�o)���9H@B�F��z��R��rZ<�]�J7���{�<��`���U@�p���kp�	����)>22M_��8��#I��y'u�EA���3�nw��R�M�`����]����*����Hm���W� ���4C�����\L��1
G�A������
�X����>8��dg�WP����q�cG^v2��X���������)�;�*u,W��;�;��>%$fL�&�2%-����>�������e,:�W.i)�ES�&	���T��S�Ak��Va	���\@$�B�X#�����U��Gl�,�����:�[��Y�H��8=>��������Her�s0�A������O$8�M�QAP���j[uo�RO���d=�B����Js����TiK��'��ia���ir����~�2�f���d�>QQA�=�"�(�A�+��+W�JNm��U�29��Cc�a������z��������
h����:���r
)o�����IC>�%�zX��W�86�m
�L��>��Zr��Y��@�|8�%J��2@�v�W�(!��,�]x�2Fw�7Y>�a�v��"PA�dwU������+�����!��/_��
�77�W9}II�3Bvj��9��et�����������4�jU�u���s�_B����� � }��~�Z#O�=C����j���KEN�E���pYY������%8����_C]}�EZ������7��!N���L���V����|
}���/&?z����m�WN� �y��>��Y���������M��~{�������;g���������F�I�P�AGc{�,|��.)�����hn��������������}|�F91X�A����<���m2�!hC-�wWb}�:b8~��=hQ=Ns�z��5������j�5K���T����_��755577���Hu��H���2��I��!����>$4$44����v�O{5>>^TPY)(*�� �+1��!6�P���4PWi.�2������w'i�"�T���t���V�[�"��t����=��'�<#�-��[���"�a���m#��n����Y8�'**J�����O�J@H���i�6����RRn�������~���1@�gI
C<4�2.(���_NN�
���`GN���������#''�[���>?}�PPZ�����`\�R����7~�x��i UR����"����k�����-,/��������S��ih$�z�G����A�����������OS
rs�`0�.�N�)nrkWW���&��Z����oZZ��	Q-f�I<�2<�uo�	p��HK��/<z�������zU7HH&?&$(���o/(���R�
%(�^��)*���C�����@�`G.�m�	������C�v<�f��OT�z��F��JJ���;���o��y��M
�w��8�B��'��������vS��tk���(F���7s��c+��q�:W����;�C�:��a�i'
���f��z8���`��g��!-PE��j���o5����K�[>|xW��+���PZZIA�:�k�5�����%e99?�
YEu5��PT?DD8����yM51l�G��F���TU�6g��###[[[�H�3��RH���wk��365
���>�J�`s�3�����
����(�FB�G��������@Q�A<==A�YUU�9W}9�Ii�$g��d�����w���P��V,**=`}������@`�y��������51��BAf�#��(O"�. ���p��w2����3����iCppp��&j=�3��DB	��A���(sj�t83�g��������0k9���6���7�����||������F��'&�_d��E��m{�&����F��6k�	�����7JJJ3S��W�@���������d��������j���1Q�������� ,�p���n1�z�D������L�}ddd��������J#��!eY%�����'&\&�)e"J`��w�?�����@t3LX�g��{o?�7�����b������m���F(�I��>���u�Pv�}Q5���,�����I!a�Z�D�����������px����\��m�������h�p��){�����>�0�jB�p�,s�i�$���^�7X�����D
9%�����������n��e�;IuI��������!1dhP�f1�Y\\���?]�8o,<
�_�a�@��k���i��NI
���J�\r�DSi�7@�CP��������{h^K����D�5��.������_��P�y�����@�k1�����0!jkk ��O���� ��V����56
		�C���yy���gcB��j�u�R�~�#�c6�wL,��	�
�N�]���O��C�!���a���!��P�20uH��|���,�ff�z��C���P�JB���\VG����;�'W�����B7��C���������?�w�W�88&���^@Ya��^]Z[�w^�Y>��b���-���81Q��a�g���L1]��DQ����#o����������OOO_��C$��<�v��?6q�����[o�6g�zQ���aUK����>�����E��u�����D&
S�|�J��H==����]m����G��R���-;#���:�N��IA7��J�VCy���@e-vp��-�ih����-)�!i��0hni��^�:�F�V�PE����������HD4���~�����&���c1���pb"�Obl�t/��t5�.����4Q�(=��l�������B�Ts��
�Pw����~r;��"�4�o��{Hu
���Q��a'��A{:0���z[Q|C�n)Q	D��/�C�3�pP�jj@���>i�h�[�������}D��]Z22���_V����/yy�321�ys��P�r�(���j)�MW�b�`��t�/=���-DSSS�h�,`?�K����P�I,'C={u�ih����a4zx�'G5I����p���>� ?^��?6`Y�#�E������VTh�����Q^N��v�x7=�t�_���w:Q��+I<����Xe=�d�E��1l���"@�8�JB��>k�2������'G{�z����b[g	���=�yi�EKK�
�~
�����fbr�rJk��]3W����W��x�H�uX��^J�������K9�>~tY��p�������BNK_�v,�z1��������`�����gK.8	YYZ�GD����RS�����n^<��6���]e�IqP
	O�Ej��U(����������/a?Zf�4�}F+`��vf�j��$Ri���d��v.�l�P�����$�"
jjf���7k�w(�-Kf������%���H$rnN�s7��M����NX�QT@AY�����Z��������������z=�E_�p��=y�����_�����]V��v�A����v���l'����C��6���QI����C[������v�(���R���
<������'O�4����~D�	78�Oq��h��Q4����K�����$`SDO�$���HKs�@///���$�<z��/� 	�w*
�E��M�Dp�Bt�b�������w����K�.Y9�����?f�?��P98�_:3NFRSW��T{���D@�2��k����c�m�U����#����8~�hb�����7����u����+"��K`NB	`kmmm7����{l���]T�������Z��bl\{=hM���S�[a}=j���y�(g$���������K	X
�>c`R#����PSS{��eu������@0��������)6�&�=s���&�K�/���z�bbc����}�u��M���B���$jT?q��|���n+�
AZ���m�A��k��kh�`b�_��;�ON���Sd�4��@����g��]cF��@�4�.&��]�g��

e</>w�b�'�)oZu��Ao��E�|�QC1��u>���O�Y����BO�w7qStF�.��(%�6H���U����lP���&z��!���m�/���@T��,+/�����d��i1>!�V��+lI���-��������)		�lk�6�+@�h����K�
��7�$�����8W��#�_����6Z�A8�6�4C#I�����JJ�nLQQj**n�o�g���N�� ��"o[dRJD�4$A�$|���911g�^$���r�P){!�������������?�w���'��������G�����N&���RuV���7@�H	J�.�6|:::q�?�\\����A�]�"tm��)��8V��d�� �^d��Y����\�nV����S�V�� zW��UB!����'>H����3P)��.^[��@�T'��qe�0"bb??������������{�h=(I2R�r@l�P�]k_�z�_��G�fI���!.ntxx�J���F�/����w���|:�*a3��IP��zD]b@%���-�#�@��A�>��f�<h��������/_d�}CR��d�"*�����,�
ug���$�g9�.<���Yx�yF�}�d���7~��s�U5��z���*G�+�����!01{HvV�y��
���Ep���:�-�s|�`����c�}�!H6��������m����]!����4���G�r����� ���6��X�u�4�����t&�����.=��==�g�)�����C��Z����9z�����3"�3 �]GO�Yu9��)���������
����J�[PE���h��V���f}�IH���n����8�&(������|�����B N�D��Vg�(@�f{���w�_$T~��o������
��������[+**���0ii�z��Q(%�6����cP������/%��utt��X\������� ���B_�9@/H�gL��m�{���%u����s�Ktm����������z?/�wT������$)��id�� ���T����a�F=9�����X�=qn���-P�E�/�Y�v��eg'����d�����|v2���7��&�0q'��t����h�r`��U��� ��������9�;�^�"�7��mJ!w�Q�F�{��������i��%�5;����BB�s���>����}�*����l�h��\�g���x6�	+B?��=bm��P����>��E\e_(#j���yx6+��Bq�WR(�cl7��s��������H�{�����1��\zJ���kH��R]7�,w�rg�����<b[.-%�S�������gB*��62X\��6��om��ycu��s0FVw�#�(7����y�r�\k~��;\9�*��y���"@E����k������z��\��Uh���;]��y��,�����>������H�.��*�����??�,$,����A;���L���U��{1h;�����h6��(L�����v,�_��gv#S;O�� ������{�r���',�O�4gL�F����tb ��-���)H^���=�
�CCEem'Ve�X�-U��}Mv-���H������x���F������[Wp��������w'vd^[���TC��zvi~~�T�1�������"��P�ee�|OkB���G�����f�exxxii	z�.9���+�������>>>RRR��p��7o������ET���W=�7�S�ud�T�)��6h������O��3������-q�Zo�����p�����+�3����X
Vn�6A��X9/!Z��@9��wvvz�K�|}}}q�BV�8�����v����^,��;A���F~?���m�����2�8����e��.�%++���m��$O�q.D>���+���r=��1��|y��������������L��4�%�W�[l����)�{�DD9X]��n��|U�2q���g�������\N��������������]���=!7f(���ExY��9��'�|�y�WK���7~����G>�&����=����*�OZ~W���)C��3��Wx��5��<\_"��N�����q��
B���������&�� l@K?L�3����+{f#������:8��"�1����2��$V+��D��C�4hvv���oU�����g6-�aR1��mPf'�P�2.��;<NK2r���My+�����@#s����TZ^���@�7������NDM��pym�]`q�����}�3zj�t*����2L�+��kU� �d��5�����A�������r��������7��|�*&6C'J�~�h��<]A���cs^>��p������z���������=P�����������+#-�bD�~z\9fP��EK�����[��j������<2-���h-y6~7��-3#�CF���?�Tf�NTKuu�@~M8s���meO����
�1��)��N=�v*��vvw	�][�������y��zi�*��1�������fz�=13-�X���G�\�Y���
<��;{{�5��R��=�Q��OZU�vvv*%��G	���{]��7v���<5�����+o:25�1���>o(}&%��/�}q�cbpj���()�VM�v����PL���� ##�����#��(����"���m_9_����n���>��������"������w��^�.-���w3��U�;�$��������5u��k��:��*ak?�=���$E�ED����;�c��>��JEC�������U����H;��*w�;g{�K{����$�amm�:3glmm��FG\l�����4f���&>r�����q���|�bP*@zR����q�@���`�)�4���5������|����������������8��&���\xi���-�/1���P����W����{�?Y�	�����?5������D�a_�C|��-$DAK[o�-���O%%%_��Y�3�������).y
�{pz�H������v�u ��1�y�TVj`��~�Sj54���������H�~�x��f���������B	lll�&NN���������&&����
z�x����m/(�b���
,,�4�r�	����|jj�'\f���!_=z�5 v�o�U����g����n���8�wQ?��{C�c	$�!�����`��x�=�n�RK,E�h������hU��p�Yx���Hl,y38���tc@:�uF����xU������F��������������kY����l���/��Q�I�t���_XX��y9�Itt[D�wr��Ao�\�0���z�&�V&�)((�������Vw_c�tX���}m!9Y�����`�����X�����,L��1dd�3��j���;>�������������[���F)hcF���U�^�F(�������|A�K���b�cGAN~���T���k�n��|�t�N�x��s��W������r�5�8g���W���T�������������o�{��������o���������o���/��h��6��D�O���TOh����U����H5q.�C������r}io������
k1�8����x��N�#�^�b^�j�n�=��5C�A]��#V���-����FXX��5��O��``\9"���q�H��O�^��)++KK��<uv����s����qpp����\���&�����>{����2��T;���PTT�n`200�8I�t?�p��CZQQ��DNN�p1F'/'���@��`��By���P%�}�&h?i����fJ�I0���r����(�Vr������{
����!�!��������sp}���.�1����A�"����O;F�ptt%�%��U����~?��SSS����_��h�
�L�+�������[�~�����Q���������b�?��og���X�	b��Gv�7��IG�.!%;Q�I�bN���W��**��������/��&���������p���Y0uQwO��J�����~z��
�����w6���?�����[�_�W��A���J����T��l�r����#������g���:2L~��T����U�rr*�7���7����{w������7���rt.;��Q=����e\�&��c��?B��n� ��6�	�g�8k7�������.����T�b�+:H�+]u�b��)�(�HN:�z:V�����M��f�\;�1dc�z�t��v��9�E��n�?�@�-4���I�{�?�d�]��
�I������p����"���1	�{{�i@8x�?��TUSk+%�q�{��vp(����qS~8$>y�`����5r��7o��������!v�r������K����(h\�E"������s�V�_7��O���5R'B8��/�*�^��H�������D?��H��Uo�����S��`�
��0x&&&�:]��mW�Hh���I�`������<n���O�P����������1��W9��F*��HD�+C�T�C��~�����m�#Uc���m�yXVS#�����Q���Uv�-IRRr�����|�t��3�oXME%��:823,��r����$L������aKh%�����<00p���������6�<7�je13DM�{~�i-v�Hiz@���x,%E	����gU���~`�kE;��G�h�����\��H�z���	o�L�	&���|���0����R!D
�R���G�b����B�����.�-��^�+6��R���������>,x��Y9����l����>RjU��v{����8MS��(��Q���#�vL�������	��"��/!�_G������:��=�J��,�F�!z��I�u-$x�����|�v�bS4A�vr�A>V���N��qv���������f��	-��Y����c�
<��X`���D�:����*�<���N��y���D��6��"�����D�������AY�5LBB�"��z=���iK�#!%���h�GOE ��j��j�����������h�g�2QU%T��Y^m,��bvfM�N���
J~[��oY4�H?{����t�a�SS�F��|��[5;������'�$h�>�������t�d������������B���K��kn"�%��ju��s=�����]|e/�w�`�ud����#x�S���t;ag1����gk����|+�5WPP�����acTsR��|�����O����33~~~,�r����4.�.I����]��Hz���������cG�O���������������*�����w>n�p_�����C��}���������E����V����	��vk���t��2��]�����P�z�	����H�/���:;��R��,����KCMM�����	9Z?��1f<64�N���)�u�"�h��n���~���RW��y�6�3�`�^aa�������4�A���\��#V�\�;�@��Z��
I�T�����e�aM�`y�9~w�V���p�49e��B�D�M"��CFO������G�M�j�@������UuiYb"�6,L#�:�W�������0�v��x����O�����d[e�@�y��Z�K���\q�t5gN-PTv+���{�����5K��y,�Jg�=!#!qn8��	�j�����V�,[Q������uIs]�GO|�������"�E��&ja{[0X�l�
8�vil����G��oP��&}���J��N�_>PUT�����dMr�]*�Y�B� A������?�I|�F>�[j����`\��>T3���w�v�(�;\����*��`ZYeK}Y���.��U�����.��z���1�i����F��������U.��+?(��q�6�NG;42���u$�r�df U��Z��;wd����,��#=���^��.=�V�����.�W�d3���\PZ�q�s�����T�C���F����wq������Q�G����A�B�����R���LL�,��H��^����i|���+�P/��h���
4[{��������������B1��~��eK�]�(���Qb��V!v�E\���J����<<������@��
��Z��s�����cNNN����]����d��bE��GGF��0��'�����\*~I/��{SSTtt]?��(]9�P;d�^���{Q�mfT2
�������7o���T��V���0 ���������=��1k�����H�������*[���z�T9����	�d7�lq3���M3�1�2vWvO�����hee�dr~^���}�����v<�q�@ZV�����p������)��mK��p�a�����������c�U�/��$��UU��xmmmw!�UU��Um��l�}M������������
3�������]��R����[V��Xbfj����9/�-�1����a:0(����
7�JM�?�2##C�f_���gX�LJ�WUe��k��j��*K�������D���+*)%�!�f�"�d<�e�n����/��ehc�����]�('�t�}o���G"���O{��3���+�~�0��<v����L����a�#}U���\�|0*��X����s�D��&S�|�;G!�vxxT���.!!��7�c6�
��n�D���4z����������?��� ����������^������u{-{-��W�@n���r�����a&�QPPh����q�� :::@����{o�+���G>����-]��UO�q>���h������Um��)����-{k��������;���X��ugee�/�@��t7����d��b��M�c|�������@`���=&i�z���o7q�������R=<����"��\�?�N��#�|�Bv����:����{����TzbL�������V���vB�X�wq;b0G�JUi�v��Q8K����������J����FCm�?����t��:)���>h�|���a�,]}��}DD���������@�����
2��M��9��/�h�Y-�����Y��*T��t�����Tdd����%5###P�H�������w�]t��7"��Y��J���qnyK������$I���b�������'y����a��y�������VVV&'>|8�[�pZ�]��������xy 5�����k[[���k�H02��yZ����O�����lo";,;qi�M�g"D����� �������gp�t�i �XA�����d�q��mLNNNII	'��{YQQq����nR;jH>QQqF�APP+\I��n)]d��P���r��Z�tww�3/%���8��@�[������
�
P]\�hz[f��200T�����o�~�
r�������Ico�����w*���R�"��f_I}���d���(��+�����������w�)ZYYAw�P
m7~�(�ZDi�}b�u��c�(	���z���1
�Q	'?�	��01*�����W?�>�.���|1�1.�2���C�l�{K�x�E��N��z�
j>)����1��!X�'��
����b�xe������c��F�h���b�m�BB������B�-��Y��%��|^��CSS��*%���f�����t65}�����g�����{�W!������> ���'I�����t	��>�;_+��2��"F���O5)�A�@����+��������X�,((����&]�9�����7���dM�v'�s�>�������	Pp��5��mlL���X)Q�uh(>..��}�U�_?FGF����tR(��o1����K�H$����)�S��H:�]���g��x��i��q���

���1D�����v.������a�����e��r���������b��<===���2�p����D�*���~}�n~���
�����J�yY99���B�������#�X7frKV����Pf�d�C�G�Q�����&y��������;\��qic&�3�C�[�?��%�i��dL�^Y���KtlF�<�6���c�"@��khhhd$+�]zz���
2r�����v����|��cd&B)�����I����biD=n������5�������|m��pcN���HJj�7k�l9��`<��b����l��Z�A�	Z�q�H��������v[�b`�A6�����:=)�?����?=�j�1,������B_��~222*�H�R |R1
�����z���Q�D��p_�.6s]a���$��y1e�A���&����fkL�%	�Wt�z4�x k8����:`���-��`&i��1 =.�$�����'g^�t���'G{�.
G[�Nxm`�
V�+HN6���;0����3�
mN���$�s���
��{?������{e�|R��Am'��A�	��6��gO����y%Q<833���wu�j��U�`�������q�e��� b<�P[��],p*3 |:H(
����;Q���<PL���o <I�P'{�.�#���00���7-�@;w$	�[�����@��#��J,����g��.f��D[�����/@���&-��!rss�]V��]�3Z���{�hf�t����JJ�>��?su�iq������?��+��l&
��"�3�D�um-�����L��a��6�t,,d���(�o�jii�wjmn�����ol���ON��7~�w%?�A��DE�����@N�#L�4����6����@��ea!&"�|�
�{ss��n��b<����Wp����[������\�{��KpqY�3�E)(����w��t��M����{��@&�����<���%��&ff---f&&�n~��A�TS�Mx2C)	`)�Li�M6I�(a�W�o�������\��D��FTf���h|l�����~����C�Srj���?�g���E$ym���Z��e���q���ws��8T�YY������K�i��l
j��#u

��wUa�?�C�����Kz��[Y�-�l�7
`���;)**
�������PJ
�# @k�����\6 No��nTx��p��l�������z���3<<�}w	�������\u+[�?�#o��������ha!!cK������=��?���<�23�}Id�������	��C#�d~F��]��x����3\���m��W�����Br2L+/7WANN	�-������t5-#c#���~��O�����|�|�����"���$"�@�9�:��u�jM�{A�!E�H��Z�����4���`@
q8Y���2!�A������=l]v2qwOPX8����EWgg�Fo�t�{�����3���aM?�P�������K�~�	��s�� �a����5���.a�����)g���
i��Y����p7�j����[�L��.�M5�E�$@�������mP��v��9�6	�O�74L>,0���x�z�y�7'NNN����[��S����T�k�cc9X�[��{�������������"���pt`��J�H�caYn
&�a�7_=�o��kwe��,W.�|A� �L��(�����,��s�8;{�*�P�}{��;���vum�w���������/{���lt��{�Qa��cO��nnn6�f'
�����x��-�\p���C���u�����l�u�g��d�y(x�C�I��|��k�[���!������c��uv2�ue���2������hq����:P�{*��Ggq��
�cac�.q�����N�y��-~�����o���	�q��������� �����ak���v����&,LLt�!''�S�u��{����?��t�o�m�4�������p�6���rrr��}���[���:'.�]���~��7�����\S��[�]v��
>�4��Ac�vkQ��@��5��\��o��99��=F���>{u���C�������@�;���J�7��__^	X�$����	7T�y�C�9Q�`�Uy=��nmm���^?
���"b&��
8�������U\����44?�`J@�2Hm��;�~�[�G��:'��2�&��))G��������K��egg�^v��)����gm��x�9ncHH��/�<�+u,��2���i3~�����j]	��,���#�666�������&�WQ�ed��y�z�-bD��>1��.���>�������M�����Gs@�V�����n�4x���X��Q�����D0{�Vo������Y��������R��@��g�3��W�^�)�����9=��������,F3�����w|||1l�������}0jm�rK3P�;"��O��7�.��&'Q��:)ir��3;+����&��
(�9�A�$:��P�@l�F������Sd����+��5l�u��%X+��n��������,��~�wl�

��$��������s�b@��R�������$c�8,�I���50�L��.2�[�;��H��y�g:����������IBxD�
JJaA�/Q������T�8A@H�p�u�f��
���J>GGGr��gFP��DbilD�=!�Z�h��9�a���]��p���E��m�}Z�|�r	�������X��9"��+�#`��-�����P}pI�W���U�:+��2 _�;�>n=����-�"Ds&�{G�	��%�@����H�@���N��������E�$x���4��S6�N�NDf@o��;�3�� ���#�P��N4�5��=�I�%���0z�f�������fb�����t�W�%Z5�l9��RP���gb3�����j��P������th��
�?�e�%�]��6D�l0*��%�.���w"��F�����������N(���u��6$�A��t��d��H�����j������m������^���N�|q�U�l����PT����g6S��/�
ZFK(()m����(\�/�������I� ���������o`���L��cS����N�0�Ib���z���%i�0^(��_\����������]|\�����
D��
:g��u.	5��*�o /���>g�������UY_?��ql,�L����v*ALh�^ 00p�+���h�.�Y�Fe�)
��j6�pA�r���e�����0��^Lv�.�����Q�BN��X�U�O"�����Am����z���'~������a�B�P������g���`/������,�<��o
}�2-�S���:��/ET�}�f��--�����s������`�+,��������^�~�R[[+�������[�E��k�X8���� ����H�����tI(�4C��*�#R" 
�
*"�� J�t�w�����������~���~r����:�3�c��������i���������}�7q���������N�"N�.�k���@Z�nL��ju43����|�v_�z(�Y|�
��y��y����^���Q����i�3g����vfuI�����OKK�E�[?�&99`��)��=#�0O}�nv$<(�����a�������v�W�k�����p�>��Q������l���������;9;����� �{�Jd�N�8�L]cQ�������O������I�m)�������M�I!���t���z0��������N�5�����\��r_���������nw����)����#�9�1�z������r��?�NXz��#U��oL��M�����fgg�
���F����Gq�>�����4�����dZ��L�,�����a����{d�[��\�n(������]��**����������%g5J��}��R�0V��f��]��CwU�Y
ei�-B�%�c�����h�gy��^}�4:�W�|��AiVffx�������L:a��'��A��-���2�t����*
���_ �8�������?'tD���a�E����D��Y���V�=��&J�Q33����vL���e���S6�������A�ct���@��r����%�5p���.���]4�����9�g�����{���mm
m"�H�������R���H/���m��$������ZON@#o����{��Qgh
���W�������-cG[�Xx�������K���lz������Y�>q*@�����\��>l_J��eWv������F�D���X���3_G]w��y;'(kV�MKNN �MY�]8v�7�Q�^�4=������\�x&��#emS�e�a�y~�
����}|J�m�������'K��DL����~��
6�~�,|��[�����98��t�[W���y%%G�_���s]�`x�2�E�������)
�,��t������~A'&�!�RKC�&�n��|���7���]D�m�m�?j�'�g�N�X���u������,������c����t����}*W�<�cQ`J��|b/9���!AA���m��M$����������X�$L��=0��Y�g]5��:��ZAM��6��{Z!h=y�����G�X	ge�P���]��}�E-��{���u���&_::;��i�����UH�D�3,���`������4z��z<�!�rg�
�����=�R�$�`��T`W��G'C�����>	_����8����'��,�\,e|���Q�/�;����*�����@���<~�c`O��{L�G+�/\� &��Fw���@��[*@9��^�������l�u�P���`�RYYE�x#B���8B|�%$��7�U�IS9��b�ghBz1@W���E?���:.n�Pd����H��������e|~:�4�}-`d�������~iL
{}`l��6����8�W�PCS�|g{;:&���.������!S��>����V����|���{c��_�����v���7��Sc|v�%����(hy�|���PC����+�p�z��Nt'ljjJ�j8�}���^\�^�
��+��wss���P�$�%�����^�y@�������{��������{���$�Ta���������������/���E���d!����.N:���+	LAZ
?@������_-Jb���3�i��hh��}�v7r����{���=M��n����	�EY�Mkhh@=-Ae�D9j&r|5RYZ���q��+�N�����/�41��	1;
C�U:/�^�v
"���'�A��|������,��gH�]nPB����E�������}���O��W�����,rY��e���W������������vSSs;��2�P�3`��X�I��>X:d��?�;�-����kt����+7�=�fYE�����R@������PcM
g��_L��x;Daf��U���:��v@QX���wD�cm������2(� a.��-���[�IKK\())Q<�������>�[�0�| ��$��V��s�������'���"���W���V��u��H���j�`T�s�&6��`l��S(�Q;��������������]��-�p��*^����;�s�J	�"EHx
(�S�k�7��%g3�OO���([f3�:p����7���L���iq��������LLM�-��D������)��5��?������:�,?	��>�Ob�;soj��lleQ\jQ"���iZ�]Xp:X����&��X'~��q��i�|}}�������0=M�����`��PEo;@�M�b�S��j�]���j]u@�*�����=?2Z�o����cI����Ml�@�$=��
���{:4�����U=����"�2���m�B�B�7=:$�_S��N�&YM�7�O�������V��`g��T��*�M��:���=fF�ZO���u1\s<7?�-x�����Z��^�H�N�Y���d��EDE�kD,%�z����5},8�05����f�Gi�����w����mmm���o��U��x��a"����n�H�-������e]�[�m�n|"1����S1�����*��������Q���H�p�D������������#��{��h�I�����>�W?�����E<}^I.�|5�-Q��N�YTT�K\\�}��qr�c��a�>e4��b���H �s+�%��i��Y��@P������t!�>=*�����H��~NN��@��SF#�s90+�����K��U���� �n��M��n99�TU��y������-�y��j��=��`Z��1�'XL���e��[�����	�_���w���W?�:X�B�s:0=z�����FTF��c�MY+	��7o+���v<��{�|MoW���H[��?���\���z��������3v���KIw��[���"��D�o�*>>u���v�)��f�B��>#�K�/~�����������@���������2�EGG�����a�t�~�N�;gv�J���
$��]�]���U}uy���T��D�^��4����POl��N���Z����<��_7J1��r�p��VE�%n���Q�;�B���8W��6�wo��(d��(��^�z��,=�l�~���l���Q��g�$�	�����A�^?�� @w;}�$�f/�o�����e���J���E�:�?�-/�*V�p�Q�cC��#?~��&��3���l������Z�����p��3��#�&�����H����l�V��M���'8�TYP\|�0��s����)�I��	�%�)���q������u�v�TV��/�3����N��~O�|l��0���ez�D�I�b<�2��x��=��-=����e����fM��F�jo���
U9�+b�{�P��<�:~��n1�����:1	����#�_B1�g�@ ���O/��sr*�i��a;va��<l��=�c��?�HE�]����t�q�e����6�����VZ�7F��=j�����Isr[����S�^����T�g&_Y���0��W��(�����"����_/������������C_O�<{rH��]}_���L�g7l�r���1�$@Us,�����M�d����Wnk��)���ulWA���5�}y��"-;$u�E>�,�)�HK��ejjj@\p�b��������"�����o��a�xS|��>	��_W�?�L�����E��������"�D	b~i������v��x����(Y����K�3DD���������n���������?-��Cs��jj�����6A������	�@}}}����j��{��k��r���X����?.�\)���~`}z������`�j������B�d��Q�:-.��*��������b0�7o.d��KJ�������H�,�V;88���u�&�P��T���)����������/6P{7SSS>Q�v6t+L�����P�xIY���������O�n����h0���h����D9���YY����v��Y���/����$��T���LQ�g�����y0 �z����>�(�����{�^��I�����[���Bc���RX����U3�R��h����"�kk�z�Z�n�m����i^0�n��"��C�����g��dR����O�_�����3��r�a�Dr!��E���%\�B��0S;ggM�XZ*�����uK1������$�#�D1��+s)��:��ct��=�x��MF�#G�+/����6�AZZ�_��/%9��pE���_����||�w�QP�v;KG�����OKO�M����yBw�	�2�<���MMo�����*�72m���u�����c�=r�Q���A�
��or
���p���������>ll8�a��6��>�����hh8������>{n7o�1��3R�2hQ�h��O��3'�&'''(0Pm�c�Bqrzb�#A�#��������2\��
�,�ak����{u������{�L�1�_�]���tR�g����:�}B��se���D@��1��k��L]�J/���Gd�o�tq�Zn��p��q�+�nht���Z/�Y��Y�Urd3������rs�mp��0����_w���*r|�����5�e����������c:�����z������o��4 ��
�BBC)��k��+I�o������ ];��`�_�x�6��W����{���^������5��{_Y�;�&7;7�w��1��@NB�k~����T�y�NFF�|(s���h��_EI��@ZJ����������---?>�%��Qr����eQQ-��������SKc(�!���F�7<,z�0h�%-�s?;;;9yp9��^0�����=�Id$��-5�RR���������������.%��/��h4��>�
��������Dy99��9�ttt�Q�'���~�e%��E�PPP���'%&��A�	.��������� �����������v����C7{I�&F���:�Uc��,cooo���Bs����@�d������;]n���B���y���i�D��:�+n��s��1��-s1��g���O�v��g��
���D�6t��oF�{?�D���y��T	��M����J�ze�
�����-��:��$�&��_4m���_�]py�������"�/�Q13�7l%���~�����s�����kY2mq�k�q�zt��.�099	���aX4������������`"�Y�R7F��?z�)�\M���{�BBlS�yA
���~��}&h�?�9yyJ�o�cy�����y����y���n��R��^�cR��W�K���0�nB����?��Dq��#;�d<...n��&?FQI���I��}�`��Ah���|�B�9���0]L�l�$�ygbt�9��n������^sg�����-hUUU@V=<$����a����1/ZA�lmUS��8�35���R7��"V���f��C����FFo@"�N�������l3+�/�h��\�Qy'��>����2�^���a�H#-�>?���/rb}�S���	�l�1X�a���_�}�I�r�Q�]v+@6^5�����K�<������M,H�j�Ds
/{��a	��W�g�w�����O�T8����<�Oo�:>�� ���������(+�T�f���$\�r���:X�����-v��k
#n���GH��{r��Y�vs$'�����l�����?���/(Y�n�<ELlfb��������&�f����Cr��	���s�������5�%.^�j��E��6�9�a�#)�%��8��6�m)Y��_�j�;�D���	�Z�
����n#���f'X:���^����$'%���(C�X
[Vi
=?���r�b�y'��:����q����-3��{�^� �e�$J�D�G��������#F�%�����s��8���xG�T\bbt��p������e1A��33�G�q�o��x��j�!�S0�����|��^��H��aZ���&��� )��o���zL���9���Z�]OOO����Ll�v3	���j"�td������Y��7����"m�R��� L��1q����T�G��mm���/�V)'I'$&����\��v�vow������@��#������t/���^��)��I������m�$	��|L]��7^e�7#{8HcK�90��%%���������H�����/A�IWN:����N��5>��>���p	G�@vS������������YXp���|tu�������|z��o �Uzf���B����?�������k��x�i6�ut������N�l�=�P���/T@�=fZZs�X\kww����������?��������4c+**@��c�������666�R�?+�_c���s����'N����������||����Z�����X�
�E
VN�����)�3�����=wN����m��3T9/T�+��j������0T���^�!,�K=�
����_���(@��j<�l����8�f���
X���y�O�Z�����Kt%��[���y�|bKH�~��}a!��\w����]����f��w��%���e\4�(�%."�������z�>4NR�<8~��;�KK�[�`�����


��`�Oe�};KO?48(<#...6��/ 2TTT����URH$����Orp�7E>�-/�H�����Zi����hN�i�z  '2��.t' ���Y������=�G��
��^����QZZ:.�3�liq1$(�����������A�l�{��N��'���=��������\�7643�$�ddd�����=��L����mM;��*���s<fw��g��(�\�����o�B��Cb�����Zr��6n��������`����
�����>��=l�����@ukn���w{�������6^�����z�[���|� �f?��^/��2����>����
���2p���y���qu�t���]Yhq	0@E��" ������w�6c�����(��.Q&�ZH9�T���u/xdccs��\����� ������Z��w��������8��K�N�c?~���(��
V� �ac��n�*���?����IX�=��]����0�����|C�?���s���]<���?�>�666n{@ -�|}a� �3��b �)�������I��zy��"&7� ����R��O9��������om�5�p��d�r�M�T`��P�gh+s��)�Xr���V#��Y1mlPKrh���$7�<`�>��xj7���l,��DWOO/V�S��Yu��i�}�Q'��K���I�==����R��������Y~��Bi+���K�(.F
yYYYw����������y���/M��TW����*%���2���L�y���Aj�q_�.:uW]�4VW�)��][^�nQh������6����!�2�`^�c���Qhd�r���;d���~����d�Q��G0�;�F*0P'Z��#e�I����x�f'���6���������b?��������8*��j��wg
�'����d�p�(����l`5X8��,���OM��d�!
k{��������={�ZK�d��0�r�%�u��.�x�n�)"-}p��%H
&66��r�+?�";E����moo���CV���	�*�vT
M�H6�WH0[G����;{�tf�%z��d�A�M}����7:��������������~�����
#=C�K	�w�������%����k=g�)��@�D1���
F\z+"�*����y&y��{�W:�S���!'��m�����[���g~�Y�������JT�O�
?~���x!+�9D<��*�>�I���))�r=z�%2n����� ���>56��Vr@�	����S��HqZ�����j�ir�����$�R�������h�_�	�`��Rl�-()������k�ws��oEkkk;8%%%���!� ��J�m)�?? 9s����G�DG$Q����������,?���� +�Y�O�Dx���|�������c�qL�����#�8�O899K�tB@�����!�m���@A���m<�����n�����1TS��s87(e�5�{�p��*:��?>�{
6XJ��@8Y����$^��l|�5�a_���7�����_;&����S��s��B{z@\fe��i�c�5�o��1��u��bR�0���`N������6����]a���P�$f�/2�s����k���C�Q���u�eO���.�&S��	�O`O��&Xr��u��5�Z��S8=}���)o.tuLWWg�@Z�9��C''f����K��F\�mG�� ��� _���9M-��9��\+�8g��{�����^��a{fPj�J2���
90�U���5�}f81��3��rgvQ���	�G�8��L�B�TT�z-���9���
���.`.k�@����.�rMM�?C<�����H�o��4,,L#�kp�#�R1���+�s�{s$��[��}��_W���,�.N�3f3��m�B��
z��R��X:�l�~KMK�g!�l���:�JD��i@�R���K�/���B
�����^��>:F���ch,,"�=�����+���
��#���dVL`���D��t��4n���C�r	9@�YA��=g!��w3"�V���Ms3�/�>����;P����b�y5�o@W����YXd<6�\]��C��y��1�~����C������^��w�0����]��Pmw����6oLm�tOi��=��'�s�ie5_b�oB���kMLdNph�����Nw�v���Y����6�C�? �����>.^��s&E�|�K����m>���cz�8�N�����k�m���
�H�����0��3�y��*����9���9G�xk�x����D`�	))�GV�d�����y�������2���02J
���G2�M�������F�<��_3R��g}�����������GDu����Y��r�u�;w^*�[����X��#0�	u]h�{��M�q�G��;�����>�x���!���5��1�0���Y�&��������"oc(��.��� ��(��7Te94������$����I^V�s'b�zC������q���8t��N�.�Z��X�o&�����o�����:���*+�g�q1q�3�x�Q�d����<���B
��53�XU�����!����C	C+c//�s�R�{(D����8��D�����nI�����t��Qw��F�����,]������b���0�t]Dck��N�"tO���y`���uo���,��6
���d���+�{*u����g���� �M���OMM_)����4�kii��#Z�P��B������������d��<�
\��}�X����"':��ah�q���
X�m���<
r��D�P�i����������B�h��C����ES����'��
�������:��T��,~�f����P&	-���'O����� ��d�uqY�%F+��y!:;l=����w�d59�Ae6j�M���6��! ���y���
�</nUMU�#��Lp��,a�l2�x�7�%zb��)�u�9�`�0&���7)1q�_�����|y��mP�����-��6;k�L����.s���S���Kd��#�D�$y<4�Ye+^�rppp���<<��Q���B��E�9�KJJV�q �Y2K��Y~��t)����������������������z����8��\��G�5���~��
c��t���1������������p������*���+=�'�E;��<%
fX��[j��X���G��3=j���v���M>�97#cK�|e�\���b���`��9q�3�����5b-BZ��������{~A'O���T�:�R��������
I������<��1��X��(�<��o ����p����h~�z��f�Ym��-��&��!
����B_g��L�x�tNoB�4�����������'.S��bds?6*F��|=lCvz�e��+����m%-���w���~
&�Q�C�������������/���F(%��;��H]+�Q���eg�|�Xf�^��0N,��B��R���e���W������N�0�r�?n�wZ��_��A9V<~
����Z���:����G��kZ�XX�+��:���=
����������(Em$��$�'�/p���aE���������~���:mx�J��l�{�z��K�y��n.��:�g()�;s��R�x����9� �����g���q0~K������X��z�%��#]���������:|�m�%$����n6]*��K��.
5�NL|�
e�1�q�82��s��%W�7���Ma�l��jv��_%�4��h	�����lP�y�qNF���D��H6@�
���A��D�a������u�����qppt��]^����n�k�����S
h������g�S,.�2STuf�;�._�t�
���"����9�������2m����w�'y�����5����	��
������((�$%�G]�k<6����,����A������P�R�8BOg��7?�O+g���M�5��Asp���������yi*^"k�P4�r���� ���WYUK
�&6-|u�����7K���g�+�%nC���_�:�o���l]L����_���������������EE�������A��E�G����n~�se��S�o�w���x��%���� !]Mj�iE�L�����u�n=��u!��%�IT�����pk��&%���g:]:���x�7��Z
�Xv������t��K`DOB�ie���������222��0�;�H�\���S��=���DmF^�z5PZ���KU��D�CB`K���_�~]]]� ���'��\`l��/�f{�IHH
��.�B`��824��������!�C��$�)���_�|ikS444\������h��}�<��	~��I����>EB�����O�y��T8�y iP��������Q�|��=nj���P@W-���U.K��������-,-,-M�s���N�_�t���|��g�����n�NG����V���\�%j��EP9b������5hic�9�}r~8wH�jA_�%6660�5�{`4������S�N	�
B6���%�C��^����B���W����	��k�~������� Eg�~����f�������������HZ��d����o�2�-�h4�r�lq��W\�����j�X!s����d��������tm3��uu�����<�u>���Vs��\��n-T�JX���S������B��J�U]dioi�����B��"�u��"Fz��fJT��$��8I1�����6]\O��N�����P��u�jt�Scc�i\���f7��$U�3��RO������%�%K
D�I�H�~T�"S����_��s�G�{gRV2W�[&�0���$"����{,��������������QQ-��
-��J�`�o��U�=A��X#��_�d���GMm����)P+lg�G���7���
fS���~���������������w������VI�<�A��Y��$��3A�Uw������H�=��,�����s��S���K������dFr��E�8r$��a�R�G�r��t�3���Ir��;������x�P}��j��3��aY�Y9��4(kA�_�����CAI��)��v1�h�F^+��do���$�M'[����E)?��&]`��zE�g ���������Y����?����n:�mZ�c3��U\����wl������P�����!W�l��.b:�^��!d��-kId����3Q��bydG�����H\����:�<�L87_Iga�����	�'��1_��V����Z�>�fgUi;�d��s�~}����J�����z����0RW���NV��������2%��ei���f��m2��jXTX�xZ���/,��S�c4�m����������Q��������#Q�� e����FvNNOi
���p�N3(X8�l���SeV/d5��<yM�,O����"������b!v^^5��<	��m�����yd�#q������N�������bb:��ZW�l���
9����u�"z	X����-,a�+�Z,/���t46�������9s�G���r��Lu�9:�}&a���]��UUT.X�K�����1��{GGg0��r�A�����2�~�������p+����F���7.�[6�yyJz�E�z����}go��N��!�^�Tl��%���{b�X�|�
m�gK��+�t��0����K��|��qb�����s�����:lhK�NN�_�l�����t�f��������wk���JO��t����e�@��$$�������/�����J?&i�5�R\��]K���E�
�����F���^nY����Ec%co'����<���1�iJ��cJE��#���;�9�5Sz�y����P��-��d*^���tDEXlP�.`�o�L�Tu��WX����!E6����[����s�g��=";��5+a2B�����J���_�pAX���U;7'+K�����SY:"}����J�����T�:4uGs��� (<���$�����qdtTJB"����f��D�����p�A�C���D��P[[�Y�w�^��=M�����*���mll����DVpP%-gLt���[������"#"���`Ae�����x��������A�`�R����kuc!��������Xm4��4��_S�U%J�efp�k���#�RPX�mV4
x!���x~{A����HxQe|[,�����������"]���K��dd���Z����7aD��J�z��

u����MOOg�[Z��}���$�.g����;\�>�*�
ojj*--���~��OAT��T�������&���ullz�%ir|%�	�{{8Ui�GF�_����(���e}}=d��|,�0'�����Bq���z(.�3g�,,
��z��$B�������1@q��X�n�tn��w�&���q~��U�""�`�����KH��[tv4�������?�q���s�[�rvv����2��@�����p��%�D�;R�����S��J%1�2/��5�4`�wba-Ai��=��.���?D�����P�JFy��
��'�79����=����sX������
��������hD��|���L�!��$B�"p�)�YI��?N>F��F��:;��'�8k����c������<��9AT��[���L�K�g�[;�t?( ����Z�3
������i���<i�%Ee��^M����G?���@�������������Z�W�}$��z��P�zO����*r�����n�����Q~K�A�XXX=�w���^d����7��sX(8��W�j<�366��Lpr�t2E>4���.y���%��!W�~��������RV������?����}N��)l�"::Z$naca833S�oP��������KVH$P�Ks$SG�V�r�F���z���`��k


���LQ�,��29��Ded�dRRR���M65=�UTT������!$���[0a�h�c�ffP�]D�j���`l��Ob|��E�����/T�u��{Q\.�������7	rQ5H�����A���u������pS>R����������w����5��s�,JG��R���<���ajjV�T70}��vs
0���>~��~��$�w$�-e'(Z_��zz>}�����)����Ex���YHh(R��B�#��N�[����X�;^^^�U�������T5t�l���X����<�I����T�u��E>u��k-�uH7�����8�1��3��]���]^~�;��kxX���6���+""_�i�s������W���<��E�H�����/l���P�������y+�H^#��sy�����lDf2������}Pp�����S�`�ED��b-�*����\�H&K�D�L8�
��SK������B���������}�P���E���q��hhh�e1�����s��G&<6.�������I�yh�b��i���'w�Gr�d_m�i�i����l�7������L7799YHH�|?8(���\iIIf �9fv���P=	T�v�V�������C��i�[*�����:��?��u^������5S�'pc���J����*R�+^&%A��p�L44}�`����||�V"H��}����@h��m�U)�	e���~ (��C%��j�������b���F�n����O���O��1&U@�#������!R���}H�c��U�`��?%�8I�����,���K/�UT�����y�4�Uj&�U�l�'(�g0vFF�.�j�12�����p$Tq�d^N�'O::;����z�,Cv8��V�/�|���*:+Ak���Q�`�.�����6R� ]�?����X����
G����Jp����g
����,���v�x���Z�b��989Y$�i:�v{����
���p	N��3���9�N�X��O�aT���++t��;���u;�\�I�;��������.�i;������������o�������41P��|+������� 
d'����ChWwogL���4��w~������-HK����Y���UJytII��8�Em���t������H���3�O�|lmmz4�@�:�������;�M
r������qe���<�-�V�l-��%�kUB!	�8�w�,�>9���k�E� $�r�����L��h[�$3@�?&���p3��o���-���eV$P����M�`���`�#��3	�2	
k�:;��i��ko������������e�3��2K�L9���������l-��f�m��,6	����%+++����fk�3;C���0%�d������_e����f�[YY155�w��C-����)�+q`������g�|�l1[�������!�L��N�l}ll,�SuP����_W������[ZB�}�"
W��6�?�S��7����|4
S���W�B�����@����u��O�G�����n�6Uc�\���V0��p��1�lnl���_$���{ZOW�8�c�q��Q���/���V��������A,���R���blA���Z�@���<���z��/��@)�A���l���$3644TU�����Y����F�^��(^�����pt���\]_9�k�wS���44������������m��UZ5.(?H?9��p�7�jvv�77���|(��=w���F
5t������#�)//off�������D\()�����w7{� ���h{��Q���)/��zo�� ��aq��M>���T�����5Md$�������/x�����C�������
5777�n.�������Ac���^�j�j<B@@@Z� �sokk��|������-O6��`TY����/��Q&���
�\�4�
������[ZZ��t�������b�*��cG���eil�!���Z���8��.� =�@2��p��&�_��tE��oK��'��$l�8��W���O��/��]\���i�u�v���{��j�����8��� B�
�n����|�����T���UG���������s��E&zz]}w�=i�Z�������+��O��_=$G�y��ok�U���\�|

	=|�@PP�����d-h�*uO�/�rV��l^HM`fd�^���j�)�n�w��*��{Z����!�$zD����_[[[�����"%f���.Kz�
X�����v������IG�@��{���)(������������_�n��f����� R�]���X<h����1+���%��L�R�����Bo�?����DEdE9��<��Rr�����r��w���O����5�d\X'�]dB�?e��~u�w939��A���	#;��I� ���Q�.-U�a����trr�������Jn��rdo9�mv�B_�&�������!P��*,\R��::�<T���������v��{,�R��QhI�MM����{sXA��	��DU�dy������Iu+�$�yt��?����������'��?�ZA1?P����*F/���Oe`ac�3)==�E�����d������%����-.>��8���3��J�
 %%=o
���Rx(��7.���<zt+�����K��3 ��d_����-A�����s�v�����AA�
�YLL���
"���m����uH��,��/y�z��^����~���Q~�����4�����c����6����	�p9A����mm���;���uw���f&t��kb@�	���iPmdwlB��KP������=-�e��{�;`�E����t��7}(9�@��F(E,��~�>�eI���(�s�����I�X:�]��T]
{Hx���bs"s*
����zbz�	�>;kq4��Kl��
�Y�C~^���m�Vjajk�?�|���������Y�C]:KO_��1�S�g[�7�x1�����Q���DyUU�zCb�k��S�+����ccc���600�q����=77wZ�T�Q���
�B�KU���j�B?�~��egw�����s;�0���x��K]]a9.3l�A����
Z�����s�^$O�^*��>:�2��F2�;���R��aY,5��bb�����I��vDu%$,�0���_I{K|�&5��Po�HNe��P��`���kkCw�@���������{7��,;7W��@2IA�;_��������--�^������H�K�?���~���U���r�8
^+����B��i�H|l\[$��C������4��������Y�a�2�A4
����C
]
�����q�c>9ml������2�'����,�0l���P��P��J��=��#�wb��>8�)`��?} c�/yy:�����
���`Fm��O]�8�Z��f�����A�O\=A�B�\e[���v(X=T��aOnL�&�������u�8���=26��3��J����v�Z�t�������GBS"��0�����x�a��c�����~��u��U�A�&�l����
�������(8J����3�,2���������q;�*B�P?�v�|����o���lb��[��{����V��ype�%	p�a(B�&&$H������vu��I��N���T�w��E�K�����x3���E��o���_��l�]��������%���3���R���������"9Y_�211�?�������I52��{��ZZ�������_���>�"������p+Z��~� ��������w#�F���+stm4��C(�}��n����Dg<O�����	���K��+��&��kik
	����F����#�J#^��������A�������G�W���j�U��YZ�]hh�"��YYY���e�����_��|��}/�|����s�\dS���XM���}��Y@k�����WPU����doo�
z�:���`HnJ" m�vtP���o�+)ux�+����<�
��3�,JR�_=s
&����R���ve4�����%N����[����K���.]�5�)G6���F�[Z�����{�4�]w�[R|�ob,-,}�3?~����w��������777+-����7 '!	�����3����HwL���9���ZQ]��������+55��Bk���$����W�P����OP�^�
���M��lt?(<z�����/�����\������54W�B�Mg�22��,��>��& {f���vvv�d���oJ#�}�w�{+����,�^�w�l�����������s$o�MN�f�n@�M���pxp�����]EKG ��h����{����;�-��-�I&�'��z5j�XXZ&T�h������P6��dZ���
�8C����Q6t#���}�N��������j��EQ@I%�D	J��dP���s�9��P���$�E$I��� ��*9��� 9s�.����v���G����U���s�5����{Nn���H�����~��������[[�����:�<��G�v8�
�����u������=g[A�v�6g�3���:;���tj�����f����!N��P��P)��S�l��q��>{�Xj�>d��WjC����Q�&Y�Wr���j��t��
�y���	x��-a �i�M���i$�xm2	h���
gV�S�bm�/��4k�1��R4��-�"��_�
U��w��� �$o���jJ����#��_zz8��o�.��V���HX���0�7��kz�����(�����[�������\������.&�Mo�]��I���k\�Yu"��z�1��q=�j
Q������m������������yn������^@B��}m�B�xE�HC�y.T?���+			�I"iqey^Qc�@QB���e��b�;���m�PW�<����#�'����;����N��O	]Y��r����-Z��vg#�p����Er$|���K�s����GT�����]G�M���f�&��D�{�0���tZ���a-OQD������s�$��G�2�k.\��M�|��j�n�?�^�N�uA
��������/y�T:�0,%u���^���Iv����Zx���p��U~�Mji�u~�cw#������8�U+��.�������H_�����
+�ll:�=���S4���������������-��2U�O��?��{y	�����A�a^�8A\~@`�����J�G�R��&U3�7�o��_Y������Yo������`W{s�6j'��G=,�.�)�<�'�"���H�	���'a?���V��t\@������j��N����_��W��>�lmn�����"&&��{{	�i9���D�e��o�n/�2��~C��������}�����_K���<q��UR������R��nKCF%Z������LdC�����+�C}����u/j7)]`����g3���ejj��������TO�����g��
�o��i?[y��B���[A�O�L�vgddp6������QWQ�1���^�*�Y�.�
����:�:�;1r��{���4!>���E�%dXi�������� �4x@���o�GaLG<���;�o1G�B-���?��o���3*,�T�rq9���G]������w����.^���V��084��v����������H�"����S
�5���l�D����yL�l������	 ���DD���R"�����`E�jcee%�8.J�KT�0�~xa�����;85eT�����j<
��
	`�D�w�fN|�5{���������O0�|����J���@w����n�N����Xi�4������A����g�,��|���o��J@ez�C�i���N?.n?}��<O,��#��z������?���������'�������_�����������^�[����������Q��<������-*��2M������E�=\:FS~#�����1|,�}��q������b���������!����������l���;]�O*��	��������[r��J�Jz�*������|�$����CCC��E�

�U={ �+H�����������R#�����R��Mua>�9L�����*  ��b~��%��h���l��e,,�d�����������ag�\�����0��T�����l���,�S�zMLMLII�����Ze�XP���o���.����4X��B�����+DD7n�'*���}pP��<y�P��386'de�Cm^DaAA��OE4�"����&ii�h���'�?#�_�QLC���o����A0Rq��)*�@h����Ug@���r�������*[%**��"��[ei�W��u#�����5�C�?���r1c#����J3>�1C�d���#�!������z���;�OP�b�g���cPxy�+��	��}����7�)�H����������>� �f��AjJJj$��fY)N�E'�����l�3bF������#���x�BLu�K�T�u����N��b�"�.�]*�n����#b(�+�Z__�M�h��N-���q9�K*GR\����~���Z�=�=~��.%}}vz"�;��W�ii�5I	Q;rz��/��?Sa�$v�@�%�&�w!�{*�"Z�m����U��D�tt��~�K�FEb�����w���%S<F��j��w��������Y"������v� ��c�7���13P�������{����D0U��B�Q�*���F�^����q�7�����>����o
%��k�<����� �&v�f]�	�����[:YN^�2���M���g3,��}����.KEUuyv����M��P>��u���X���Mh���Hk�"(x������4����-���������LLO��5~"�V?��H|������#�fh�����~�*}gb�q��*��&C��WIH���H���^+��W�9�L����XA*���k�8
��,�$�nEK��<SS3f��_����n��]C9@�XZXU<�����X@�j�2r(�����=���N����fPI
lL;���+#�"wJ��t�w{g��`Y�9�V��1uz�rI�hQ����L����B:c����RN���g�@%�egKuC��wJlvv�5n�����3=���&|K333:����K�������r��G�����A�>I���s/#1`�U���~�[�of+Nfc� L��9r��;6V����#i����7/������0x�"-c2�Q������:���`�aT�y+*Sd������#�;�W�stR��IHo�����e����@�*;��g��J`��a�^>qd�$c�j��5^&'�;�i��Lhp����-0�q���_����x��U�R�TE"!>^�!Y��/)�F���gvRSb�T!M����]EK���E�M�[��5�!3+�3)�b�V��fEkp��O#%�S�lU���rOd��(���@oo�U

��"�
��Wc���$~��>�C�{HY��kr�#����C>Z)�u����L��~�j��� �v��(1&�f�����z�\�=A��)2�B����X�c�_�8�qaj5k��Ps�"��oY���f�U�Zh��	I���=�B�%%�����:�J:X������&3����L����Y&L=�G�>Y�h�U�&����b���<,���4�m`���y4�&�P�q�JSO���lV1
���������Qx5&l���� ���H������YA��������y�i{�V'�k����6
:�WyV�m+v���2�^���;0�z���H��+&|I�--jF�&�\�xQr�1���V~i��`�TW8���f�!�%k��)(����Y������?����X5qT�
�LF�UWOOO{�v�X_�R�*
-==�[��F~����1l-HW$�s��d��U������K�0F�B��[[[:�^Xu��]j<:66 f��e�u�i�������~�2X� #!��Dbcc�wUN;��1�t�]���	�1�*��r����XZZFq�/�����T���6���������v����w�b�L��^���:�@����f����&7W:_O�-2��k�:����������\���V�+%Zo�4�r����Pc



]h�kk{P���Yd�����G��2�f���FS���SO��{�h[]��*f�����	
���	{���^Er7J��A>q�n�;&&�����
u#�"�
~�Ag]�x�bSc�I�-�C�@���]�="���L������{��%��Lv�J��8��K����T�Hqqq�����������_�z{V���t��j�0�Zhb-�:������36g��;G�DUU��nFH�r�"�P[4]]����.I��P�?@`bV�������(������>��y���?~�P���Pg�#;�qn}xJ~�Q�=f�����	�����H��e��V��X�}�{M���^�r��o#�������(n��Y��F���s�-��`��v>U�$��w!e%��-����B
�M4l������jjj�����Tr�^>KL$K�Ku��
3ss��?^$C���:;;�9:�|z8���N���qss`�����fe��S1��/$�=<<,-s��b�5��[[C���k���3�9rlllm�7o�dffx���g��j���`�����R���������k������������Rz@��S^[���3f�
�y���/_����jk���������.��
6&������G�_���*2����c�^���d���'��~���M�y��O��I"8��w����.��(?��o��j��Sss9rr

���06he� �`wVV�7VrJ���2����
�������aiw��_�djg�'*���LYY-��@�t'��xxm���*��(g'':=9�i6�r�=N��Z�M��$�@1�� �4�K�����O����m��S������
l������1mx�����Xa�&�@��>a�����%�h�1Ay�M���;���P��2K��`��*��>����
��p�������������/e���Ly�������0�~���;����d�o�����i����3�����1�>?oaW��y��6QPQ:R����~��(n�[��v�����<g���U~��:P�x4^=*	u��^����d�$���}�/���J�
j
z~���{l�l������������A����i���������h��)�����~��b����Z���N�i�N�C�4��
��\��K���_����L�����0��s!�5g��P����XS�	�S����UQ����9x�HL�Il_�g���D,1���@=�'u/CI8Qs�89�m��������Pqj>
��!XSCCZ���B�YX�---�?x!�����g�����< u�/)	@��fH�`�
`D�H�����qpp>}�VrR�4�B���7o����ww+���z_Q�.��i%C�ENAQ�a��W���m�=|L��9�)�W�!f~ocv��4���EB0����]�F���1SeFM���%��H�p8<2%�:����FGY�lmm�4n��=v���>��Pj.p�k]������+��~��3�6_Re�|��:;_45�[/..z?&�
{p��b�B����@(!JHHG���=W1f���n�����
������8�W-Y`���gq����7'���&DT|���/��~ee#�D��a-a3w��h��R)����SS��\�MNf��>��O���A��:��5��P�W8��6�km�bF�Y~n��A�WX�w�lj�ob��l��9�w�����C*Q?.��}l1�=N�����"�#k������623UG4X�	4g9��U����5���|�q�p�z�0pP�ZGs�h!�&��������&��?��Q	�W�^�Mo�>wF2(
�}O������J��a�5-.�w���C���b����C�����=j�LX(pBkw������i�
���0]]]_M�P�k�]_�3�����loo�zE���4��Xu�z����s`T�����d��������2����
 �&�g���P��.�����g����1��I���I�+9���Ft�P`(QQQ�H�L�����uP_����c{���Q���������5������i8I9�l��`����r�����V��y�@cC-�ku���"������
*�xS*�p��ye���`����vVO7z�Fhi�;A(����
���.@����E&p��t&�BE�n��g >[(!����_����\A���C�����B�z�#�M}���=�5*�0�� J"��K!�~J�C���g�S>l�pH�V4������l�������nKC�����11s���`{YuP!�&��K(���
��\+��H����ago�~�H�e{���OUmm�3M9�;����&��t�r`NPc�������v�����������x����h�>#�����a�]
�k��^�HKK���4j�����P<���~p(�� �/��
�=����3q��C5��g�O�iC��8�������������������nol{���[_Ww�MQT��|NE�l|T2���oe%3^"yQ��i�[���,L���y�1�!���a�S<�|����:i��H���o6�������������iij����30��,��s��-��6���7�	�������=Z����Cd�nce%M�P���������y�8���=0��#F���Iii���uN�����}{ek��1�d��9�/�+��l�w���-,����5��l�����������j|$���{�$h� ^B"���_<��!@�$9�j��p��2�So�>^WWH:(=>p���z��Y�E���- (8�r���0��(�����W��""���ZBJba�S�������	8������7����������������Z�R������l�gi�����������z_@\:X���?�\�_��e�\W1|�6`�=�Q*����F���
���&okkvii��{���ll|��;U�_m�EP;u�}���j@m�FG����g���ngue�ZV:3d�������,W�)�v&��RR@��Z�
�J�������;D�����VUW{89EO��y/B����'�*���'���OuC�	��9j�"<{j�!���w�`^�����d?����t@��X���\�_`d�=Ho�v���%�v�-�����������i��9L\��e,�5��7����a`����Ug����\A^|��	��m+�p����W��v�B��
���W/	�>�,�F�""b����K����Q.K/���������3"��{5`��~T����D��_�z�������r��`�Y����������#
�Z�Y����U�?�P&�T;�c��@�����R�����m���:��N_�i�
c�R!���?����u/:�.r��?
�NY���	!���/�����|j�h�����.�@�������KP�����}��{����+���	p\k�P��a$��*2������N���������E��p���NN:u�i,P9����*=s�������%�q��};����������:~vz��&��l#	������2s���bV�I���oa���BtF� ��a��%Zq�A���W�9�$MV���bfb"��c���H=�����=�K�o�VW�#]-��L�V��@lwes{[RBx�v�ed���#;�����}}}	�2������5�p�B&����������
��Yf��7^(*}7$��=%/f�m5'��s�}������
nJ�������S�'$ 
*8�v�����X���oG�����y3KK�O�������~�*z[�|�,�T�
t/R�t�VVU�(�������0�H�s�M5!�j������u!mm��p��Z�q�p�6�_���GP+p���*LL�<@CX.@���F�p��� 6����no
v��������A9��:�N��$����E�y&'�,!a���G�K"��WSQR)5��:U���@�����xT�!�X��3�����ksqq)�/�=F�\AA�o��`���x��N�����z��i������E/-+��X��`��K�}>1�v:\~"�2�?~!�>pN�pq�v������I����~;=����t0�������#����?=4��)4
L�\�v:?�r��8���-*��G�Yi~��A��%c�-�^�f��c�0�
1zA����w���'\����Z+
���a���N���4��ym1x@w�'+4��wS�9��@��p���OO���x�t��F���Oh\��7�^�}��?�������o�<�����p����`����8WVT@�>�kj��O{�hY� �Sx
�������Ui��+��#i��������B7=a��0zO�sT?�@���[:[��n�����U0�z��+����>5=M���q�1��.�K[,,,�=���o�9��6=2UCl�F����3��:��gSf^������v�H:�M)Y/Qe���=x=+��f+�A9��������Mfn����d=���S�AO���#��5<�� ��U=�$^O�uA
�����V���f��\����q���)���Uz^��r�b�9�LA������k?[^\t.���������F������xx�&>>"�/����L�|����s,(�;��������t��2��^0;��lOZ���i_Vv-�+��2����E`��S!`-�����O�;��zN�y��u??��H�|�3�Z��6������	����_&�;�*��~{3	��&���%��4""zv���|��s�	Y�o�989�d..���{JO/����
*�^a�k������J�n�;?�����U���)��{%4��:��te2���]����3.������jGABn|�a ������8m2:����Se��?����f+�.�����0VD$� V��e&@�����^�m�B����)J�����5��9y6��ev�,J��D�m��Z�>ok���~���Q%+�kY�_����������Y��Q��.����E��k����#�_�|A"��\&�f��������A�o+���8]�\e4�O5�=>�"���x�i�#p�{�>�L�knn>���Vt�)M�Pq�����Z���OFqazhH�7���g��}+�..�[Z���=WJ��t�={�4����GAu�V������;�+�8���;��^X�1��L�`������x���h�U~�T�@6�_�"��"�B�MC�V���_}�7���W��z�T�����eT�TU��s���!��m���o���u��L�SP��������>#�Y_�3L�����>e��A�H!j"�Q�O��T�i���K��'/m���U�p���/C��XqW�'�xKV�A_g�GI�F�!�K mc�D�b#���	����dd%���l��u�y
��B{H+v������T�D�����%�[�e_��b�L��r/@�A���#��`��aY�9�%}}tUP�o��G6�{�
��_I���(t���?�K������J���q����*�����Sy9�=�c5bsS��r�������Z��(mnj��l����	S����h����������3��5��40~
%%��F����&:2
�H���V��/�����-���f4�g�
�'��@���R�v�S9`��h�t���<H��zn,%u��8�Z�H���C	��a��B\\%y>>c����7�F��Kdkl3�C�		N���;�CI�H�����|S!F��v{�����|���k�.�����_��������T�9�uv
@l�������I]_?�
xx����q����/�|����a���M[�/�X5����S���G�Ba�o��X%��m�����)�
�����D=��,F���`�����7��d<�
 "����~���{�����<���������?�/�>�Z��� v����`��w�ww�����W�����t��_�#?�L'��?{��������w����C������#L ��v�\5uttl
�����9�=��r�Y�m�=�N�2K����W���|���lZOgg�
gggk����%���� �%"��w!�����
�x���
���������yy2���l/�rg_C�A,��x�),FiCVGb�~��u[�u�H��Fp@I_�1������v����C���>�s~��9�4�';m��|�7^���I]s!�.�N�����0�����Wa��z�+�OnR��������m��QQQ��f
�M4z��c�I��������%�-v~u��!<<<�i�G�K�.j^�������I��h���xvM��$t�������6���P[k[ne

�.L�s����4c���w�M�0
�EY[[C�c��������;8�"�}7���{�����1��$Z�g�������~4���~G�#"��9�����@�d9���A��z,Q����^AJ"�����������b�d�jn������������+���������?���7~�����?����Wv&�~/��#\IpPW\u��F���ckn�
���*�\���H�J�����t>�9�}dzTsP��{X�X�%+�[4]����/��=�?`��z5��z�����2�������g��>�,�2)iqvv�.����5U��Nd�$���`T��dedy�9"�o��i+�����$-���i�CeI���?�# X��P�i�p������8+� R���5�8"�URR~Q��<�r(kl�a��������$��B��������Q9�l	�m�
��e@^e7�JBh����,������:Of�=����C�_�~=77�������z�rd����((E38����bbb:����9e^����P�.n�*++cz���w�g�%�����������/3}=�}���������ozyyYXX88�I��/�ES����A��(�8 �C��_�]�]5�u��������#[Z[+�s|�]��;J�����W��PYY��5V�w�>w�\)�����4�u���i��@>�;+&�K�3�Q�
�Y����q�AN�q�w�����"rJ5B��q��0���5uz���':SP�mE���DU}}�o!���8?��P��P�1@��Q����4�,�,ll��q�Q��k�&�,��"k9�M��������M��Pk`%��K�R���k�A����U��B3�����LLLD�{(kk3;�L|X�DH�o�K[�4��B�/��:� �dk�7����0I>I�eb���m�"N���r�_z�>��RYi���x�k���9�17�p^��&���������0�G7�0#j�[^$o�������<�������z=�Ej]�����X�{RRR�T�{��Cul��>]���������
�J���oP�q���:}f���E�o2y���L�{�ZH:d�`���SE��y
r��0�����!J��R��4+�6���;�����{p�#�~�5Y�L;m�����0W)����5��$h��
/#0������{]<��������wwU�_������K�w�������j�X��Q��������W����j���.�����=����_��!�w����<[���-Y#����+Q�����!�)���Z������}��TNC��[@�6��=��M]��nBI��I�>08�a�>�j=P�2�i��1/O��`]~N������:���z���B*�����H�k$��bMg���<��o���1|S)U�j��S�*IR���0���a����"!5�P���$"�]�W��Lvg�K�������-%(B�!f�F���7..�s�����d����&�`�����5|5
5���	
����r����LJG����i���"(���B}�ek�	g��K�-�q�����/�n`�����E�>��^�.{�V2���a`�����?��v��~?���+l^�������GFt���ZeL��	n��)��Ex8p�|���q������t����O�K�������%u.�����pr����/[T�@����obf�������.�l+�����w��y���5�:�fo}� E6�;�����w�����K�3���v��7n���n�f,T�`����cU�G�'X� ����n_�3l��	���[�����`#�_������pY����{r��R�AkC]C�4�k�l3v(w��)���J�����8M���
0?U=<rm��~B��\�R,�QuaY���/F��H�e���B���h�h�(\��+�5�����WT����D�n&���v�p�E6���{���3Q����G�G&�P�������^�J$��-|�k��hd������v�s5�y�j�!��B��Vq[�����'�=��zlI�����X����=f��1M��~���a��4��������Y��.9���<��t�6V���8�Y;������+�/;.�������/�(�*B(�MR����
T�0neeE6�#(8����sll�O�3���&��G��a`��iD������`��*oF�Vu������z���-D�a}��P�+W�t[���B����.��b��n������_*�����������
��v
�b�hbbbl�ss�\hejj���5&&�����	m,�UW�K������&�f��������99L�#fe�/�]��x!���;*+5R��]3+�������������Q�tt��������EE�H������#�a���+%���f:�:�_'�O��H�g�1����~�*����"Rskq0�*�--U��
Hinn�*��$��%m�7�_#��/O�����LH}7�{\BB�����Zs(����K5�u��^^�����1�J_3L�fe���$m\��D�DA~���D*�B���-k�kj$�H��&�}S���`��������}�R��

� 33���O�������s�_�d���y��KP��T�P��^�Lv7����:O���3����8�����||H�`�>j*+;���&�������^��������l{F�80007gQs�J�FN�������733cV�M����#A�.'�o�Y���xQ�������'O����`Y�������j\5,,r|��{�x������Io�A6�b��xs0���UL���VY��f���Jc��5t���f��88���{J�2�����2Q���ND�f=uR
N1���^���O��9BX���q�/�w���Y%�/��S����Lpi�)�E�}m`L!�Ug|z���2�q<����#�6�����/a1��g��N��Y��C�E[w�cXZy/�O�>�~�F���������_��z|�:��o?{�S�R�iz�����+@����R�F��[LL^^:)�ZZ��T�	m�v����?�p3��Z���e&�Mq�����!��G��>>>��Q���e��>rs��xM��f��G���?HU�s�����&n
bw��87�;;#���y~k��qbb������
��v|I�u�	/^���ePL|�v���<����}����X�K�@%�����F�P	c�+9>�g�p�\^&$0��13��-�rj�����m*���Y��@�����L�[X��!�������_�i�.�F`$����"�����g����|����;9�J����A"M^�17�v��$� =�0�)����;-bl���13���U����$.%iR^F� �;%6�
)���Dapqm�0���,��'�?	k���������������39iH����u1�M��d��7�F!?s��i��'&
rs�O��f:Wo(�	�ce�����t�����s�^�aG��n���YaC������
�.�k5������P�g)��KX�X�R��]tq��8�u&�{���������uq����,�/��w8����u�54���]�F��{g���XQ�"&66���L��k3+RCY���SB��q2���*f��<~e��������}/H�^_H?c���*@J�|;!x
 ���o��^�EN��,�	^R�K�<��-(����K�T:����;nK/����2N�ljj:��D5@���&''g�����Z��bC����1B��:l��j�����A�h�TDEE�Wooa�;���_����<�z��}R�s��Uk��=PO��;l�7�����[Ti�N ��'x���W���e�IA^��xf��Bt���i�������/_��������;n�_n����U��cgee5��V�;�7�=�Q,���skg���`x�8N��P���>��Rz$�w��Ge��I�t�l�w���a���
0���R�a>������4��1�X%w	-� ���p��u�
Pnjj}a����-w����8�����w�h-�	B��H�������T(��~���4l�	PB����G����H9J��Q<�*~�>P�uY�/p�������8���
<�d��
���]�^S���i�

��}���Y����.Z���c{���&��|Cyo������N� �`��Q�=���p_��R�@,VVVS�T(	'����H����N^v6�}:�H���6���!�*��K�D�����.L
_t>�,#=�p���������j�w���F��!
�7n j�>xC�SB0�%f��Ia�{��&B������6����b```����
6�Z|������~�����P.��w��9	(�8K�������&4���A�����q�69%��;^�N-����)�����nL\������iw��EM2@�+�\=�S���TXo�s��
���EDD�!js6����@e�@�����,:>�<1B4��+����q����W���r��BH:�����1�
���n�����u3���>]2�E{��j���A��;�-�u��x-���Z�K�������3$�����s|E@�����_�|;::Z%9��xxT�@� ������=M��4r��8O`A�����������tHI�Q#�����iv��I����j�g��	����=2����dzg&�,-����6�nO@�'T0�����6`;Z�����	X�_�43�C��y���������l0L28�yM%��������S�./������:������� ����/^��_��g6��[�	c��a`d$!!�$8��
uc���x���G�x�~�	:���x��s���[[�wn�S��QQ�A��`g�����Ke=o��M,�V�D���W��E�J���S���=�������4����������������Z6Y��c5��A	n�����e���C�O{���l�F�F����r[���>�t���7�� Tq�b.D.��g��� yx�87�Y��2sEe�/ t��������i:�KP�@t)ap�����Z�����k��r��RRS�7��2�?:]�_�KKKc��Er6�?q��2)/+����qEZ���O�iR��&#!1�N��X__���cC��-��z�Y�h�prr��<�#������������=��N6�N��`���?����p�S�E2���*���
U0���TWS��8-�LD333IIIYY�@����R��A���M�]VT�W �3"px�C6�����P�v`��J�cx� xvINM=�z}�S�w99kkk��^�C�F9��(�_����+��!��6�^�����.���g-`gW���������!�=�2M;+�~SE���g�I!�;��h�X�=�Ot����U��F�	w+:[eZ�����_������''/��T�J��Kpl|T2��F�.��`���f��{SV���%�n�j���2��/��pVK�*�����Z�j
A�HI�P���kjj���rC�����a�AI�JHQ��#���edT��[X�D:d���K���GH��y�b���������77E�a��o[��G.m>Mq��`baY1� �y�!p�����MK;��B2��D�����A"�Fq�:zm�[AA��O��������D����}�i�]vgQ����~ ��\�����>�Y���M�hj�o52>�2+s����c:;AI�2����K��P�2 �|Ia�N�Y����Ub��33[?#D�C�XG*�_pC�?.�����(������z����}e��	��'A���|)q���}���;�������{���������-\����U3��7��G2�6��;�>��U��N_�.�����6�bO�E�r��#h�[o���4������P�cR	O��}F����@�k�3kSM����4Fwqa��>8��wA�������:�S@����$�hW�,����mcs���<'g��?�y$�y�8����_	�:::�z��|����?���ul����7o����D�����o&�I�<�^�)�����?j���uS
8����7p�h;�L�������-9+�/iyg����=�lR\���8z\�
8����N�����;��H��o���z�=n��iY�%���'���,""b7��N�x(��F"���Rr���R�������+fg���f�n3E,j3K|epqh�j��W'>l��A�0|���Z�#�J999sss�����rR��-�?��_2=�8�qE�����M���O�����u�\���QR���s����hf2�q����E��M��: 6��d��l���R�CN���.� �~o}��.~�^����_/�������	����vp[L8DQ����K����!<Z=���0���[!iD������m�[0��,�#;w���x[#�����&C���!�i\���qp���������nm����u]��S�d��Y9a8uK��(�����^�`�x�sh�*6<uM���h���������=�?'U������}|`:'x��)m�N�p�5��!S������k���b�i����F����i'��"$�����"��E�����M�<�!�u	��7|3ncX��� ��_�$�1$�
�a��L�)�OQVz��^n�^����+Ws�������l��>����m��M.*y��T( ���)-����. 0TL���W������bx�����us0�Q��8j��|��g��<��mN����02�����S��u�o��K$�������{9��L��������3�#�H0���O�M���".�T6)�����#���JN���k������M6|�������{�c��
����P�S��K�����4�lkI|zA<$h!O�����=�N�Z�c���������d�@v���s��-v8�v�.�DD```5t���#**�e�v#A�{�FZ�}wwwoc���q�+��(��4C���k�������?�x�FH�:7����2���}����_��w�(�//���%3�jP�����Z�>����D�n�������|��U(�YNM��44:�_�t�������`�������
��^k|��m����������t��0���->����d�-^R��d&���=��M��l��PS\�](��?KB���3bF��6y&���c���)q��o����|���+<w�����6|�������������`���d��kt_���-����45��O���W�-���<�+]��0�g�U��Nqus+,��(JJ�=r���t����MLlv��F�m`k5�^
���a��m!�����k�~���
J�Rw|�]��.\`�m���?AeT��-�o��yv�7H�0$s�������g����������|��.?�T��<��v� ���3����O�.���xY4�0�.Y�����}B�����W�Z<�z�`������/�)�]���{`��:222�H,O����2�J����?�Jb��"5��Ev��kw�@��	BZp|�mm;l�n�f=�������\Q�h��/���������<Gw��177��S��p�����k�%vN������tllr�l��;0�O/}��9��������0wF�\l.��u�
������{C1UGy�N�����������6���s�5j��������km��&��8V�����^������
�'�mA3��W�0L���������/h��loo�]��Z�3����&�aTDu����g������jv?���/%Q�e]G�����C��86���!�����~��-uY�C�����G�X���,V!+�!{~���N��������������Il6;����gc����z�{N|���:KE~�,C9���V������%�8B2�<�������l7��"IF��8�i,�<}��O��������'���ump(���F���4�zX�{oU������.Ab�t*
H���F��[�A@�Q�������"%��iP���|s����q�����=w�qc �������}��������������s������g��u�(����%`Cc��Ct�����s}�r@�onn�unr��5d�����x�<Y���0~���l�6���k�n�E]~��~������7�K_\V��`��A��q���Z���xPa_E��e��C�������DE���8�H�^��&G]x}���A�J�v�sc��r��$���}������������w.��O�����-��3O9s��Kj���-��GLv<*���^���K�������Yl�kI=����)~C��VdF�N����������;� /����D��l����,��*�01��n����i4E��9>8�'($4�����������4���O�'	���nl$���J���Y�o�W��P�G�L����
�I}`X����_s�BH�� ���F�{���0#���[0�'�N��k�Pmd�`��cv����������h|�b\��@��]=�����]�O�!���~��z��p���_�_�\�8����AG3)���Q���}e*J��&��n�����*�*�N��-;�����>��CCC���OJ�\��3=���XY.nN~�6B2+;;�@~����#PQ���(,�2>�y��C�6R�����%����L�+���s	vl����444���2��r�L����U@��z�*�������bQx�x�w}>(K��\��\O/1pc����G�/���U������#�Y�Zk�G�����A5�0���c�hc�6AAA��B���(|���,IK!(#HE@HHX?�F��3P5��q�
�#����>�"$''�UX����z����5(r
�$�����hy�h HZ��
=<=��u�y�����~XF��Q�����tl����"����.TUd)))h���J�8*���C$y��z����T
��wU�|8l�rx|,�#jY��o����A�M�L��Q�@��������PW_O���iR>|��^��?>�2��+���%%C��0��O�/�������Ss�������_^���R��./��������w������R������3�����b�&���b�v}*
�D'��s�����o�]�L{����D�}xMI-{�I��]Mw�}�?���G�����?:d	^�_~�����/RnK��F�+}���B���m��i..[;�}$~A~~��2���DO
�
��6�~��76�%t���
��7��HNI��q6��@x�gz���|���'hcE�{gg������ ��!��Ez7n������x�F|����,%���������2���4z::#{������]&,mm��nl���!X0�\�����6�����&-�hd$�"�gV�	�,MZ��v999�S'F��R��#�9�5�o�%&&��I9/�Q��;m�������S�{4&������XP� ��W�������%q�{�*`(�����,nf��bn���j����x��0S���c�c��O���Z���00d��5������I���P�-}���K��J�����2/e�����;;�J�wRx���4��}�S�w�"��K�*'�����0[KK����}�z>��n
JJ��n���R
�[]]������� �.�R��C6���oo��R�_^Z���4d�D�f�L�r�RQV������S����+��V#�!Aa�C9�����2<D�I*�b����V��Nh�H5��N�.?�&8,;+���	�)�8]�{a^�B�����[�#<2���NX^XT�2��������1�{EC����M��,����[�"X��s�U��T�`�n�S����^�{������������cy`��Y�f���wjs�b#?|���lg�(��{r�j�@�0�P�<�
���xq�����q0CEQL���1,V�)���*u<��N����-U��
q���'��6���-���.���6;?���.��1>9zjDX������U?��/���o��D�(H�{W����!A���L���|�]����#�,�+�^�(f�
��5�q���$S�:V#�E�LR���]�7&��ws�z�T��������1�[����r3�89A.�4.IVn�8��}xfG�Y��o�9Q�x�>^K]� ����1��2�n�����M�j�Rs�o8���II	�U����]�

���AU�0��<Q�t�}".~X&.��_�l�>�u�_�~}��y��WG	���Vg��2�'/�T����� ���)�~�4��G�P]]�fFF��?�u�FX���T?��;��CZ�mj��uu*~��6�Q~�qrR�7]t\h[_X�H�:�~1�.��[����xb�6h�tU�a�����@������[����+���2R����������\K�]{�Bl9�}�6r�C(bAv$��EY'���~b�v���V������Kn�" ��^,�Y�Z <T���������o)�~OI�:(�M{�����Q &��0au��jm�71�2�����i����;0unY��Z5"�#c��-�������<e�����kW���P_�d+���3WE��i��7V)�\�����~0i���7�zIX	���@u������uW������Q����H>Q������W�������MZW�������cc|e}�]FFF�:�e�y$�8-��#���u%&���6D�+j��q�!6g�;��5l�������T92��lp�������P%tW��fzg9�������"�'JJ�v��o���������`��bXd<�}D����,e��g����,[�4u��]����_�e{������[w��
�Yta���������`�Hl������&:�ER^AuY���
���$$)�I�)<�Wz"������>~���s��r��:��JI�
���y3�{~*v~��>��������29gPY����*S�{D����� �+���'�K����l�$,��d�$�Z�H}��t3��z{0V���b�zwav
�/���`EC������������/����"Zx
������}�\Ge��F5�M�=��C�#�+*�UX%���S����������s�K6�J��V;���5_Z����V��L[if���wmu��PE0y�_D������}:Z�U!f��t��TRf���-�Q������������"�r:|)bE��pd,��RU����:F��1G8�w�4�$����%��K����Se`���.�|���{��|�y\�D2�w7�gB���������Q�$f�.9�{@�F��h���5�����t���/��86���c�����E�1� c���,y�Z�m���l�E��l�&����5���L�����z��vI+q��W��}�v^�kc�F��cCo�_G�i5���%$��7�����������]rA8n��N�q���UzH�P��1���e~���N��G\��G7[��}M]y�ji%�R������c�.���8W����R�R�L��Mr���[[Xttw��F�����njp�d~�-O�;�^+K���}cf����1#9��Z�������A�/W�$l���������KY�����Kg)&42hpX�����X���0�f���daZ����eR4lm�9%*���dcq���!���W*5�����d�9LOO/�x��tut�_x@���	���V���>��_]������H|��kn�b,�?�T��������sSA!�u���������f�?��������6m_�~��L*?osz���\���.�����i�`�G�D>h�������w��y��Emmm^CEEEAA�89��8���,{�52rr,LL�@�/]q�v�M�/GGG3�r^���,-/{_z��nx� ����q�Rq����o�_��&"������J����
��+���-�yyy`-�������/h�D�^G��
�b���$����K�.���I)���=�MLMe�$��H��I,�����k����������duE�����;ll��d������h�(��Z@�1�vwe:@��������������x�e�f����rB ����P���Z��f�pF&��+~H������q���aa`���0�H�;����;O<C
����Q�jvCs)�	�*53#c��U�p��F��x�HI)��N�07���r2�	k�������%�����_|S^h�+hkk���v����hj2��S��������a��egf�����@��Lz�x���������8�roff�!-T!��w��$�8�e�'>>~nN�r4%!A�����JZ���4*#�A��_������A��?v�W�SO�=�Go�}��h���l�{-+{&����Z��Z[CL�n�.��z�
��[Z�=J��s������w2�##��ab+�*�|*`���S~�����C`?�x.��sj�g����1j�����>����a�����Z����������������L�V#~�������u�;�mB���mo���6>>84419�	��	K���w�LM��9�����|�<�bC��"253�b�k,�)d���x� '&~����I0s������,��P�B�v���|kej�1�������y;H��i�w�<y����,�.u���cm�>� /���6'�j_�I���`�eo��I2�����8��hu�����e ���s��m�����iY ��������_�P�� ��&�����p�c�H
��,Qll�A���0mUUUee�8[\=-����u\��wx����c��7�=����]����BY�Q�PY�����6I`��s��2�������%������J�z��h���>Q���
>�����c�c�����G�7����x-im���=�����z4S�������� �����#��H$p��MF���U&ThA${F-��E�|y�z��LiZ���)##����������I6rvn������(c��-�>z�����z������N�!�!�HHBU7&�x\����P�Rhd���5�hg?���6��u&���5F������r�U%���7�_k�2����	1�vB�����8������s�+���G@��i�K����#w�����N4y��WWW�
)c

	UN:�,/�������O���������3��*��[��-��yy�G�<����Y8�b��U�	��=�\������"��p��i�455��C��������l
kc�������:��W������>��v�s�F��Qx����1W����/�]��
�%0��c@�A���� ��\r
��G3��ndn�s���YSs��-�Sq�l)14��edj���:��Z����������0�@eeU����F�����&6N��_/+&=z��,L�[f�U]]wI_����C�V.4���x-����)Z�||Y���uN�0�{�����D�v����J�����6DX�j
xC���!�������
�&�z�x���{���SDH��>P�����W�����!!���a}�KE�.7*Z�8�w��\�/j\����������-`T~A��/A��P�q*C����#����|+�������
k�Z�*�N�{������8p��e����H���"��3�]
`���rs�%�??19&.�x�

��:Z��}�����i[����,���,�fp�xF������������H�������C�C���^�t	|^m;~4�`7����7l6�
nk��r����X��T������R�?�J�73S�<Pl
=��%l��K�n�s
�k��DN���3+&�s����V1I0&)I��u��sp|,wfA������\�xo��a6xZ,a����������mJ%4%������%a�y����3�'Y$?7�
l&���y���ac�*)���0�9K=�m���lS����"jlF���,+�$��1��/@������d!��=u���h�X���������V����������x�|���l�[�R5�%�~V������n�~���)�'��_!u��I���;��_�n031U��9Y�N�3s`���W�_��C������{{jjTb>'�!�:F

��W�>�����!
c��������/��������)<���vGNN����M�Q�V��K���$�f`6�^���iz 9�BDal���i >�:/E�C�������0�����L<��`kV�
���?SSS�Y���CJM�����
���E���������s���o����h�������?��)��<�����!'%�G"[A�h���\���V��A�m���>c�H����FG��rYY.�7�c���pf������������gM�<�8=���;:2���$G7����co_
�{��26��;	&�w�tp������c���`}�PM�����]KK�����me��9��U��b�o�oc�wYUq���{�,|�h��s8&&f)�vKAA����(*GO5�����V����������dg^PX����)~���������0���:�{��Oakn����0���a���r��5���3������@EA��~_�d&J	:�)0[	�v��'��R0�
>����
KWUi`���FEE�O��7-���z]]�B�*�_�k��������s=oK���0L��*����
�	�%.�.�{�vP�-tj7U�b�v!��xQ�[���K�U��VZ
aba�r����P����Ao�Z�X������
l�!�P?���7
s����c'=
5����,,�h���+0����G`��w���(C�m�������:;;�2�3yH��f_�G���iU���ba�����8/��������;�8;�%f;R��������&'��>%	� 7����$�}d$>!!����QQ�?[qQ���5���������W��t�4�3===���^�������ed�msS#���8E`�1������z 3�����hhi'��
d����v��ggeY����9�2�oQ;u��I��-���%���$�����U��q��g��}�.���yiRH��:�<nX~^���I��g��������KJ(E�tJ����V�V"U���o��#0Q3�~y����p�c�_4������<7�l����K�/"�
��}�������,����{��;W	w�G���3@H;����������+WJTnomG�AgoR=j���������t1�oP��+���- ;=��G���w`��~G���mK���6&]�}�*=�\8����*��5�Y0q�I(r��r�J�4d-7�
Q�kC�S8K�ve�F[�?@u�=v���+�!X��V���6��H����8�
���G�,-IHH._��t~vPI��E����t�~�->>����>b���u�@*%����^�:
�=��V~��P#vy��W�,�a�8��You�#���NU1L�����E��p7�8�A�����#pU��cccmm�k8�d����}|||��������?<I�pC������/q]
����I.��.��|������@�����#��W���A�s�����pj!&����kDD�����!�jaa!0��� n����Dj��es���V��G1+eeg���*�u
�����?��T0���d�S��no��:%e2Y������Yv���������%������&&&[�����\���m�,--	��Q�CN}T��������=�C�O���=)bP�N��o��	��>@qT�H�#6""�B����{z 
g0������tI�@�&����8���&�H�����W��O�fWu�����O@����������������d��o\�S/��m@�3I5�����L��HOO���dg�B��Sii)66v~�:p��E,��xAX��/M�k��������g�����C���O�nS���?����
���9���v�qw��W�����R���$*(8�;�\H�#\�y||��������66&�B�����������HB7t!>2�s�U��JL�'(��vF3^�P_��<��w�ntG�P#��������&��yC����!m�oH����
�
t:�A����P��'b�
3��'}��%C�
���j�cst��'e���U��&Wr2�Jq��a���1�1�������7$/n��L�5N_�����2��vi��y�M]���2
;p�_���%��A��%��yz!vi��w*��Q���!����*M�y��cN�s�&�N��UTO�S�� �EF:���#yx���9'%��6�6tf�Q_��wk"��N�-�p�5n#5�pTg��l=v����y�[>1DNF&�[��<��A���D#	�L����LM	h�R��UY�U��M��{����n!\��������E�������=�����o.?��O��|z�����!`��5�m���=N��#)F���]���hKi�m�Tv-0+4� �w��������Q�l��X5��9�/:���wY�|�K4����NIL�9�Q-a�p���5.�q�\&sg~n�����5�G�V!�<�Mp����A}}}�T�%��:�v��jq�)�,Il�
��(�]�v��
RR���'&���V��!�9�����?�g`������i[/��cFBJ��_������7���a�6zW��-l������������c�������+t������y@����J��7��KN��D>&x�c�_����o���&�����.�����������F���������^������p����z1S�u�R;�����e�h�]�4`�O�^S���/|���0������Q�����G�p��$$$������%0���,���E99/d�X�\}���54/���
*�f!h��!���%�M����!��8E��8�Ih�������S�"�?����\)^��Y����$	n����3D�2rj�1� ��|MM���Z->��o�i�[`1���"��������Jc���k��:E��?��	��YT���~WeQ�rz���!��\���,�=��n�����/��{!�8+�+������r1yK�;Tm2%���e�����������������������T����[k��Pi�+`��c�
$)
���Pq�u�%�����������Wgwl�K*�!���,S�3UF�{���������[��t�(*�>3�?���W�H+�.�j��h�����������I��J'�����g�sI"��?A�|���1�	g~4D
�k[U^�2k�7hh��lU��:hY6����0k
$h��d��N�6�r-=5)^����
*����5����bH��3�9�d��m!��)��X�����L&���L�b�A�%:���������ir��{�����V���'�
Dt���b7o>8����YJ�C��N��o�$o���I��������vO���7��zB��)���g�(q5�^�=�u�W��V4s��ZEHH������?�.)�6�TW'���O���\wa{'A�lQ����V�NUZn�"@��s�V�j��u�
��S/D��/y���a'�B���.@���a�j��uyB��-�~X��n.7]�f^�:j�xO��
��n'���})D�B�$_�z��1����@�h�\�&�cyBzC9�g=���K���R���<����1@��7�xm�F~6IF�?<{k������7a]~��5�������^�{ACd����=0�T��H������%���BEAq��L��g+�'�s���'r�`/�m���!���MhG�-c��6no�:�|rww�l����5�� �E�*[8l<!1�+����*�)�k��Ff|�
!�}74�5���9�u��r�+�,����=�;�_����i�����|��l<'P�*�=;EC2���f�Z*�}X����`�����}����������
ck�=���,���`��@a�����Ta����0��l���p8/�3��������U
g����x�C�����������SSS99,K��	��������B/.J�Nd�;I�5�����!���������:I����=�@c��������5#��� ������=�q
��%�&@�u�m9)mll"�#��(0��Uwv�D��da�����l��Tn����j~nn��]Z���J���Q���N2���1���4Fs [��'�������	a^PXX9�: p���U�lW\��M0z�7I�(��;��gv��`���D���"��.a���+ch��zy�����cgl,A���Q9���w�������g��r��9����c7�5����i��^�f``���4�I^*�#KvW�0rt�R#E��^D2�[C[���������N��������1]����������>���g7��x��O�@W����sa�z���I�(?�Y�(��*�b|BBV�Z��)�{���������W.t��_�tI___
\�	94$��_�����}�������E%Y(�������w�_����w�7��;��w�O�����&���O���������Y	���"��P��5�r����om��l�T�����������0�����^����]���?Z���>>s���R!Q�e�9q�)�����M��b�����1�����[+�_�����@`���F��{7~'���b_,�����ZXXhjj�w����t��6l�
������V���eQRRRW'����^8�i���Ftt4�� ?m�����

���.���a�w�---���������:Z��>y>�U���\���x�a4@F<�����?��qr*()�}1K�����il�������KC�{{.������O�qqp2~�;b#�	P�����2\�	��������(��$�������?T���c�f�)��9� �[[C8��������<���n��S	�E�\VGt��WRZ�L�c�H('�������#�8�"LTdd��O&��z�x�zz�\,����������y���lYS�,��j�)>G�����r����.�����,x9�?���	��
z�:�r|p0kD)�^���F!�G5/��iZ�����-+#c`p��Vu���=�)���MU�������������
���h
���xVI��K#,�$�Z?|��F���j,G��'����\��l�gZ`*N#r�������R���h,-��sc���D��:r���%�������+#�+�F���(�:����������CV��1�@�a�����#���lqj��:)��A:::{�����ii�K�4^����.wD1�L�t��t�
�w;Ue����3�/�g����@��X�wn�.���ijB�:��v]�����f�n�V��R�F��#C{::���d	K�I��0J���-��R
��k_V���Z��5u���H����������X����,3������3�w���Ce����O�
�"SG��}�������w+92g>�������R�6v��6�2\�S�.!���q��8���L����==m�J}g8z�j����V����=RA�c�a�C�gI��GE��L�3������>'?�y�T�cA&���k!C$���.���L>���#�H$�_�G^���Ll��sK�}%��E|���'�xe�%�T�}���0g���ZI���lFddd��d[��@e\I��lV���_H,��6�V���tssd������X���c�����Y$�z���p�,�7m��T��G���T�*oz���G��6}�X#�T_���FO�[O�m��s,z��]����F/9���	E�'O��q�7mF����C��S��l�B���Q�n��T��F�5��uj�)�Rf����J�����sT���a��*�`��yG�y�P�o����X��)���d]~�i�����rZ���;
��qq����C:������4%%_|[RIb�a@���8���G,��j` �l���#��	oi�k�=~e2S�7)�\~��o�^r
��S_U��mh���<�N���~<�o��44��5i8V��K���;����`����E9Y����v�S�vo�<H����g�v4��>����+
G�qj��m�t0����v���6(�<7 ���4_Q���`��-��165���6���3����g��*E1"g�ZO��k��zjb�L��-^�[G��k���3��������v�Yx���L�����
��F_r\\va�����7��`����v\��b��]���/�(m/��f�Y�}�c�`�i��7aL����R?nE473c�.�!����;r� @��}�h�}�z��i b����[��Ul�|�:::xz�'�RMI��H���������]��VLr8�����2SRP�������D{�(�y��&����Xx�5h�eb,��'�]��a�{�l�����T�,"���7O�'$�=���/U�<��lM}�NG�q��-M�r�J4T���c����.�<x��0Db.����KA�n=��r��������vC���N�UG;v�n����^^�X~#9[�ExxOO��(z���Y@��V!��e{���2U�J�Y��Y���q�oD�L���,vY��U��lz\��Wk�;T��k����+C����c|\j'F��(#��7�~p�	w���i`������������Z���M-6�H�S�g���M������
Rz�(;�O��k�.����'[~4���v�q6���i��`�����������#�3sA.��x��y��r�����[Taym]+�sd�����L,���O������&oR��oi�|�������-6�N�>����I�A��Ek�'�{��-�c?�D�~]�J�do���#�Q�i??<���h��I��#	����/��*�M�f(By>��v=��5���G\���������&#8��?���������XT����+�q�}�C�R�`H����A���(�#�b�����R�,�.�>=�F�����<�T>���
!X<��?8TD����mf��������j�|�:~�2�Pwf�����_����[ �H���b�kf��Pf::�����*((�
�c�	�����2*(��8;9�j�$t�iy���9�%�pk;;1�)4�T@{������.�U
��Ny����5��sKL�p�������K;f@=/Y���x��5/&����&�x�� &6��y����Si�WwAD�������M�X��������w�%��s��9�xne����$
����������PCrrr �U��yy5��"�ZK�F�&��7���Zt�maiZZ�1q��<��W���"����W�r�������w�^F��$���o���z�%���^YY���w��!�<@z3������2����B���r��)GQU��W2��<MM�<�����D��f�b%8�a���Z"�xH���&����l���n��9����FnaaL����R��p�o����,f&����Z�e=��z&ff>�a5C�I}�[x���a�RR�dP���� R�yvV�G�0GG������������Bq�3_���WWWy:�]Vulm��(RR���-Bc6��ax�����ZNN;���:��o��),�G���]�6&�����9�J<T�s�_����MD�{J,LL(`|
�@�������UH�%%���ka����\w�9a&�����
�%����������U����/}��2-�--�>m�.��->tq�T�.c��Pg
Y�J���(}���������������`����^@��'��9@��"��(��HZWW�ARg��4.���=�����<}�T�����g (��J��D��1�}�����������e�v���
����Fp����d��e��������m.1iUIn9$Lmr���ZO�;QRRO��qn7��t�#t�O�m�M��s�o�H�,\�v��;mv(Ph�R�����;?�����eki����~��V�WCM��|VV�!���p
���-`���G��oS3-���*+�[t��F���o)b����jd�ff�@�����536n��b�jk��#BG7=�1�����Q�"3����$���19IKM=>>.����T+>!av���D��T���X�2
pp�;w�f�8arrr�F`n@���#u&��e$���M���H��|�����\��:�������<IO�^��M�e��������|p�OW'�
��|�:�PB^pp0��l��
�I�n����/��666���}�-�J�����dB������m�4d'��J%�=�����5�x���^��4����=������;b�;�L0 &������B����Y�����|�?9�I�������^�c<o�,�q����e���oPR�������kGl����a`ga�NM�o8�#O6>I�.��Gm��A�H���8bu[W���v�dn�{:�}w==�����B��f�R�qt7O�k�6���������������%��2��p���>�\�����+'�}p��(=|������[bB��::��+�kW�~C/p422�=��:�re�0��
B�f�C)M�u�o��U��q�>uv��4�WHHB|<=�f6
V��5���$q�{��w�����?@�{�e<�)�q����	�lllh���
����S�������������:���W�����&�G��|��9���??c��������3���@}z-;�TS�^�]RZZ	eh�(�A��VF���/}�����Ak����HEXv����88`!�5��69S�iu3D�t�{����h,��<�[�o�VV�_/��N|r��W
�L"�z7����1!�������E�!( ����x�wSu0g�Pv>q~���d7
��w��F�Q1E���5�����a��<C��fd��inPt��NA��,x
��@��D!#T��o'[��������[
e�����X,}�1xtt����/^�"�c����	Ea�$�������a�-����7�qth^va��U����~�����=������?�T������8.}�{T�+��H2�
[�Z[G���1Os�,Z�>���;@mmm�j���>I�R$���������`P�*�����J�,��
���k�:��Z_p��'�A����Lz�d�>�316�mCz�p��r.��`��dzwy���}���.���x��TT'���M�s7jY�R��(Nq����o�tD�V��i�?r����:~�G�oo��\'9Y�LACC"����^��9h����8i�D�{U���Q�j����5�������E����$$4Vn����"�����g��!p�P�����Qr(�\�\�����#����3=�F��y9VNK���"G�D��l_���6t��P�S-�v�C�����R������e"h��@�9���8���m��BCK��n.�� ������`\���'�:"�}��\\]�uv������6y��B���������������l3���}Ad�};u<zVI�4����&���Y�(8?����}2s��"i(��l�D�n�aR���A�����{vxvz���|���H��f�=a����d�@������0DIE=���[H��sP����o]TcX..�:Z-����~xd��j���h]ho��N)I�y��X���(�Y�`0�'�lm!!�E=h`�+./*���E�tt���k|���B�e�Y��gOk�UTHhh��������"5���o�rYQ��R�7�>����9\oI5���������3F�v?RV�h��r@H�w7��O�n�l5z�������')2CNp
����?��nnwx��W�B�k*�2���(f�����6V��-/��t(�j!�����lu0����m�
nB>'���`���?�����������O���j�����o�����$!W��*}UC}������I��������G��j����"� �$|��������xy������`��E�
Kd�C��Xp�8�F���0Z���}�h������������^��\��Z�o	�__���sz>����� R�UUVv�?���w��(8� �3W���� �M��H$'�AB*j	����8�O�;����7 
h���Qr6AA��<����Zt�`ET�t�p(����su���Qj������l�Wg��C��IHH�`���U��9�O���k�GB���B��G�99��R�����z��0 ��vb{���^��-Bp�&��sT=R�Vj���a���C���O�.���f�N ����"0�D>�A��%�xv���f\|�%��S�Z@4� �7�oggg�>�122���[��		�8����_�.2lN��#O�v����h��J<�m��-�&n�t���8PK�'-==��{8�����b6
F��@�����������? ����oG�_���p�=�����)>G�W�q#6^����>����7bf71�@WV&��o��\w;��/_<!�v�6��v\PTT���p���������0���B/��a����Y����~(���\,�
�����h��Q�|xR�:��F��uoll���������
8����bG�i�w�c���:�0��W���>%=lQ:�����������U\�R�18:����g��l�������
p����+��so�p1;%Y�W�����[Ua�[����\�\^.n��+��A��]3"�n�-#3���@���}t
Y����P�<�
*��mt��g�6���[���O�]i��������^r��v�����Vw��/7���(//����c�wa�7N,��OS�����B�IGO�_,���SPP�
M���C�k�p������^�50(:6�����).tW>Y"��m}�h����������QQ7��*XM���=<<��32�Q�k�k����6��1�������MM���kkk����@�~��r����i�d������a�n���b�z�����>|���f���pe����	�S�(;������5�\�E=j����;!�
�S�����M�\�5L��q�]���{d�������������fWW�����i�&$$@l�xe��xbf&�����������'����T����=�
1�������@H�IX��
k�K��js�
[�uZ�Q
H ��i���l�������_	�[>����������j)� D�O��m�mm���];^������������]5�
��A>�m�M��Y�7\e3
�P�^1��Z'���O��Y�n�<�{J���10���75B�
#l^�^���9
���0u��cN&''�)===�@����5=0���7���c�|�q7��NL����.���	�CZI��o���Q�^V�	-u���n��F���E���a�m�>W)UH��=����Zd��q�rVVKKY�7��?K������aNu���Z�:���5�;��������y.(���W���b����������������[����{r��?�_ S)�t:0���D�O����'�~q����.�i�W��BDC�b��'�\�
`�:�:�,:�/���������	���O�������"/E��"veq�vB���m5�$l�NZn���[Em9#_)��~�G^3�F>��w����T������j�QwS�����Pwpl,1)�0(tFh��8a&~��s4?���o������7��@���S�����n� ~|x�^><�����=Y��8.��4)�pF�u�@C{�=����+J()u~	�qdke�z=��h!����fO0?������=��N�Q(���9��c����H&�n��'jb�����/A!�� 
%e^y��U����������G{W�T��A���"�����
	a1���1��fQ\���!����A#A�\@A�y���`C@$�/����y�������V��{����9�w�t���u�|O��!3���u�U���{��oX�:�h���$'
��-�u�&Z��H8��i���%�8�#M���%��m����!_�18��O���cc��u��������~�����NVV�5��y3��*������;N���CY�#�,25��"���w�U�Qt;�G$.�\w�>�������������SR"�`fH�h>g�v')�V�M\�xb���W��mRWQ1��	q����\{dyM����!���
��@y�����t���/��)��)h�U����[aT��us������<���N�������_�T�a:�����[xm���c���R�{\���>!�H��)�@�3�:�){��y#����OKJ3��+��#�����O�~O"������O���]o	p����T��4��\r6�����xs�~+L���R�y���J3u{���E�i&}���)-6���f#�GWJv6�
5>Q43�~��'�(i�8r�*�L�7I����/\3��e�h��:qj���*��{D�K������M@H��G��b�^����:E"����j�������u��?��]]�X,��c@4���u��V���"�5������G���eJu��f;�� h&�)�>��R�iW/�>7��&���:.!2���L����X�F;����=�{�Z���_)���|Kj7��4����G���:�l�����1���1zt�+"���~M}b��c�XZ*��&��){�����4��v��,�4�D�T�S"".{��}w�F��d
�#����^�p���k�R�r=a���kvF�
[��;�����>�X��v�������F����`0�e��ya�r�UW��4�[�0��f!L-�����w���<d��@��Za0D��z�c��Y�z�[�2�>1Z�=���m�~sxX�Dg�����^/����CQ����lx��x[N�����m���w��c���M9v��E����Yg?yZ�l���(q5\�_���?��k��I!-�F���Z���Q_
'�a�����o,,B�oi�Rj$���$��aKK��G
~A�K���i��@�Qek�3�c��0	�l7��CCC�W+)'��z 
h���p�������!�{��B�}��4���D�y�'V*s����!�����~�����57�4���VD>-��A����i���������FpzV��V��*Dl�����k�c'+tV�@�*%�6?���O!D�J�t��s<�OJn����<+ �o�W�T	n��ZZE���*U��<���`������K@g\*��6�o�'���O@�����,h�h:M������=�7��v
���k���lR_GKK��R�}k��OR$dV^�F��}{��s��S/"�L|�I?{��z6�v�<`���}���a"�
<��o�����%#��fydt�Y8�G����"���?�X��M������� �{zN����=��
8��	O���8���x���K�������b��Y����������%��,2U_m�B��FJ����Nzi5~Lt�~�����9�����w"��X����=�Pu�!���]��t��}��@��G���$�����;f�/���G}�~�|_���(G�X�h]QE����@��^�d8q&-�����5(��Ct�hz�P���^���O~����z�p_R������\Afzzzv66"�m�\s�A'���M�]�@�&B�e�p���~���A7L��rp���	����������F�fo�Xc�/{�%p�\	��B�u�>�	V,�>�
M�zi����W�2�[��Nf�$�����9���[���P#s�k��^���3��S��s�Ji�)(��/��%}I_��9�����l
v7-0001-Precalculate-stable-and-immutable-functions.patchtext/plain; name=v7-0001-Precalculate-stable-and-immutable-functions.patchDownload
From 3ec3bbc4334b9073b6eb9aa658493d9da0887f3f Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Mon, 15 Jan 2018 16:50:22 +0300
Subject: [PATCH v7] Precalculate stable and immutable functions

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.

In this patch the function / operator / another expression is precalculated (=
calculated once for all output rows, but as many times as the expression is
mentioned in the query) if:
1) it doesn't return a set,
2) it's not volatile itself,
3) its arguments are also constants or precalculated expressions.

Costs are changed to reflect the changed behaviour.
Tests, small notes in the documentation and support for the prepared statements
are included.
---
 doc/src/sgml/ref/create_function.sgml              |    9 +
 doc/src/sgml/xfunc.sgml                            |    6 +-
 src/backend/commands/createas.c                    |    3 +-
 src/backend/commands/explain.c                     |    3 +-
 src/backend/commands/foreigncmds.c                 |    1 +
 src/backend/commands/portalcmds.c                  |    2 +-
 src/backend/commands/prepare.c                     |    7 +-
 src/backend/commands/schemacmds.c                  |    1 +
 src/backend/commands/trigger.c                     |    1 +
 src/backend/executor/execExpr.c                    |   39 +-
 src/backend/executor/execExprInterp.c              |   51 +
 src/backend/executor/execParallel.c                |    1 +
 src/backend/executor/functions.c                   |    2 +
 src/backend/executor/spi.c                         |    9 +-
 src/backend/nodes/copyfuncs.c                      |   17 +
 src/backend/nodes/equalfuncs.c                     |   11 +
 src/backend/nodes/nodeFuncs.c                      |   76 +
 src/backend/nodes/outfuncs.c                       |   12 +
 src/backend/nodes/params.c                         |    2 +
 src/backend/nodes/readfuncs.c                      |   16 +
 src/backend/optimizer/path/allpaths.c              |    9 +-
 src/backend/optimizer/path/clausesel.c             |   12 +
 src/backend/optimizer/path/costsize.c              |  218 +-
 src/backend/optimizer/plan/planagg.c               |    2 +
 src/backend/optimizer/plan/planner.c               | 1106 +++-
 src/backend/optimizer/prep/prepjointree.c          |    1 +
 src/backend/optimizer/util/clauses.c               |   41 +-
 src/backend/parser/parse_agg.c                     |    1 +
 src/backend/tcop/postgres.c                        |   11 +-
 src/backend/tcop/utility.c                         |    2 +
 src/backend/utils/adt/domains.c                    |    5 +-
 src/backend/utils/adt/ruleutils.c                  |    5 +
 src/backend/utils/cache/plancache.c                |  180 +-
 src/backend/utils/cache/typcache.c                 |   56 +-
 src/include/executor/execExpr.h                    |   31 +
 src/include/nodes/execnodes.h                      |   19 +-
 src/include/nodes/nodes.h                          |    3 +
 src/include/nodes/params.h                         |   21 +
 src/include/nodes/plannodes.h                      |    2 +
 src/include/nodes/primnodes.h                      |   96 +-
 src/include/nodes/relation.h                       |    4 +-
 src/include/optimizer/planner.h                    |   11 +-
 src/include/optimizer/tlist.h                      |    8 +-
 src/include/tcop/tcopprot.h                        |    4 +-
 src/include/utils/plancache.h                      |   12 +-
 src/include/utils/typcache.h                       |    2 +
 src/pl/plpgsql/src/pl_exec.c                       |   18 +
 .../expected/precalculate_stable_functions.out     | 6645 ++++++++++++++++++++
 .../expected/precalculate_stable_functions_1.out   | 6291 ++++++++++++++++++
 src/test/regress/parallel_schedule                 |    2 +-
 src/test/regress/serial_schedule                   |    1 +
 .../regress/sql/precalculate_stable_functions.sql  | 2117 +++++++
 52 files changed, 17040 insertions(+), 165 deletions(-)
 create mode 100644 src/test/regress/expected/precalculate_stable_functions.out
 create mode 100644 src/test/regress/expected/precalculate_stable_functions_1.out
 create mode 100644 src/test/regress/sql/precalculate_stable_functions.sql

diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index fd229d1..7079ec2 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -337,6 +337,15 @@ CREATE [ OR REPLACE ] FUNCTION
        <literal>setval()</literal>.
       </para>
 
+      <note>
+       <para>
+        Stable, immutable functions and other nonovolatile expressions are
+        precalculated (= calculated once for all output rows, but as many times
+        as expression is mentioned in query), if they don't return a set and
+        their arguments are constants or recursively precalculated expressions.
+       </para>
+      </note>
+
       <para>
        For additional details see <xref linkend="xfunc-volatility"/>.
       </para>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index bbc3766..4825d98 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1533,7 +1533,11 @@ CREATE FUNCTION test(int, int) RETURNS int
 
    <para>
     For best optimization results, you should label your functions with the
-    strictest volatility category that is valid for them.
+    strictest volatility category that is valid for them. Stable, immutable
+    functions and other nonovolatile expressions are precalculated (= calculated
+    once for all output rows, but as many times as expression is mentioned in
+    query), if they don't return a set and their arguments are constants or
+    recursively precalculated expressions.
    </para>
 
    <para>
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 3d82edb..74b7dfb 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -327,7 +327,8 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		Assert(query->commandType == CMD_SELECT);
 
 		/* plan the query */
-		plan = pg_plan_query(query, CURSOR_OPT_PARALLEL_OK, params);
+		plan = pg_plan_query(query, CURSOR_OPT_PARALLEL_OK,
+							 (ParamListInfoCommon) params);
 
 		/*
 		 * Use a snapshot with an updated command ID to ensure this query sees
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 41cd47e..423b25a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -361,7 +361,8 @@ ExplainOneQuery(Query *query, int cursorOptions,
 		INSTR_TIME_SET_CURRENT(planstart);
 
 		/* plan the query */
-		plan = pg_plan_query(query, cursorOptions, params);
+		plan = pg_plan_query(query, cursorOptions,
+							 (ParamListInfoCommon) params);
 
 		INSTR_TIME_SET_CURRENT(planduration);
 		INSTR_TIME_SUBTRACT(planduration, planstart);
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 44f3da9..ef3577e 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -1619,6 +1619,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
 			pstmt->utilityStmt = (Node *) cstmt;
 			pstmt->stmt_location = rs->stmt_location;
 			pstmt->stmt_len = rs->stmt_len;
+			pstmt->hasCachedExpr = false;
 
 			/* Execute statement */
 			ProcessUtility(pstmt,
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 6ecaea1..48a5dc3 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -89,7 +89,7 @@ PerformCursorOpen(DeclareCursorStmt *cstmt, ParamListInfo params,
 		elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
 
 	/* Plan the query, applying the specified options */
-	plan = pg_plan_query(query, cstmt->options, params);
+	plan = pg_plan_query(query, cstmt->options, (ParamListInfoCommon) params);
 
 	/*
 	 * Create a portal and copy the plan and queryString into its memory.
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index b945b15..9703f41 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -243,7 +243,8 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
 									   entry->plansource->query_string);
 
 	/* Replan if needed, and increment plan refcount for portal */
-	cplan = GetCachedPlan(entry->plansource, paramLI, false, NULL);
+	cplan = GetCachedPlan(entry->plansource, (ParamListInfoCommon) paramLI,
+						  false, NULL, true);
 	plan_list = cplan->stmt_list;
 
 	/*
@@ -397,6 +398,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params,
 		palloc(offsetof(ParamListInfoData, params) +
 			   num_params * sizeof(ParamExternData));
 	/* we have static list of params, so no hooks needed */
+	paramLI->common.type = PARAM_LIST_INFO_DATA;
 	paramLI->paramFetch = NULL;
 	paramLI->paramFetchArg = NULL;
 	paramLI->paramCompile = NULL;
@@ -670,7 +672,8 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	}
 
 	/* Replan if needed, and acquire a transient refcount */
-	cplan = GetCachedPlan(entry->plansource, paramLI, true, queryEnv);
+	cplan = GetCachedPlan(entry->plansource, (ParamListInfoCommon) paramLI,
+						  true, queryEnv, true);
 
 	INSTR_TIME_SET_CURRENT(planduration);
 	INSTR_TIME_SUBTRACT(planduration, planstart);
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 16b6e8f..3a1bfcd 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -188,6 +188,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
 		wrapper->utilityStmt = stmt;
 		wrapper->stmt_location = stmt_location;
 		wrapper->stmt_len = stmt_len;
+		wrapper->hasCachedExpr = false;
 
 		/* do this step */
 		ProcessUtility(wrapper,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 1c488c3..8b3475c 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1254,6 +1254,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
 		wrapper->utilityStmt = (Node *) atstmt;
 		wrapper->stmt_location = -1;
 		wrapper->stmt_len = -1;
+		wrapper->hasCachedExpr = false;
 
 		/* ... and execute it */
 		ProcessUtility(wrapper,
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 7945738..6b641e6 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -863,6 +863,38 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		case T_CachedExpr:
+			{
+				/*
+				 * Allocate CachedExprState used by all steps of CachedExpr
+				 * evaluation.
+				 */
+				scratch.d.cachedexpr.state = (CachedExprState *) palloc(
+					sizeof(CachedExprState));
+				scratch.d.cachedexpr.state->isExecuted = false;
+				scratch.d.cachedexpr.state->resnull = false;
+				scratch.d.cachedexpr.state->resvalue = (Datum) 0;
+				scratch.d.cachedexpr.state->restypid = exprType(
+					(const Node *) node);
+
+				/* add EEOP_CACHEDEXPR_IF_CACHED step */
+				scratch.opcode = EEOP_CACHEDEXPR_IF_CACHED;
+				ExprEvalPushStep(state, &scratch);
+
+				/* add subexpression steps */
+				ExecInitExprRec((Expr *) ((CachedExpr *) node)->subexpr, state,
+								resv, resnull);
+
+				/* add EEOP_CACHEDEXPR_SUBEXPR_END step */
+				scratch.opcode = EEOP_CACHEDEXPR_SUBEXPR_END;
+				ExprEvalPushStep(state, &scratch);
+
+				/* adjust jump target */
+				scratch.d.cachedexpr.state->jumpdone = state->steps_len;
+
+				break;
+			}
+
 		case T_ArrayRef:
 			{
 				ArrayRef   *aref = (ArrayRef *) node;
@@ -2062,6 +2094,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		/* note that DomainConstraintExpr nodes are handled within this block */
 		case T_CoerceToDomain:
 			{
 				CoerceToDomain *ctest = (CoerceToDomain *) node;
@@ -2665,6 +2698,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;
@@ -2702,15 +2736,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/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f646fd9..8f6c886 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -391,6 +391,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_AGG_PLAIN_TRANS,
 		&&CASE_EEOP_AGG_ORDERED_TRANS_DATUM,
 		&&CASE_EEOP_AGG_ORDERED_TRANS_TUPLE,
+		&&CASE_EEOP_CACHEDEXPR_IF_CACHED,
+		&&CASE_EEOP_CACHEDEXPR_SUBEXPR_END,
 		&&CASE_EEOP_LAST
 	};
 
@@ -1755,6 +1757,55 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_CACHEDEXPR_IF_CACHED)
+		{
+			if (op->d.cachedexpr.state->isExecuted)
+			{
+				/* use saved result and skip subexpression evaluation */
+				*op->resnull = op->d.cachedexpr.state->resnull;
+				if (!(*op->resnull))
+					*op->resvalue = op->d.cachedexpr.state->resvalue;
+
+				EEO_JUMP(op->d.cachedexpr.state->jumpdone);
+			}
+
+			/* we are ready for subexpression evaluation */
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHEDEXPR_SUBEXPR_END)
+		{
+			int16		restyplen;
+			bool		restypbyval;
+			MemoryContext oldContext;
+
+			/* save result */
+			op->d.cachedexpr.state->resnull = *op->resnull;
+			if (!(*op->resnull))
+			{
+				get_typlenbyval(op->d.cachedexpr.state->restypid, &restyplen,
+								&restypbyval);
+
+				/*
+				 * Switch per-query memory context. It is necessary to save the
+				 * subexpression result between all tuples if its value datum is
+				 * a pointer.
+				 */
+				oldContext = MemoryContextSwitchTo(
+					econtext->ecxt_per_query_memory);
+
+				op->d.cachedexpr.state->resvalue = datumCopy(*op->resvalue,
+															 restypbyval,
+															 restyplen);
+
+				/* switch memory context back */
+				MemoryContextSwitchTo(oldContext);
+			}
+			op->d.cachedexpr.state->isExecuted = true;
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_LAST)
 		{
 			/* unreachable */
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index f8b72eb..a9690de 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -207,6 +207,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->utilityStmt = NULL;
 	pstmt->stmt_location = -1;
 	pstmt->stmt_len = -1;
+	pstmt->hasCachedExpr = false;
 
 	/* Return serialized copy of our dummy PlannedStmt. */
 	return nodeToString(pstmt);
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 7e249f5..bce4352 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -502,6 +502,7 @@ init_execution_state(List *queryTree_list,
 				stmt->utilityStmt = queryTree->utilityStmt;
 				stmt->stmt_location = queryTree->stmt_location;
 				stmt->stmt_len = queryTree->stmt_len;
+				stmt->hasCachedExpr = false;
 			}
 			else
 				stmt = pg_plan_query(queryTree,
@@ -912,6 +913,7 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
 				palloc(offsetof(ParamListInfoData, params) +
 					   nargs * sizeof(ParamExternData));
 			/* we have static list of params, so no hooks needed */
+			paramLI->common.type = PARAM_LIST_INFO_DATA;
 			paramLI->paramFetch = NULL;
 			paramLI->paramFetchArg = NULL;
 			paramLI->paramCompile = NULL;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 995f67d..375f5aa 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1202,7 +1202,8 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 	 */
 
 	/* Replan if needed, and increment plan refcount for portal */
-	cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv);
+	cplan = GetCachedPlan(plansource, (ParamListInfoCommon) paramLI, false,
+						  _SPI_current->queryEnv, false);
 	stmt_list = cplan->stmt_list;
 
 	if (!plan->saved)
@@ -1636,7 +1637,7 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
 
 	/* Get the generic plan for the query */
 	cplan = GetCachedPlan(plansource, NULL, plan->saved,
-						  _SPI_current->queryEnv);
+						  _SPI_current->queryEnv, false);
 	Assert(cplan == plansource->gplan);
 
 	/* Pop the error context stack */
@@ -2026,7 +2027,8 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 		 * Replan if needed, and increment plan refcount.  If it's a saved
 		 * plan, the refcount must be backed by the CurrentResourceOwner.
 		 */
-		cplan = GetCachedPlan(plansource, paramLI, plan->saved, _SPI_current->queryEnv);
+		cplan = GetCachedPlan(plansource, (ParamListInfoCommon) paramLI,
+							  plan->saved, _SPI_current->queryEnv, false);
 		stmt_list = cplan->stmt_list;
 
 		/*
@@ -2257,6 +2259,7 @@ _SPI_convert_params(int nargs, Oid *argtypes,
 		paramLI = (ParamListInfo) palloc(offsetof(ParamListInfoData, params) +
 										 nargs * sizeof(ParamExternData));
 		/* we have static list of params, so no hooks needed */
+		paramLI->common.type = PARAM_LIST_INFO_DATA;
 		paramLI->paramFetch = NULL;
 		paramLI->paramFetchArg = NULL;
 		paramLI->paramCompile = NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ddbbc79..a47cfa7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -101,6 +101,7 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_LOCATION_FIELD(stmt_location);
 	COPY_LOCATION_FIELD(stmt_len);
+	COPY_SCALAR_FIELD(hasCachedExpr);
 
 	return newnode;
 }
@@ -1417,6 +1418,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 *
@@ -4884,6 +4898,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 30ccc9c..ec67571 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);
@@ -3035,6 +3043,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 6c76c41..4a8bfbf 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;
@@ -1910,6 +1955,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;
@@ -2528,6 +2585,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 5e72df1..995970e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -286,6 +286,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_NODE_FIELD(utilityStmt);
 	WRITE_LOCATION_FIELD(stmt_location);
 	WRITE_LOCATION_FIELD(stmt_len);
+	WRITE_BOOL_FIELD(hasCachedExpr);
 }
 
 /*
@@ -1180,6 +1181,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");
@@ -3793,6 +3802,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/params.c b/src/backend/nodes/params.c
index 79197b1..c748cf1 100644
--- a/src/backend/nodes/params.c
+++ b/src/backend/nodes/params.c
@@ -46,6 +46,7 @@ copyParamList(ParamListInfo from)
 		from->numParams * sizeof(ParamExternData);
 
 	retval = (ParamListInfo) palloc(size);
+	retval->common.type = PARAM_LIST_INFO_DATA;
 	retval->paramFetch = NULL;
 	retval->paramFetchArg = NULL;
 	retval->paramCompile = NULL;
@@ -222,6 +223,7 @@ RestoreParamList(char **start_address)
 		nparams * sizeof(ParamExternData);
 
 	paramLI = (ParamListInfo) palloc(size);
+	paramLI->common.type = PARAM_LIST_INFO_DATA;
 	paramLI->paramFetch = NULL;
 	paramLI->paramFetchArg = NULL;
 	paramLI->paramCompile = NULL;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 9925866..1db3a18 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -640,6 +640,19 @@ _readWindowFunc(void)
 }
 
 /*
+ * _readCachedExpr
+ */
+static CachedExpr *
+_readCachedExpr(void)
+{
+	READ_LOCALS(CachedExpr);
+
+	READ_NODE_FIELD(subexpr);
+
+	READ_DONE();
+}
+
+/*
  * _readArrayRef
  */
 static ArrayRef *
@@ -1484,6 +1497,7 @@ _readPlannedStmt(void)
 	READ_NODE_FIELD(utilityStmt);
 	READ_LOCATION_FIELD(stmt_location);
 	READ_LOCATION_FIELD(stmt_len);
+	READ_BOOL_FIELD(hasCachedExpr);
 
 	READ_DONE();
 }
@@ -2482,6 +2496,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/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index c5304b7..c7a841a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -380,7 +380,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 				set_subquery_pathlist(root, rel, rti, rte);
 				break;
 			case RTE_FUNCTION:
-				set_function_size_estimates(root, rel);
+				{
+					rel->baserestrictinfo = replace_qual_cached_expressions(
+						rel->baserestrictinfo, root);
+					set_function_size_estimates(root, rel);
+				}
 				break;
 			case RTE_TABLEFUNC:
 				set_tablefunc_size_estimates(root, rel);
@@ -519,6 +523,9 @@ set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	 */
 	check_index_predicates(root, rel);
 
+	rel->baserestrictinfo = replace_qual_cached_expressions(
+		rel->baserestrictinfo, root);
+
 	/* Mark rel with estimated output rows, width, etc */
 	set_baserel_size_estimates(root, rel);
 }
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index f471794..3122db1 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -827,6 +827,18 @@ clause_selectivity(PlannerInfo *root,
 								jointype,
 								sjinfo);
 	}
+	else if (IsA(clause, CachedExpr))
+	{
+		/*
+		 * Not sure this case is needed, but it can't hurt.
+		 * Calculate selectivity of subexpression.
+		 */
+		s1 = clause_selectivity(root,
+								(Node *) ((CachedExpr *) clause)->subexpr,
+								varRelid,
+								jointype,
+								sjinfo);
+	}
 	else
 	{
 		/*
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 8679b14..a9cdf2b 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -143,6 +143,8 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root,
 			   PathKey *pathkey);
 static void cost_rescan(PlannerInfo *root, Path *path,
 			Cost *rescan_startup_cost, Cost *rescan_total_cost);
+static QualCost cost_eval_cacheable_expr(CacheableExpr *node,
+										 PlannerInfo *root);
 static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
 static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
 						  ParamPathInfo *param_info,
@@ -3737,6 +3739,129 @@ cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root)
 	*cost = context.total;
 }
 
+/*
+ * cost_eval_cacheable_expr
+ *		Evaluate the cost for expressions that can be cached.
+ *
+ * This function was created to not duplicate code for some expression and
+ * cached some expression.
+ */
+static QualCost
+cost_eval_cacheable_expr(CacheableExpr *node, PlannerInfo *root)
+{
+	QualCost	node_cost;
+
+	node_cost.startup = node_cost.per_tuple = 0;
+
+	/*
+	 * For each operator or function node in the given tree, we charge the
+	 * estimated execution cost given by pg_proc.procost (remember to multiply
+	 * this by cpu_operator_cost).
+	 *
+	 * Vars and Consts are charged zero, and so are boolean operators (AND,
+	 * OR, NOT). Simplistic, but a lot better than no model at all.
+	 */
+	if (IsA(node, FuncExpr))
+	{
+		node_cost.per_tuple = get_func_cost(((FuncExpr *) node)->funcid) *
+			cpu_operator_cost;
+	}
+	else if (IsA(node, OpExpr) ||
+			 IsA(node, DistinctExpr) ||
+			 IsA(node, NullIfExpr))
+	{
+		OpExpr	   *opexpr = (OpExpr *) node;
+
+		/* rely on struct equivalence to treat these all alike */
+		set_opfuncid(opexpr);
+
+		node_cost.per_tuple = get_func_cost(opexpr->opfuncid) *
+			cpu_operator_cost;
+	}
+	else if (IsA(node, ScalarArrayOpExpr))
+	{
+		/*
+		 * Estimate that the operator will be applied to about half of the
+		 * array elements before the answer is determined.
+		 */
+		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
+		Node	   *arraynode = (Node *) lsecond(saop->args);
+
+		set_sa_opfuncid(saop);
+		node_cost.per_tuple = get_func_cost(saop->opfuncid) *
+			cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
+	}
+	else if (IsA(node, CoerceViaIO))
+	{
+		CoerceViaIO *iocoerce = (CoerceViaIO *) node;
+		Oid			iofunc;
+		Oid			typioparam;
+		bool		typisvarlena;
+
+		/* check the result type's input function */
+		getTypeInputInfo(iocoerce->resulttype,
+						 &iofunc, &typioparam);
+		node_cost.per_tuple = get_func_cost(iofunc) * cpu_operator_cost;
+		/* check the input type's output function */
+		getTypeOutputInfo(exprType((Node *) iocoerce->arg),
+						  &iofunc, &typisvarlena);
+		node_cost.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
+	}
+	else if (IsA(node, ArrayCoerceExpr))
+	{
+		ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
+		QualCost	perelemcost;
+
+		cost_qual_eval_node(&perelemcost, (Node *) acoerce->elemexpr, root);
+		node_cost.startup = perelemcost.startup;
+		if (perelemcost.per_tuple > 0)
+			node_cost.per_tuple += perelemcost.per_tuple *
+				estimate_array_length((Node *) acoerce->arg);
+	}
+	else if (IsA(node, RowCompareExpr))
+	{
+		/* Conservatively assume we will check all the columns */
+		RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+		ListCell   *lc;
+
+		foreach(lc, rcexpr->opnos)
+		{
+			Oid			opid = lfirst_oid(lc);
+
+			node_cost.per_tuple += get_func_cost(get_opcode(opid)) *
+				cpu_operator_cost;
+		}
+	}
+	else if (IsA(node, MinMaxExpr) ||
+			 IsA(node, SQLValueFunction) ||
+			 IsA(node, XmlExpr) ||
+			 IsA(node, CoerceToDomain))
+	{
+		/* Treat all these as having cost 1 */
+		node_cost.per_tuple = cpu_operator_cost;
+	}
+	else if (!(IsA(node, ArrayRef) ||
+			   IsA(node, BoolExpr) ||
+			   IsA(node, FieldSelect) ||
+			   IsA(node, RelabelType) ||
+			   IsA(node, ConvertRowtypeExpr) ||
+			   IsA(node, CaseExpr) ||
+			   IsA(node, CaseTestExpr) ||
+			   IsA(node, ArrayExpr) ||
+			   IsA(node, RowExpr) ||
+			   IsA(node, CoalesceExpr) ||
+			   IsA(node, NullTest) ||
+			   IsA(node, BooleanTest) ||
+			   IsA(node, CoerceToDomainValue)))
+	{
+		elog(ERROR,
+			 "unrecognized type of cacheable node: %d",
+			 (int) nodeTag(node));
+	}
+
+	return node_cost;
+}
+
 static bool
 cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 {
@@ -3810,32 +3935,35 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 	 * moreover, since our rowcount estimates for functions tend to be pretty
 	 * phony, the results would also be pretty phony.
 	 */
-	if (IsA(node, FuncExpr))
-	{
-		context->total.per_tuple +=
-			get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
-	}
-	else if (IsA(node, OpExpr) ||
-			 IsA(node, DistinctExpr) ||
-			 IsA(node, NullIfExpr))
+	if (IsA(node, FuncExpr) ||
+		IsA(node, OpExpr) ||
+		IsA(node, DistinctExpr) ||
+		IsA(node, NullIfExpr) ||
+		IsA(node, ScalarArrayOpExpr) ||
+		IsA(node, CoerceViaIO) ||
+		IsA(node, ArrayCoerceExpr) ||
+		IsA(node, RowCompareExpr) ||
+		IsA(node, MinMaxExpr) ||
+		IsA(node, SQLValueFunction) ||
+		IsA(node, XmlExpr) ||
+		IsA(node, CoerceToDomain))
 	{
-		/* rely on struct equivalence to treat these all alike */
-		set_opfuncid((OpExpr *) node);
-		context->total.per_tuple +=
-			get_func_cost(((OpExpr *) node)->opfuncid) * cpu_operator_cost;
+		QualCost	node_cost = cost_eval_cacheable_expr(
+			(CacheableExpr *) node, context->root);
+
+		context->total.startup += node_cost.startup;
+		context->total.per_tuple += node_cost.per_tuple;
 	}
-	else if (IsA(node, ScalarArrayOpExpr))
+	else if (IsA(node, CachedExpr))
 	{
 		/*
-		 * Estimate that the operator will be applied to about half of the
-		 * array elements before the answer is determined.
+		 * Calculate subexpression cost as usual and add it to startup cost
+		 * (because subexpression will be executed only once for all tuples).
 		 */
-		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
-		Node	   *arraynode = (Node *) lsecond(saop->args);
+		QualCost	node_cost = cost_eval_cacheable_expr(
+			((CachedExpr *) node)->subexpr, context->root);
 
-		set_sa_opfuncid(saop);
-		context->total.per_tuple += get_func_cost(saop->opfuncid) *
-			cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
+		context->total.startup += node_cost.startup + node_cost.per_tuple;
 	}
 	else if (IsA(node, Aggref) ||
 			 IsA(node, WindowFunc))
@@ -3851,55 +3979,9 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 		 */
 		return false;			/* don't recurse into children */
 	}
-	else if (IsA(node, CoerceViaIO))
-	{
-		CoerceViaIO *iocoerce = (CoerceViaIO *) node;
-		Oid			iofunc;
-		Oid			typioparam;
-		bool		typisvarlena;
-
-		/* check the result type's input function */
-		getTypeInputInfo(iocoerce->resulttype,
-						 &iofunc, &typioparam);
-		context->total.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
-		/* check the input type's output function */
-		getTypeOutputInfo(exprType((Node *) iocoerce->arg),
-						  &iofunc, &typisvarlena);
-		context->total.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
-	}
-	else if (IsA(node, ArrayCoerceExpr))
-	{
-		ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
-		QualCost	perelemcost;
-
-		cost_qual_eval_node(&perelemcost, (Node *) acoerce->elemexpr,
-							context->root);
-		context->total.startup += perelemcost.startup;
-		if (perelemcost.per_tuple > 0)
-			context->total.per_tuple += perelemcost.per_tuple *
-				estimate_array_length((Node *) acoerce->arg);
-	}
-	else if (IsA(node, RowCompareExpr))
+	else if (IsA(node, NextValueExpr))
 	{
-		/* Conservatively assume we will check all the columns */
-		RowCompareExpr *rcexpr = (RowCompareExpr *) node;
-		ListCell   *lc;
-
-		foreach(lc, rcexpr->opnos)
-		{
-			Oid			opid = lfirst_oid(lc);
-
-			context->total.per_tuple += get_func_cost(get_opcode(opid)) *
-				cpu_operator_cost;
-		}
-	}
-	else if (IsA(node, MinMaxExpr) ||
-			 IsA(node, SQLValueFunction) ||
-			 IsA(node, XmlExpr) ||
-			 IsA(node, CoerceToDomain) ||
-			 IsA(node, NextValueExpr))
-	{
-		/* Treat all these as having cost 1 */
+		/* Treat this as having cost 1 */
 		context->total.per_tuple += cpu_operator_cost;
 	}
 	else if (IsA(node, CurrentOfExpr))
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 95cbffb..8d7b345 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -38,6 +38,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "optimizer/planner.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
@@ -368,6 +369,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
 	subroot->plan_params = NIL;
 	subroot->outer_params = NULL;
 	subroot->init_plans = NIL;
+	subroot->hasCachedExpr = false;
 
 	subroot->parse = parse = copyObject(root->parse);
 	IncrementVarSublevelsUp((Node *) parse, 1, 1);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 7b52dad..8865445 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 */
@@ -109,6 +110,14 @@ typedef struct
 	int		   *tleref_to_colnum_map;
 } grouping_sets_data;
 
+typedef struct replace_cached_expressions_context
+{
+	PlannerInfo *root;
+
+	bool		innermost_const_or_cached_casetestexpr;
+	bool		innermost_const_or_cached_coercetodomainvalue;
+} 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);
@@ -185,6 +194,11 @@ 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 bool is_cached_or_const(Expr *node, ParamListInfoCommon boundParams);
+static Node *get_cached_expr_node(CacheableExpr *subexpr,
+								  replace_cached_expressions_context *context);
+static Node *replace_cached_expressions_mutator(Node *node,
+								replace_cached_expressions_context *context);
 
 
 /*****************************************************************************
@@ -201,7 +215,7 @@ static void adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
  *
  *****************************************************************************/
 PlannedStmt *
-planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
+planner(Query *parse, int cursorOptions, ParamListInfoCommon boundParams)
 {
 	PlannedStmt *result;
 
@@ -213,7 +227,8 @@ planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 }
 
 PlannedStmt *
-standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
+standard_planner(Query *parse, int cursorOptions,
+				 ParamListInfoCommon boundParams)
 {
 	PlannedStmt *result;
 	PlannerGlobal *glob;
@@ -480,6 +495,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->utilityStmt = parse->utilityStmt;
 	result->stmt_location = parse->stmt_location;
 	result->stmt_len = parse->stmt_len;
+	result->hasCachedExpr = root->hasCachedExpr;
 
 	return result;
 }
@@ -554,6 +570,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	else
 		root->wt_param_id = -1;
 	root->non_recursive_path = NULL;
+	root->hasCachedExpr = false;
 
 	/*
 	 * If there is a WITH list, process each WITH query and build an initplan
@@ -1220,6 +1237,7 @@ inheritance_planner(PlannerInfo *root)
 		 */
 		subroot = makeNode(PlannerInfo);
 		memcpy(subroot, parent_root, sizeof(PlannerInfo));
+		subroot->hasCachedExpr = false;
 
 		/*
 		 * Generate modified query with this rel as target.  We first apply
@@ -6083,6 +6101,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	root->query_level = 1;
 	root->planner_cxt = CurrentMemoryContext;
 	root->wt_param_id = -1;
+	root->hasCachedExpr = false;
 
 	/* Build a minimal RTE for the rel */
 	rte = makeNode(RangeTblEntry);
@@ -6201,3 +6220,1086 @@ get_partitioned_child_rels_for_join(PlannerInfo *root, Relids join_relids)
 
 	return result;
 }
+
+/*
+ * replace_pathtarget_cached_expressions
+ *		Replace cached expresisons in a PathTarget tlist.
+ *
+ * As a notational convenience, returns the same PathTarget pointer passed in.
+ */
+PathTarget *
+replace_pathtarget_cached_expressions(PathTarget *target, PlannerInfo *root)
+{
+	replace_cached_expressions_context context;
+
+	context.root = root;
+	context.innermost_const_or_cached_casetestexpr = false;
+	context.innermost_const_or_cached_coercetodomainvalue = false;
+
+	target->exprs = (List *) replace_cached_expressions_mutator(
+		(Node *) target->exprs, &context);
+
+	return target;
+}
+
+/*
+ * replace_qual_cached_expressions
+ *		Replace cacehd expressions in a WHERE clause. The input can be either an
+ *		implicitly-ANDed list of boolean expressions, or a list of RestrictInfo
+ *		nodes.
+ */
+List *
+replace_qual_cached_expressions(List *quals, PlannerInfo *root)
+{
+	replace_cached_expressions_context context;
+
+	context.root = root;
+	context.innermost_const_or_cached_casetestexpr = false;
+	context.innermost_const_or_cached_coercetodomainvalue = false;
+
+	return (List *) replace_cached_expressions_mutator((Node *) quals,
+													   &context);
+}
+
+/*
+ * Return true if node is null or const or CachedExpr or extern const parameter.
+ */
+static bool
+is_cached_or_const(Expr *node, ParamListInfoCommon boundParams)
+{
+	if (node == NULL || IsA(node, Const) || IsA(node, CachedExpr))
+		return true;
+
+	if (IsA(node, Param))
+	{
+		Param	   *param = (Param *) node;
+		int			paramid = param->paramid;
+		ParamListInfoDataType boundParamsType;
+
+		if (param->paramkind != PARAM_EXTERN ||
+			paramid <= 0 ||
+			boundParams == NULL)
+			return false;
+
+		boundParamsType = boundParams->type;
+
+		switch (boundParamsType)
+		{
+			case PARAM_LIST_INFO_DATA:
+				{
+					ParamListInfo params = (ParamListInfo) boundParams;
+
+					return (paramid <= params->numParams &&
+							params->params[paramid - 1].pflags &
+							PARAM_FLAG_CONST);
+				}
+			case PARAM_LIST_INFO_PRECALCULATION_DATA:
+				{
+					ParamListInfoPrecalculation params =
+						(ParamListInfoPrecalculation) boundParams;
+
+					return (paramid <= params->numParams &&
+							params->isConstParam[paramid - 1]);
+				}
+			default:
+				elog(ERROR, "unrecognized ParamListInfoData type: %d",
+					 (int) boundParamsType);
+				break;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * Return the new CachedExpr node and mark this in context.
+ *
+ * This is an auxiliary function for replace_cached_expressions_mutator.
+ */
+static Node *
+get_cached_expr_node(CacheableExpr *subexpr,
+					 replace_cached_expressions_context *context)
+{
+	CachedExpr *cached_expr = makeNode(CachedExpr);
+	cached_expr->subexpr = subexpr;
+
+	context->root->hasCachedExpr = true;
+
+	return (Node *) cached_expr;
+}
+
+static Node *
+replace_cached_expressions_mutator(Node *node,
+								   replace_cached_expressions_context *context)
+{
+	ParamListInfoCommon boundParams = context->root->glob->boundParams;
+
+	if (node == NULL)
+		return NULL;
+
+	/* mutate certain types of nodes */
+	if (IsA(node, CachedExpr))
+	{
+		/* nothing to mutate */
+		return node;
+	}
+	else if (IsA(node, RestrictInfo))
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) node;
+		RestrictInfo *new_rinfo;
+
+		/* Recurse into node manually */
+		new_rinfo = (RestrictInfo *) palloc(sizeof(RestrictInfo));
+		memcpy((new_rinfo), (rinfo), sizeof(RestrictInfo));
+
+		/*
+		 * For an OR clause, recurse into the marked-up tree so that we replace
+		 * cached expressions for contained RestrictInfos too.
+		 */
+		if (new_rinfo->orclause)
+			new_rinfo->orclause = (Expr *) replace_cached_expressions_mutator(
+				(Node *) new_rinfo->orclause, context);
+		else
+			new_rinfo->clause = (Expr *) replace_cached_expressions_mutator(
+				(Node *) new_rinfo->clause, context);
+
+		/* Do NOT recurse into children */
+		return (Node *) new_rinfo;
+	}
+	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;
+
+		/* 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)
+		{
+			if (!is_cached_or_const(lfirst(indexpr), boundParams))
+			{
+				/* return ArrayRef, which will not be cached */
+				return (Node *) aref;
+			}
+		}
+
+		/* check expressions of lower array indexes */
+		foreach(indexpr, aref->reflowerindexpr)
+		{
+			if (!is_cached_or_const(lfirst(indexpr), boundParams))
+			{
+				/* return ArrayRef, which will not be cached */
+				return (Node *) aref;
+			}
+		}
+
+		/* check expression of array value */
+		if (!is_cached_or_const(aref->refexpr, boundParams))
+		{
+			/* return ArrayRef, which will not be cached */
+			return (Node *) aref;
+		}
+
+		/* check expression of source value */
+		if (!is_cached_or_const(aref->refassgnexpr, boundParams))
+		{
+			/* return ArrayRef, which will not be cached */
+			return (Node *) aref;
+		}
+
+		return get_cached_expr_node((CacheableExpr *) aref, context);
+	}
+	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;
+
+		/* firstly recurse into children */
+		funcexpr = (FuncExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		/* check whether the function returns a set */
+		if (funcexpr->funcretset ||
+			expression_returns_set((Node *) funcexpr->args))
+		{
+			/* return FuncExpr, which will not be cached */
+			return (Node *) funcexpr;
+		}
+
+		/* check function arguments */
+		foreach(arg, funcexpr->args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return FuncExpr, which will not be cached */
+				return (Node *) funcexpr;
+			}
+		}
+
+		/* check that the function is not volatile */
+		if (contain_volatile_functions((Node *) funcexpr))
+		{
+			/* return FuncExpr, which will not be cached */
+			return (Node *) funcexpr;
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) funcexpr, context);
+	}
+	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;
+
+		/* 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);
+
+		/* check whether the operator returns a set */
+		if (opexpr->opretset ||
+			expression_returns_set((Node *) opexpr->args))
+		{
+			/* return OpExpr, which will not be cached */
+			return (Node *) opexpr;
+		}
+
+		/* check operator arguments */
+		foreach(arg, opexpr->args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return OpExpr, which will not be cached */
+				return (Node *) opexpr;
+			}
+		}
+
+		/* check that the operator is not volatile */
+		if (contain_volatile_functions((Node *) opexpr))
+		{
+			/* return OpExpr, which will not be cached */
+			return (Node *) opexpr;
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) opexpr, context);
+	}
+	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;
+
+		/* 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);
+
+		/* check whether the operator returns a set */
+		if (distinctexpr->opretset ||
+			expression_returns_set((Node *) distinctexpr->args))
+		{
+			/* return DistinctExpr, which will not be cached */
+			return (Node *) distinctexpr;
+		}
+
+		/* check operator arguments */
+		foreach(arg, distinctexpr->args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return DistinctExpr, which will not be cached */
+				return (Node *) distinctexpr;
+			}
+		}
+
+		if (contain_volatile_functions((Node *) distinctexpr))
+		{
+			/* return DistinctExpr, which will not be cached */
+			return (Node *) distinctexpr;
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) distinctexpr, context);
+	}
+	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;
+
+		/* 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);
+
+		/* check whether the operator returns a set */
+		if (nullifexpr->opretset ||
+			expression_returns_set((Node *) nullifexpr->args))
+		{
+			/* return NullIfExpr, which will not be cached */
+			return (Node *) nullifexpr;
+		}
+
+		/* check operator arguments */
+		foreach(arg, nullifexpr->args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return NullIfExpr, which will not be cached */
+				return (Node *) nullifexpr;
+			}
+		}
+
+		if (contain_volatile_functions((Node *) nullifexpr))
+		{
+			/* return NullIfExpr, which will not be cached */
+			return (Node *) nullifexpr;
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) nullifexpr, context);
+	}
+	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;
+
+		set_sa_opfuncid(saopexpr);
+
+		/* firstly recurse into children */
+		saopexpr = (ScalarArrayOpExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		/* check operator arguments */
+		foreach(arg, saopexpr->args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return ScalarArrayOpExpr, which will not be cached */
+				return (Node *) saopexpr;
+			}
+		}
+
+		if (contain_volatile_functions((Node *) saopexpr))
+		{
+			/* return ScalarArrayOpExpr, which will not be cached */
+			return (Node *) saopexpr;
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) saopexpr, context);
+	}
+	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;
+
+		/* firstly recurse into children */
+		boolexpr = (BoolExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		/* check the arguments of this expression */
+		foreach(arg, boolexpr->args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return BoolExpr, which will not be cached */
+				return (Node *) boolexpr;
+			}
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) boolexpr, context);
+	}
+	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;
+
+		/* firstly recurse into children */
+		fselect = (FieldSelect *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(fselect->arg, boundParams))
+		{
+			/* return FieldSelect, which will not be cached */
+			return (Node *) fselect;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			return get_cached_expr_node((CacheableExpr *) fselect, context);
+		}
+	}
+	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;
+
+		/* firstly recurse into children */
+		relabel = (RelabelType *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(relabel->arg, boundParams))
+		{
+			/* return RelabelType, which will not be cached */
+			return (Node *) relabel;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			return get_cached_expr_node((CacheableExpr *) relabel, context);
+		}
+	}
+	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;
+
+		/* firstly recurse into children */
+		iocoerce = (CoerceViaIO *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(iocoerce->arg, boundParams) ||
+			contain_volatile_functions((Node *) iocoerce))
+		{
+			/* return CoerceViaIO, which will not be cached */
+			return (Node *) iocoerce;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			return get_cached_expr_node((CacheableExpr *) iocoerce, context);
+		}
+	}
+	else if (IsA(node, ArrayCoerceExpr))
+	{
+		/*
+		 * ArrayCoerceExpr is cached if:
+		 * 1) its element-type coercion expression can be cached,
+		 * 2) its argument is const or cached expression too.
+		 * (it returns its argument with a type coercion so we don't need to
+		 * check if it returns set)
+		 */
+		ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
+		replace_cached_expressions_context new_context;
+		Expr	   *elemexpr;
+
+		/* Firstly recurse into children */
+		acoerce = (ArrayCoerceExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		/* Check arg and fill new context */
+		if (!is_cached_or_const(acoerce->arg, boundParams))
+		{
+			/* Return ArrayCoerceExpr, which will not be cached */
+			return (Node *) acoerce;
+		}
+
+		new_context.root = context->root;
+		new_context.innermost_const_or_cached_casetestexpr = true;
+		new_context.innermost_const_or_cached_coercetodomainvalue =
+			context->innermost_const_or_cached_coercetodomainvalue;
+
+		/*
+		 * Recurse into element-type coercion expression with new context (it
+		 * will be used by CaseTestExpr if it's used here). The changed elemexpr
+		 * is not saved because it must be calculated for each element of the
+		 * array.
+		 */
+		elemexpr = (Expr *) replace_cached_expressions_mutator(
+													(Node *) acoerce->elemexpr,
+													(void *) &new_context);
+
+		/* Check elemexpr */
+		if (!is_cached_or_const(elemexpr, boundParams))
+		{
+			/* Return ArrayCoerceExpr, which will not be cached */
+			return (Node *) acoerce;
+		}
+
+		/* Create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) acoerce, context);
+	}
+	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;
+
+		/* firstly recurse into children */
+		convexpr = (ConvertRowtypeExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(convexpr->arg, boundParams))
+		{
+			/* return ConvertRowtypeExpr, which will not be cached */
+			return (Node *) convexpr;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			return get_cached_expr_node((CacheableExpr *) convexpr, context);
+		}
+	}
+	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;
+		bool		has_nonconst_or_noncached_input = false;
+		bool		const_or_cached_testvalue;
+		replace_cached_expressions_context new_context;
+		ListCell   *cell;
+
+		/*
+		 * 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);
+
+		/* Check arg and fill new context */
+		const_or_cached_testvalue = is_cached_or_const(new_caseexpr->arg,
+														boundParams);
+		has_nonconst_or_noncached_input = !const_or_cached_testvalue;
+
+		new_context.root = context->root;
+		new_context.innermost_const_or_cached_casetestexpr =
+			const_or_cached_testvalue;
+		new_context.innermost_const_or_cached_coercetodomainvalue =
+			context->innermost_const_or_cached_coercetodomainvalue;
+
+		/*
+		 * 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 *) replace_cached_expressions_mutator(
+													(Node *) new_caseexpr->args,
+													(void *) &new_context);
+
+		/* Check args */
+		foreach(cell, new_caseexpr->args)
+		{
+			CaseWhen   *casewhen = lfirst(cell);
+
+			if (!is_cached_or_const(casewhen->expr, boundParams) ||
+				!is_cached_or_const(casewhen->result, boundParams))
+				has_nonconst_or_noncached_input = true;
+		}
+
+		/* Recurse into defresult */
+		new_caseexpr->defresult = (Expr	*) replace_cached_expressions_mutator(
+											(Node *) new_caseexpr->defresult,
+											context);
+
+		/* Check defresult */
+		if (!is_cached_or_const(new_caseexpr->defresult, boundParams))
+			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 */
+			return get_cached_expr_node((CacheableExpr *) new_caseexpr,
+										context);
+		}
+	}
+	else if (IsA(node, CaseTestExpr))
+	{
+		/*
+		 * CaseTestExpr is cached if we got in context that it is in CaseExpr/
+		 * ArrayCoerceExpr and arg of innermost CaseExpr/ArrayCoerceExpr is
+		 * const or cached expression too. (it is a placeholder node for the
+		 * test value of its CaseExpr/source element of its ArrayCoerceExpr 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_const_or_cached_casetestexpr)
+		{
+			/* return CaseTestExpr, which will not be cached */
+			return (Node *) casetest;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			return get_cached_expr_node((CacheableExpr *) casetest, context);
+		}
+	}
+	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;
+
+		/* firstly recurse into children */
+		arrayexpr = (ArrayExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(element, arrayexpr->elements)
+		{
+			if (!is_cached_or_const(lfirst(element), boundParams))
+			{
+				/* return ArrayExpr, which will not be cached */
+				return (Node *) arrayexpr;
+			}
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) arrayexpr, context);
+	}
+	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;
+
+		/* firstly recurse into children */
+		rowexpr = (RowExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(arg, rowexpr->args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return RowExpr, which will not be cached */
+				return (Node *) rowexpr;
+			}
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) rowexpr, context);
+	}
+	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;
+
+		/* firstly recurse into children */
+		rcexpr = (RowCompareExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		/* check the left-hand input arguments */
+		foreach(arg, rcexpr->largs)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return RowCompareExpr, which will not be cached */
+				return (Node *) rcexpr;
+			}
+		}
+
+		/* check the right-hand input arguments */
+		foreach(arg, rcexpr->rargs)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return RowCompareExpr, which will not be cached */
+				return (Node *) rcexpr;
+			}
+		}
+
+		/* check that the pairwise comparison operators are not volatile */
+		if (contain_volatile_functions((Node *) rcexpr))
+		{
+			/* return RowCompareExpr, which will not be cached */
+			return (Node *) rcexpr;
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) rcexpr, context);
+	}
+	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;
+
+		/* firstly recurse into children */
+		coalesce = (CoalesceExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(arg, coalesce->args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return CoalesceExpr, which will not be cached */
+				return (Node *) coalesce;
+			}
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) coalesce, context);
+	}
+	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;
+
+		/* firstly recurse into children */
+		minmaxexpr = (MinMaxExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		foreach(arg, minmaxexpr->args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return MinMaxExpr, which will not be cached */
+				return (Node *) minmaxexpr;
+			}
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) minmaxexpr, context);
+	}
+	else if (IsA(node, SQLValueFunction))
+	{
+		/*
+		 * SQLValueFunction is always cached because all of these functions
+		 * don't return a set and are stable.
+		 */
+		return get_cached_expr_node((CacheableExpr *) node, context);
+	}
+	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;
+
+		/* firstly recurse into children */
+		xexpr = (XmlExpr *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		/* check arguments */
+
+		foreach(arg, xexpr->named_args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return XmlExpr, which will not be cached */
+				return (Node *) xexpr;
+			}
+		}
+
+		foreach(arg, xexpr->args)
+		{
+			if (!is_cached_or_const(lfirst(arg), boundParams))
+			{
+				/* return XmlExpr, which will not be cached */
+				return (Node *) xexpr;
+			}
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) xexpr, context);
+	}
+	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;
+
+		/* firstly recurse into children */
+		nulltest = (NullTest *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(nulltest->arg, boundParams))
+		{
+			/* return NullTest, which will not be cached */
+			return (Node *) nulltest;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			return get_cached_expr_node((CacheableExpr *) nulltest, context);
+		}
+	}
+	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;
+
+		/* firstly recurse into children */
+		btest = (BooleanTest *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		if (!is_cached_or_const(btest->arg, boundParams))
+		{
+			/* return BooleanTest, which will not be cached */
+			return (Node *) btest;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			return get_cached_expr_node((CacheableExpr *) btest, context);
+		}
+	}
+	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;
+		replace_cached_expressions_context new_context;
+		DomainConstraintRef *constraint_ref;
+		List	   *constraints;
+		ListCell   *cell;
+
+		/* firstly recurse into children */
+		ctest = (CoerceToDomain *) expression_tree_mutator(
+											node,
+											replace_cached_expressions_mutator,
+											(void *) context);
+
+		/* check arg and fill new context */
+		if (!is_cached_or_const(ctest->arg, boundParams))
+		{
+			/* return CoerceToDomain, which will not be cached */
+			return (Node *) ctest;
+		}
+
+		new_context.root = context->root;
+		new_context.innermost_const_or_cached_casetestexpr =
+			context->innermost_const_or_cached_casetestexpr;
+		new_context.innermost_const_or_cached_coercetodomainvalue = true;
+
+		/* 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 (!is_cached_or_const(check_expr, boundParams))
+					{
+						/* return CoerceToDomain, which will not be cached */
+						return (Node *) ctest;
+					}
+					break;
+				default:
+					elog(ERROR, "unrecognized constraint type: %d",
+						 (int) con->constrainttype);
+					break;
+			}
+		}
+
+		/* create and return CachedExpr */
+		return get_cached_expr_node((CacheableExpr *) ctest, context);
+	}
+	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_const_or_cached_coercetodomainvalue)
+		{
+			/* return CoerceToDomainValue, which will not be cached */
+			return (Node *) domval;
+		}
+		else
+		{
+			/* create and return CachedExpr */
+			return get_cached_expr_node((CacheableExpr *) domval, context);
+		}
+	}
+
+	/* otherwise recurse into children */
+	return expression_tree_mutator(node, replace_cached_expressions_mutator,
+								   (void *) context);
+}
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 45d82da..cb66c5d 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -918,6 +918,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->hasRecursion = false;
 	subroot->wt_param_id = -1;
 	subroot->non_recursive_path = NULL;
+	subroot->hasCachedExpr = false;
 
 	/* No CTEs to worry about */
 	Assert(subquery->cteList == NIL);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 89f27ce..5c61a55 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -62,7 +62,7 @@ typedef struct
 
 typedef struct
 {
-	ParamListInfo boundParams;
+	ParamListInfoCommon boundParams;
 	PlannerInfo *root;
 	List	   *active_fns;
 	Node	   *case_val;
@@ -983,6 +983,11 @@ contain_volatile_functions_walker(Node *node, void *context)
 		/* NextValueExpr is volatile */
 		return true;
 	}
+	else if (IsA(node, CachedExpr))
+	{
+		/* CachedExpr is not volatile */
+		return false;
+	}
 
 	/*
 	 * See notes in contain_mutable_functions_walker about why we treat
@@ -2547,7 +2552,13 @@ eval_const_expressions_mutator(Node *node,
 		case T_Param:
 			{
 				Param	   *param = (Param *) node;
-				ParamListInfo paramLI = context->boundParams;
+				ParamListInfo paramLI;
+
+				if (context->boundParams != NULL &&
+					context->boundParams->type == PARAM_LIST_INFO_DATA)
+					paramLI = (ParamListInfo) context->boundParams;
+				else
+					paramLI = NULL;
 
 				/* Look to see if we've been given a value for this Param */
 				if (param->paramkind == PARAM_EXTERN &&
@@ -2659,6 +2670,32 @@ eval_const_expressions_mutator(Node *node,
 
 				return (Node *) newexpr;
 			}
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+				CacheableExpr *new_subexpr = (CacheableExpr *)
+					eval_const_expressions_mutator((Node *) cachedexpr->subexpr,
+												   context);
+				CachedExpr *new_cachedexpr;
+
+				if (IsA(new_subexpr, Const))
+				{
+					/* successfully simplified it */
+					return (Node *) new_subexpr;
+				}
+				else
+				{
+					/*
+					 * The expression cannot be simplified any further, so build
+					 * and return a replacement CachedExpr node using the
+					 * possibly-simplified arguments of subexpression.
+					 */
+					new_cachedexpr = makeNode(CachedExpr);
+					new_cachedexpr->subexpr = new_subexpr;
+
+					return (Node *) new_cachedexpr;
+				}
+			}
 		case T_FuncExpr:
 			{
 				FuncExpr   *expr = (FuncExpr *) node;
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 6a9f1b0..d87ee22 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -1111,6 +1111,7 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
 		root->parse = qry;
 		root->planner_cxt = CurrentMemoryContext;
 		root->hasJoinRTEs = true;
+		root->hasCachedExpr = false;
 
 		groupClauses = (List *) flatten_join_alias_vars(root,
 														(Node *) groupClauses);
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index ddc3ec8..babcdea 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -787,7 +787,8 @@ pg_rewrite_query(Query *query)
  * This is a thin wrapper around planner() and takes the same parameters.
  */
 PlannedStmt *
-pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
+pg_plan_query(Query *querytree, int cursorOptions,
+			  ParamListInfoCommon boundParams)
 {
 	PlannedStmt *plan;
 
@@ -848,7 +849,8 @@ pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
  * The result is a list of PlannedStmt nodes.
  */
 List *
-pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams)
+pg_plan_queries(List *querytrees, int cursorOptions,
+				ParamListInfoCommon boundParams)
 {
 	List	   *stmt_list = NIL;
 	ListCell   *query_list;
@@ -867,6 +869,7 @@ pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams)
 			stmt->utilityStmt = query->utilityStmt;
 			stmt->stmt_location = query->stmt_location;
 			stmt->stmt_len = query->stmt_len;
+			stmt->hasCachedExpr = false;
 		}
 		else
 		{
@@ -1644,6 +1647,7 @@ exec_bind_message(StringInfo input_message)
 		params = (ParamListInfo) palloc(offsetof(ParamListInfoData, params) +
 										numParams * sizeof(ParamExternData));
 		/* we have static list of params, so no hooks needed */
+		params->common.type = PARAM_LIST_INFO_DATA;
 		params->paramFetch = NULL;
 		params->paramFetchArg = NULL;
 		params->paramCompile = NULL;
@@ -1794,7 +1798,8 @@ exec_bind_message(StringInfo input_message)
 	 * will be generated in MessageContext.  The plan refcount will be
 	 * assigned to the Portal, so it will be released at portal destruction.
 	 */
-	cplan = GetCachedPlan(psrc, params, false, NULL);
+	cplan = GetCachedPlan(psrc, (ParamListInfoCommon) params, false, NULL,
+						  false);
 
 	/*
 	 * Now we can define the portal.
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ec98a61..0dcc300 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1068,6 +1068,7 @@ ProcessUtilitySlow(ParseState *pstate,
 							wrapper->utilityStmt = stmt;
 							wrapper->stmt_location = pstmt->stmt_location;
 							wrapper->stmt_len = pstmt->stmt_len;
+							wrapper->hasCachedExpr = false;
 
 							ProcessUtility(wrapper,
 										   queryString,
@@ -1148,6 +1149,7 @@ ProcessUtilitySlow(ParseState *pstate,
 								wrapper->utilityStmt = stmt;
 								wrapper->stmt_location = pstmt->stmt_location;
 								wrapper->stmt_len = pstmt->stmt_len;
+								wrapper->hasCachedExpr = false;
 								ProcessUtility(wrapper,
 											   queryString,
 											   PROCESS_UTILITY_SUBCOMMAND,
diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c
index 1df554e..165187b 100644
--- a/src/backend/utils/adt/domains.c
+++ b/src/backend/utils/adt/domains.c
@@ -138,7 +138,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)
 		{
@@ -178,7 +179,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/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9cdbb06..5042d15 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7661,6 +7661,11 @@ get_rule_expr(Node *node, deparse_context *context,
 			get_windowfunc_expr((WindowFunc *) node, context);
 			break;
 
+		case T_CachedExpr:
+			get_rule_expr((Node *) ((CachedExpr *) node)->subexpr, context,
+						  showimplicit);
+			break;
+
 		case T_ArrayRef:
 			{
 				ArrayRef   *aref = (ArrayRef *) node;
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 8d7d8e0..3597c1e 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -90,11 +90,12 @@ static CachedPlanSource *first_saved_plan = NULL;
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
 static List *RevalidateCachedQuery(CachedPlanSource *plansource,
 					  QueryEnvironment *queryEnv);
-static bool CheckCachedPlan(CachedPlanSource *plansource);
+static bool CheckCachedPlan(CachedPlanSource *plansource,
+							ParamListInfoPrecalculation boundParams);
 static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
-				ParamListInfo boundParams, QueryEnvironment *queryEnv);
+				ParamListInfoCommon boundParams, QueryEnvironment *queryEnv);
 static bool choose_custom_plan(CachedPlanSource *plansource,
-				   ParamListInfo boundParams);
+				   ParamListInfoCommon boundParams);
 static double cached_plan_cost(CachedPlan *plan, bool include_planner);
 static Query *QueryListGetPrimaryStmt(List *stmts);
 static void AcquireExecutorLocks(List *stmt_list, bool acquire);
@@ -785,6 +786,65 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
 }
 
 /*
+ * CheckNotConst: check if the bound params are not constants.
+ */
+static bool
+CheckNotConst(ParamListInfoPrecalculation boundParams, int start_index)
+{
+	int			index;
+
+	if (boundParams == NULL)
+		return true;
+
+	if (start_index < 0)
+		start_index = 0;
+
+	for (index = start_index; index < boundParams->numParams; ++index)
+	{
+		if (boundParams->isConstParam[index])
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * CheckBoundParams: check if bound params are compatible in the generic plan.
+ */
+static bool
+CheckBoundParams(ParamListInfoPrecalculation firstBoundParams,
+				 ParamListInfoPrecalculation secondBoundParams)
+{
+	int			numCommonParams,
+				index;
+
+	if (firstBoundParams == NULL || secondBoundParams == NULL)
+		numCommonParams = 0;
+	else if (firstBoundParams->numParams <= secondBoundParams->numParams)
+		numCommonParams = firstBoundParams->numParams;
+	else
+		numCommonParams = secondBoundParams->numParams;
+
+	/*
+	 * Check that the common parameters are equal (both are constants or both
+	 * are not).
+	 */
+	for (index = 0; index < numCommonParams; ++index)
+	{
+		if (firstBoundParams->isConstParam[index] !=
+			secondBoundParams->isConstParam[index])
+			return false;
+	}
+
+	/*
+	 * Check that the other parameters are not constants, so they have not
+	 * previously been precalculated and will not be precalculated.
+	 */
+	return (CheckNotConst(firstBoundParams, numCommonParams) &&
+			CheckNotConst(secondBoundParams, numCommonParams));
+}
+
+/*
  * CheckCachedPlan: see if the CachedPlanSource's generic plan is valid.
  *
  * Caller must have already called RevalidateCachedQuery to verify that the
@@ -794,7 +854,8 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
  * (We must do this for the "true" result to be race-condition-free.)
  */
 static bool
-CheckCachedPlan(CachedPlanSource *plansource)
+CheckCachedPlan(CachedPlanSource *plansource,
+				ParamListInfoPrecalculation boundParams)
 {
 	CachedPlan *plan = plansource->gplan;
 
@@ -845,8 +906,10 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		 */
 		if (plan->is_valid)
 		{
-			/* Successfully revalidated and locked the query. */
-			return true;
+			/*
+			 * Successfully revalidated and locked the query. Check boundParams.
+			 */
+			return CheckBoundParams(plan->boundParams, boundParams);
 		}
 
 		/* Oops, the race case happened.  Release useless locks. */
@@ -879,7 +942,7 @@ CheckCachedPlan(CachedPlanSource *plansource)
  */
 static CachedPlan *
 BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
-				ParamListInfo boundParams, QueryEnvironment *queryEnv)
+				ParamListInfoCommon boundParams, QueryEnvironment *queryEnv)
 {
 	CachedPlan *plan;
 	List	   *plist;
@@ -1002,6 +1065,35 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_saved = false;
 	plan->is_valid = true;
 
+	/*
+	 * Specify the precalculation parameters. If the plan is not one-shot, make
+	 * a deep copy.
+	 */
+	if (boundParams != NULL &&
+		boundParams->type == PARAM_LIST_INFO_PRECALCULATION_DATA)
+	{
+		ParamListInfoPrecalculation params =
+			(ParamListInfoPrecalculation) boundParams;
+
+		if (!plansource->is_oneshot)
+		{
+			plan->boundParams = (ParamListInfoPrecalculation) palloc(
+				offsetof(ParamListInfoPrecalculationData, isConstParam) +
+				params->numParams * sizeof(bool));
+
+			memcpy(plan->boundParams, params,
+				   sizeof(ParamListInfoPrecalculationData));
+		}
+		else
+		{
+			plan->boundParams = params;
+		}
+	}
+	else
+	{
+		plan->boundParams = NULL;
+	}
+
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
 
@@ -1016,7 +1108,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
  * This defines the policy followed by GetCachedPlan.
  */
 static bool
-choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
+choose_custom_plan(CachedPlanSource *plansource,
+				   ParamListInfoCommon boundParams)
 {
 	double		avg_custom_cost;
 
@@ -1025,7 +1118,8 @@ choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
 		return true;
 
 	/* Otherwise, never any point in a custom plan if there's no parameters */
-	if (boundParams == NULL)
+	if (boundParams == NULL ||
+		boundParams->type != PARAM_LIST_INFO_DATA)
 		return false;
 	/* ... nor for transaction control statements */
 	if (IsTransactionStmtPlan(plansource))
@@ -1114,6 +1208,50 @@ cached_plan_cost(CachedPlan *plan, bool include_planner)
 }
 
 /*
+ * GetPrecalculationData: get ParamListInfoPrecalculationData from the source.
+ */
+static ParamListInfoPrecalculation
+GetPrecalculationData(ParamListInfoCommon boundParams)
+{
+	ParamListInfoPrecalculation result;
+	ParamListInfoDataType boundParamsType;
+
+	if (boundParams == NULL)
+		return NULL;
+
+	boundParamsType = boundParams->type;
+
+	switch (boundParamsType)
+	{
+		case PARAM_LIST_INFO_DATA:
+			{
+				ParamListInfo params = (ParamListInfo) boundParams;
+				int 		index;
+
+				result = (ParamListInfoPrecalculation) palloc(
+					offsetof(ParamListInfoPrecalculationData, isConstParam) +
+					params->numParams * sizeof(bool));
+
+				result->common.type = PARAM_LIST_INFO_PRECALCULATION_DATA;
+				result->numParams = params->numParams;
+				for (index = 0; index < params->numParams; ++index)
+					result->isConstParam[index] =
+						params->params[index].pflags & PARAM_FLAG_CONST;
+			}
+			break;
+		case PARAM_LIST_INFO_PRECALCULATION_DATA:
+			result = (ParamListInfoPrecalculation) boundParams;
+			break;
+		default:
+			elog(ERROR, "unrecognized ParamListInfoData type: %d",
+				 (int) boundParamsType);
+			break;
+	}
+
+	return result;
+}
+
+/*
  * GetCachedPlan: get a cached plan from a CachedPlanSource.
  *
  * This function hides the logic that decides whether to use a generic
@@ -1130,14 +1268,22 @@ cached_plan_cost(CachedPlan *plan, bool include_planner)
  *
  * Note: if any replanning activity is required, the caller's memory context
  * is used for that work.
+ *
+ * Note: set genericPlanPrecalculateConstBoundParams to true only if you are
+ * sure that the bound parameters will remain (non)constant quite often for the
+ * next calls to this function.  Otherwise the generic plan will be rebuilt each
+ * time when there's a bound parameter that was constant for the previous
+ * generic plan and is not constant now or vice versa.
  */
 CachedPlan *
-GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
-			  bool useResOwner, QueryEnvironment *queryEnv)
+GetCachedPlan(CachedPlanSource *plansource, ParamListInfoCommon boundParams,
+			  bool useResOwner, QueryEnvironment *queryEnv,
+			  bool genericPlanPrecalculateConstBoundParams)
 {
 	CachedPlan *plan = NULL;
 	List	   *qlist;
 	bool		customplan;
+	ParamListInfoPrecalculation genericPlanBoundParams;
 
 	/* Assert caller is doing things in a sane order */
 	Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
@@ -1154,7 +1300,13 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	if (!customplan)
 	{
-		if (CheckCachedPlan(plansource))
+		/* set the parameters for the generic plan */
+		if (genericPlanPrecalculateConstBoundParams)
+			genericPlanBoundParams = GetPrecalculationData(boundParams);
+		else
+			genericPlanBoundParams = NULL;
+
+		if (CheckCachedPlan(plansource, genericPlanBoundParams))
 		{
 			/* We want a generic plan, and we already have a valid one */
 			plan = plansource->gplan;
@@ -1163,7 +1315,9 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 		else
 		{
 			/* Build a new generic plan */
-			plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv);
+			plan = BuildCachedPlan(plansource, qlist,
+								   (ParamListInfoCommon) genericPlanBoundParams,
+								   queryEnv);
 			/* Just make real sure plansource->gplan is clear */
 			ReleaseGenericPlan(plansource);
 			/* Link the new generic plan into the plansource */
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index cf22306..6c9ce77 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -946,7 +946,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)
@@ -985,11 +986,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);
 
@@ -1006,7 +1010,7 @@ load_domaintype_info(TypeCacheEntry *typentry)
 				ccons = (DomainConstraintState **)
 					repalloc(ccons, cconslen * sizeof(DomainConstraintState *));
 			}
-			ccons[nccons++] = r;
+			ccons[nccons++] = r_state;
 		}
 
 		systable_endscan(scan);
@@ -1043,7 +1047,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)
@@ -1063,15 +1068,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);
 	}
@@ -1100,7 +1107,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);
 }
 
 /*
@@ -1150,16 +1157,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);
@@ -1278,6 +1290,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/executor/execExpr.h b/src/include/executor/execExpr.h
index 117fc89..83f9716 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -230,6 +230,16 @@ typedef enum ExprEvalOp
 	EEOP_AGG_ORDERED_TRANS_DATUM,
 	EEOP_AGG_ORDERED_TRANS_TUPLE,
 
+	/*
+	 * Evaluate CachedExpr.  EEOP_CACHEDEXPR_IF_CACHED is used before
+	 * subexpression evaluation (if subexpression was evaluated use cached value
+	 * and jump to next state or get prepared to subexpression evaluation
+	 * otherwise).  EEOP_CACHEDEXPR_SUBEXPR_END is used after subexpression
+	 * evaluation for caching its result.
+	 */
+	EEOP_CACHEDEXPR_IF_CACHED,
+	EEOP_CACHEDEXPR_SUBEXPR_END,
+
 	/* non-existent operation, used e.g. to check array lengths */
 	EEOP_LAST
 } ExprEvalOp;
@@ -634,6 +644,13 @@ typedef struct ExprEvalStep
 			int			transno;
 			int			setoff;
 		}			agg_trans;
+
+		/* for EEOP_CACHEDEXPR_* */
+		struct
+		{
+			/* steps for evaluation the same CachedExpr have the same state */
+			struct CachedExprState *state;
+		}			cachedexpr;
 	}			d;
 } ExprEvalStep;
 
@@ -674,6 +691,20 @@ typedef struct ArrayRefState
 } ArrayRefState;
 
 
+/*
+ * Non-inline data for EEOP_CACHEDEXPR_* operations (steps for evaluation the
+ * same CachedExpr have the same state).
+ */
+typedef struct CachedExprState
+{
+	bool		isExecuted;
+	bool		resnull;
+	Datum		resvalue;
+	Oid 		restypid;		/* for copying resvalue of subexpression */
+	int			jumpdone;		/* jump here if result determined */
+} CachedExprState;
+
+
 /* functions in execExpr.c */
 extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4bb5cb1..a9292aa 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -803,25 +803,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 2eb3d6d..8d6872f 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/params.h b/src/include/nodes/params.h
index 04b03c7..622d7ce 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -96,6 +96,8 @@ typedef struct ParamExternData
 } ParamExternData;
 
 typedef struct ParamListInfoData *ParamListInfo;
+typedef struct ParamListInfoCommonData *ParamListInfoCommon;
+typedef struct ParamListInfoPrecalculationData *ParamListInfoPrecalculation;
 
 typedef ParamExternData *(*ParamFetchHook) (ParamListInfo params,
 											int paramid, bool speculative,
@@ -107,8 +109,20 @@ typedef void (*ParamCompileHook) (ParamListInfo params, struct Param *param,
 
 typedef void (*ParserSetupHook) (struct ParseState *pstate, void *arg);
 
+typedef enum ParamListInfoDataType
+{
+	PARAM_LIST_INFO_DATA,		/* for ParamListInfoData */
+	PARAM_LIST_INFO_PRECALCULATION_DATA	/* for ParamListInfoPrecalculationData */
+}	ParamListInfoDataType;
+
+typedef struct ParamListInfoCommonData
+{
+	ParamListInfoDataType type;
+}	ParamListInfoCommonData;
+
 typedef struct ParamListInfoData
 {
+	ParamListInfoCommonData common;
 	ParamFetchHook paramFetch;	/* parameter fetch hook */
 	void	   *paramFetchArg;
 	ParamCompileHook paramCompile;	/* parameter compile hook */
@@ -124,6 +138,13 @@ typedef struct ParamListInfoData
 	ParamExternData params[FLEXIBLE_ARRAY_MEMBER];
 }			ParamListInfoData;
 
+typedef struct ParamListInfoPrecalculationData
+{
+	ParamListInfoCommonData common;
+	int			numParams;		/* number of elements in isConstParam array */
+	bool		isConstParam[FLEXIBLE_ARRAY_MEMBER];
+}	ParamListInfoPrecalculationData;
+
 
 /* ----------------
  *	  ParamExecData
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 74e9fb5..dd78559 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -96,6 +96,8 @@ 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" */
+
+	bool		hasCachedExpr;	/* true if any expressions are cached */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4b0d7..e784356 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
@@ -790,7 +822,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) */
@@ -810,7 +842,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 */
@@ -834,7 +866,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) */
@@ -859,7 +891,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 */
@@ -906,7 +938,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 */
@@ -936,7 +968,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 */
@@ -952,7 +984,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 */
@@ -986,7 +1018,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 */
 
@@ -1034,7 +1066,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 */
@@ -1048,7 +1080,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 */
@@ -1066,7 +1098,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 */
@@ -1107,7 +1139,7 @@ typedef enum SQLValueFunctionOp
 
 typedef struct SQLValueFunction
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	SQLValueFunctionOp op;		/* which function this is */
 	Oid			type;			/* result type/typmod */
 	int32		typmod;
@@ -1145,7 +1177,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 */
@@ -1183,7 +1215,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 */
@@ -1206,7 +1238,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 */
@@ -1223,7 +1255,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) */
@@ -1233,6 +1265,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.
@@ -1243,7 +1293,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/nodes/relation.h b/src/include/nodes/relation.h
index 71689b8..32ecc30 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -93,7 +93,7 @@ typedef struct PlannerGlobal
 {
 	NodeTag		type;
 
-	ParamListInfo boundParams;	/* Param values provided to planner() */
+	ParamListInfoCommon boundParams;	/* Param values provided to planner() */
 
 	List	   *subplans;		/* Plans for SubPlan nodes */
 
@@ -317,6 +317,8 @@ typedef struct PlannerInfo
 
 	/* optional private data for join_search_hook, e.g., GEQO */
 	void	   *join_search_private;
+
+	bool		hasCachedExpr;	/* true if any expressions are cached */
 } PlannerInfo;
 
 
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 997b91f..1138d13 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -21,7 +21,7 @@
 /* Hook for plugins to get control in planner() */
 typedef PlannedStmt *(*planner_hook_type) (Query *parse,
 										   int cursorOptions,
-										   ParamListInfo boundParams);
+										   ParamListInfoCommon boundParams);
 extern PGDLLIMPORT planner_hook_type planner_hook;
 
 /* Hook for plugins to get control when grouping_planner() plans upper rels */
@@ -33,9 +33,9 @@ extern PGDLLIMPORT create_upper_paths_hook_type create_upper_paths_hook;
 
 
 extern PlannedStmt *planner(Query *parse, int cursorOptions,
-		ParamListInfo boundParams);
+		ParamListInfoCommon boundParams);
 extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
-				 ParamListInfo boundParams);
+				 ParamListInfoCommon boundParams);
 
 extern PlannerInfo *subquery_planner(PlannerGlobal *glob, Query *parse,
 				 PlannerInfo *parent_root,
@@ -61,4 +61,9 @@ extern List *get_partitioned_child_rels(PlannerInfo *root, Index rti);
 extern List *get_partitioned_child_rels_for_join(PlannerInfo *root,
 									Relids join_relids);
 
+extern PathTarget *replace_pathtarget_cached_expressions(PathTarget *target,
+														 PlannerInfo *root);
+
+extern List *replace_qual_cached_expressions(List *quals, PlannerInfo *root);
+
 #endif							/* PLANNER_H */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index 9fa52e1..688bb36 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -65,8 +65,12 @@ extern void split_pathtarget_at_srfs(PlannerInfo *root,
 						 PathTarget *target, PathTarget *input_target,
 						 List **targets, List **targets_contain_srfs);
 
-/* Convenience macro to get a PathTarget with valid cost/width fields */
+/*
+ * Convenience macro to get a PathTarget with valid cost/width fields and
+ * cached expressions.
+ */
 #define create_pathtarget(root, tlist) \
-	set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
+	set_pathtarget_cost_width(root, replace_pathtarget_cached_expressions( \
+		make_pathtarget_from_tlist(tlist), root))
 
 #endif							/* TLIST_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 63b4e48..4a00a51 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -58,9 +58,9 @@ extern List *pg_analyze_and_rewrite_params(RawStmt *parsetree,
 							  void *parserSetupArg,
 							  QueryEnvironment *queryEnv);
 extern PlannedStmt *pg_plan_query(Query *querytree, int cursorOptions,
-			  ParamListInfo boundParams);
+			  ParamListInfoCommon boundParams);
 extern List *pg_plan_queries(List *querytrees, int cursorOptions,
-				ParamListInfo boundParams);
+				ParamListInfoCommon boundParams);
 
 extern bool check_max_stack_depth(int *newval, void **extra, GucSource source);
 extern void assign_max_stack_depth(int newval, void *extra);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index ab20aa0..1e6a1df 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -138,6 +138,13 @@ typedef struct CachedPlan
 	bool		dependsOnRole;	/* is plan specific to that role? */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
+
+	/*
+	 * Used to check whether the generic plan is valid for the new
+	 * boundParams; NULL for the custom plans.
+	 */
+	ParamListInfoPrecalculation boundParams;
+
 	int			generation;		/* parent's generation number for this plan */
 	int			refcount;		/* count of live references to this struct */
 	MemoryContext context;		/* context containing this CachedPlan */
@@ -177,9 +184,10 @@ extern List *CachedPlanGetTargetList(CachedPlanSource *plansource,
 						QueryEnvironment *queryEnv);
 
 extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
-			  ParamListInfo boundParams,
+			  ParamListInfoCommon boundParams,
 			  bool useResOwner,
-			  QueryEnvironment *queryEnv);
+			  QueryEnvironment *queryEnv,
+			  bool genericPlanPrecalculateConstBoundParams);
 extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
 
 #endif							/* PLANCACHE_H */
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index f25448d..16ab52a 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -167,6 +167,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,
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index d096f24..e0a5748 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3376,6 +3376,7 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
 	/* initialize our ParamListInfo with appropriate hook functions */
 	estate->paramLI = (ParamListInfo)
 		palloc(offsetof(ParamListInfoData, params));
+	estate->paramLI->common.type = PARAM_LIST_INFO_DATA;
 	estate->paramLI->paramFetch = plpgsql_param_fetch;
 	estate->paramLI->paramFetchArg = (void *) estate;
 	estate->paramLI->paramCompile = plpgsql_param_compile;
@@ -6391,6 +6392,7 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
 	Query	   *query;
 	CachedPlan *cplan;
 	MemoryContext oldcontext;
+	ListCell   *cell;
 
 	/*
 	 * Initialize to "not simple".
@@ -6472,6 +6474,22 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
 	/* Can't fail, because we checked for a single CachedPlanSource above */
 	Assert(cplan != NULL);
 
+	/* Check that there're no cached expressions in the plan.
+	 *
+	 * If CachedExpr will not be initialized by ExecInitCachedExpr possibly it
+	 * will use cached value when it shouldn't (for example, snapshot has
+	 * changed).
+	 */
+	foreach(cell, cplan->stmt_list)
+	{
+		if (((PlannedStmt *) cell->data.ptr_value)->hasCachedExpr)
+		{
+			/* Oops, release refcount and fail */
+			ReleaseCachedPlan(cplan, true);
+			return;
+		}
+	}
+
 	/* Share the remaining work with replan code path */
 	exec_save_simple_expr(expr, cplan);
 
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
new file mode 100644
index 0000000..6bb0c90
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -0,0 +1,6645 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+-- Create domains for testing
+CREATE DOMAIN my_integer_no_check AS integer;
+CREATE DOMAIN my_integer_not_null AS integer;
+CREATE DOMAIN my_integer_vlt_check AS integer CHECK (VALUE === 1);
+CREATE DOMAIN my_integer_stl_check AS integer CHECK (VALUE ==== 1);
+CREATE DOMAIN my_integer_imm_check AS integer CHECK (VALUE ===== 1);
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_no_check (
+  my_integer_no_check
+)
+RETURNS my_integer_no_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_no_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_not_null (
+  my_integer_not_null
+)
+RETURNS my_integer_not_null STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_not_null';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_vlt_check (
+  my_integer_vlt_check
+)
+RETURNS my_integer_vlt_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_stl_check (
+  my_integer_stl_check
+)
+RETURNS my_integer_stl_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_imm_check (
+  my_integer_imm_check
+)
+RETURNS my_integer_imm_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_imm_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_vlt_check (
+  my_integer_vlt_check[]
+)
+RETURNS my_integer_vlt_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_stl_check (
+  my_integer_stl_check[]
+)
+RETURNS my_integer_stl_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Functions testing
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Multidimensional ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+-- Array subscripting operations testing
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- FieldSelect expressions testing
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- RelabelType expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- CoerceViaIO expressions testing
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- RowCompareExpr testing
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- SQLValueFunction testing
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_name(current_role) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(current_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(session_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+(4 rows)
+
+SELECT x_stl2_name(current_catalog) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ regression
+ regression
+ regression
+ regression
+(4 rows)
+
+SELECT x_stl2_name(current_schema) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ public
+ public
+ public
+ public
+(4 rows)
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+-- Xml expressions testing
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+NOTICE:  s2 xml
+          x_stl2_xml          
+------------------------------
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+NOTICE:  s2 xml
+          x_stl2_xml          
+------------------------------
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+NOTICE:  s2 xml
+                          x_stl2_xml                          
+--------------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+NOTICE:  s2 text
+                       x_stl2_text                       
+---------------------------------------------------------
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+NOTICE:  s text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- CoerceToDomain expressions testing
+SELECT x_stl2_my_integer_no_check(1::my_integer_no_check) FROM x;
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(1::my_integer_not_null) FROM x;
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_vlt_check(1::my_integer_vlt_check) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+ x_stl2_my_integer_vlt_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(1::my_integer_stl_check) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(1::my_integer_imm_check) FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- Mixed functions and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- Mixed ArrayCoerce and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_my_integer_vlt_check(
+  '{1, 1}'::integer[]::my_integer_vlt_check[]
+)
+FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+ x_stl2_array_my_integer_vlt_check 
+-----------------------------------
+ {1,1}
+ {1,1}
+ {1,1}
+ {1,1}
+(4 rows)
+
+SELECT x_stl2_array_my_integer_stl_check(
+  '{1, 1}'::integer[]::my_integer_stl_check[]
+)
+FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 array_my_integer_stl_check
+ x_stl2_array_my_integer_stl_check 
+-----------------------------------
+ {1,1}
+ {1,1}
+ {1,1}
+ {1,1}
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+NOTICE:  s text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- ROW() expressions with dropped columns testing
+ALTER TABLE wxyz DROP COLUMN z;
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      4
+(1 row)
+
+INSERT INTO x VALUES (5);
+SELECT simple();
+ simple 
+--------
+      5
+(1 row)
+
+ROLLBACK;
+-- Prepared statements testing
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+-- Drop tables and domains for testing
+DROP TABLE x;
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
+DROP FUNCTION x_stl2_my_integer_no_check;
+DROP DOMAIN my_integer_no_check;
+DROP FUNCTION x_stl2_my_integer_not_null;
+DROP DOMAIN my_integer_not_null;
+DROP FUNCTION x_stl2_my_integer_vlt_check;
+DROP FUNCTION x_stl2_array_my_integer_vlt_check;
+DROP DOMAIN my_integer_vlt_check;
+DROP FUNCTION x_stl2_my_integer_stl_check;
+DROP FUNCTION x_stl2_array_my_integer_stl_check;
+DROP DOMAIN my_integer_stl_check;
+DROP FUNCTION x_stl2_my_integer_imm_check;
+DROP DOMAIN my_integer_imm_check;
diff --git a/src/test/regress/expected/precalculate_stable_functions_1.out b/src/test/regress/expected/precalculate_stable_functions_1.out
new file mode 100644
index 0000000..b640bd9
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions_1.out
@@ -0,0 +1,6291 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+-- Create domains for testing
+CREATE DOMAIN my_integer_no_check AS integer;
+CREATE DOMAIN my_integer_not_null AS integer;
+CREATE DOMAIN my_integer_vlt_check AS integer CHECK (VALUE === 1);
+CREATE DOMAIN my_integer_stl_check AS integer CHECK (VALUE ==== 1);
+CREATE DOMAIN my_integer_imm_check AS integer CHECK (VALUE ===== 1);
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_no_check (
+  my_integer_no_check
+)
+RETURNS my_integer_no_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_no_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_not_null (
+  my_integer_not_null
+)
+RETURNS my_integer_not_null STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_not_null';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_vlt_check (
+  my_integer_vlt_check
+)
+RETURNS my_integer_vlt_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_stl_check (
+  my_integer_stl_check
+)
+RETURNS my_integer_stl_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_imm_check (
+  my_integer_imm_check
+)
+RETURNS my_integer_imm_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_imm_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_vlt_check (
+  my_integer_vlt_check[]
+)
+RETURNS my_integer_vlt_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_stl_check (
+  my_integer_stl_check[]
+)
+RETURNS my_integer_stl_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Functions testing
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Multidimensional ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+-- Array subscripting operations testing
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- FieldSelect expressions testing
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- RelabelType expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- CoerceViaIO expressions testing
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- RowCompareExpr testing
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- SQLValueFunction testing
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_name(current_role) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(current_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(session_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+(4 rows)
+
+SELECT x_stl2_name(current_catalog) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ regression
+ regression
+ regression
+ regression
+(4 rows)
+
+SELECT x_stl2_name(current_schema) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ public
+ public
+ public
+ public
+(4 rows)
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+-- Xml expressions testing
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FRO...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   '<?xml version="1.0"?><content>abc</content>',
+          ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   DOCUMENT '<?xml version="1.0"?><book><title>Manual</title>...
+                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+                  ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS D...
+                              ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+-- NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- CoerceToDomain expressions testing
+SELECT x_stl2_my_integer_no_check(1::my_integer_no_check) FROM x;
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(1::my_integer_not_null) FROM x;
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_vlt_check(1::my_integer_vlt_check) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  s2 my_integer_vlt_check
+ x_stl2_my_integer_vlt_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(1::my_integer_stl_check) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(1::my_integer_imm_check) FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- Mixed functions and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- Mixed ArrayCoerce and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_my_integer_vlt_check(
+  '{1, 1}'::integer[]::my_integer_vlt_check[]
+)
+FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 array_my_integer_vlt_check
+ x_stl2_array_my_integer_vlt_check 
+-----------------------------------
+ {1,1}
+ {1,1}
+ {1,1}
+ {1,1}
+(4 rows)
+
+SELECT x_stl2_array_my_integer_stl_check(
+  '{1, 1}'::integer[]::my_integer_stl_check[]
+)
+FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 array_my_integer_stl_check
+ x_stl2_array_my_integer_stl_check 
+-----------------------------------
+ {1,1}
+ {1,1}
+ {1,1}
+ {1,1}
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+-- Mixed functions and NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and CoerceToDomain expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+NOTICE:  v
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+NOTICE:  v
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_no_check
+ x_stl2_my_integer_no_check 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+NOTICE:  s
+NOTICE:  s2 my_integer_not_null
+ x_stl2_my_integer_not_null 
+----------------------------
+                          1
+                          1
+                          1
+                          1
+(4 rows)
+
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  s2 my_integer_stl_check
+ x_stl2_my_integer_stl_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+NOTICE:  s
+NOTICE:  equal integers immutable
+NOTICE:  s2 my_integer_imm_check
+ x_stl2_my_integer_imm_check 
+-----------------------------
+                           1
+                           1
+                           1
+                           1
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- ROW() expressions with dropped columns testing
+ALTER TABLE wxyz DROP COLUMN z;
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      4
+(1 row)
+
+INSERT INTO x VALUES (5);
+SELECT simple();
+ simple 
+--------
+      5
+(1 row)
+
+ROLLBACK;
+-- Prepared statements testing
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+-- Drop tables and domains for testing
+DROP TABLE x;
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
+DROP FUNCTION x_stl2_my_integer_no_check;
+DROP DOMAIN my_integer_no_check;
+DROP FUNCTION x_stl2_my_integer_not_null;
+DROP DOMAIN my_integer_not_null;
+DROP FUNCTION x_stl2_my_integer_vlt_check;
+DROP FUNCTION x_stl2_array_my_integer_vlt_check;
+DROP DOMAIN my_integer_vlt_check;
+DROP FUNCTION x_stl2_my_integer_stl_check;
+DROP FUNCTION x_stl2_array_my_integer_stl_check;
+DROP DOMAIN my_integer_stl_check;
+DROP FUNCTION x_stl2_my_integer_imm_check;
+DROP DOMAIN my_integer_imm_check;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e224977..0e893df 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -116,7 +116,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid c
 # ----------
 # Another group of parallel tests
 # ----------
-test: identity partition_join partition_prune reloptions hash_part
+test: identity partition_join partition_prune reloptions hash_part precalculate_stable_functions
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 9fc5f1a..54470ea 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -184,5 +184,6 @@ test: partition_join
 test: partition_prune
 test: reloptions
 test: hash_part
+test: precalculate_stable_functions
 test: event_trigger
 test: stats
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
new file mode 100644
index 0000000..9437f24
--- /dev/null
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -0,0 +1,2117 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+
+-- Create volatile functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create stable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create immutable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create operators for testing
+
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+-- Create domains for testing
+
+CREATE DOMAIN my_integer_no_check AS integer;
+CREATE DOMAIN my_integer_not_null AS integer;
+CREATE DOMAIN my_integer_vlt_check AS integer CHECK (VALUE === 1);
+CREATE DOMAIN my_integer_stl_check AS integer CHECK (VALUE ==== 1);
+CREATE DOMAIN my_integer_imm_check AS integer CHECK (VALUE ===== 1);
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_no_check (
+  my_integer_no_check
+)
+RETURNS my_integer_no_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_no_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_not_null (
+  my_integer_not_null
+)
+RETURNS my_integer_not_null STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_not_null';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_vlt_check (
+  my_integer_vlt_check
+)
+RETURNS my_integer_vlt_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_stl_check (
+  my_integer_stl_check
+)
+RETURNS my_integer_stl_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer_imm_check (
+  my_integer_imm_check
+)
+RETURNS my_integer_imm_check STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer_imm_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_vlt_check (
+  my_integer_vlt_check[]
+)
+RETURNS my_integer_vlt_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_vlt_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_my_integer_stl_check (
+  my_integer_stl_check[]
+)
+RETURNS my_integer_stl_check[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_my_integer_stl_check';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Functions testing
+
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+SELECT x_stl() FROM x;
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM x WHERE x_stl() < x;
+
+-- JOIN/ON clause testing
+
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+SELECT x_stl2(1) FROM x;
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2(x_stl()) FROM x;
+SELECT x_imm2(x_stl()) FROM x;
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+
+-- Operators testing
+
+SELECT 1 === 2 FROM x; -- should not be precalculated
+SELECT 1 ==== 2 FROM x;
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+SELECT x_stl() ==== 1 FROM x;
+
+-- IS (NOT) DISTINCT FROM expression testing
+
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- Boolean expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+
+-- ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+
+-- Multidimensional ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+
+-- Array subscripting operations testing
+
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+
+-- Mixed functions and array subscripting operations testing
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+
+-- FieldSelect expressions testing
+
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+
+-- ROW() expressions testing
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+
+-- RelabelType expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+
+-- CoerceViaIO expressions testing
+
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+
+-- Mixed functions and CoerceViaIO expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+
+-- ArrayCoerce expressions testing
+
+-- Binary-coercible types:
+
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+
+-- ConvertRowtypeExpr testing
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+
+-- Mixed functions and ConvertRowtypeExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+-- CASE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+
+-- RowCompareExpr testing
+
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+
+-- Mixed functions and RowCompareExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+
+-- COALESCE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+
+-- GREATEST and LEAST functions testing
+
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+
+-- Mixed functions and GREATEST and LEAST functions testing
+
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+
+-- SQLValueFunction testing
+
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+
+SELECT x_stl2_name(current_role) FROM x;
+SELECT x_stl2_name(current_user) FROM x;
+SELECT x_stl2_name(user) FROM x;
+SELECT x_stl2_name(session_user) FROM x;
+SELECT x_stl2_name(current_catalog) FROM x;
+SELECT x_stl2_name(current_schema) FROM x;
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+
+-- Xml expressions testing
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+
+-- NullTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+
+-- BooleanTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+
+-- CoerceToDomain expressions testing
+
+SELECT x_stl2_my_integer_no_check(1::my_integer_no_check) FROM x;
+SELECT x_stl2_my_integer_not_null(1::my_integer_not_null) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_vlt_check(1::my_integer_vlt_check) FROM x;
+
+SELECT x_stl2_my_integer_stl_check(1::my_integer_stl_check) FROM x;
+SELECT x_stl2_my_integer_imm_check(1::my_integer_imm_check) FROM x;
+
+-- Mixed functions and CoerceToDomain expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+
+-- Mixed ArrayCoerce and CoerceToDomain expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_my_integer_vlt_check(
+  '{1, 1}'::integer[]::my_integer_vlt_check[]
+)
+FROM x;
+
+SELECT x_stl2_array_my_integer_stl_check(
+  '{1, 1}'::integer[]::my_integer_stl_check[]
+)
+FROM x;
+
+-- Tracking functions testing
+
+SET track_functions TO 'all';
+
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+SELECT x_stl() FROM x;
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM x WHERE x_stl() < x;
+
+-- JOIN/ON clause testing
+
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+SELECT x_stl2(1) FROM x;
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2(x_stl()) FROM x;
+SELECT x_imm2(x_stl()) FROM x;
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+SELECT 1 ==== 2 FROM x;
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+SELECT x_stl() ==== 1 FROM x;
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- Mixed functions and boolean expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+
+-- Mixed functions and ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+
+-- Mixed functions and array subscripting operations testing
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+
+-- Mixed functions and CoerceViaIO expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+
+-- Mixed functions and ConvertRowtypeExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+-- Mixed functions and CASE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+
+-- Mixed functions and RowCompareExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+
+-- Mixed functions and COALESCE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+
+-- Mixed functions and NullTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+
+-- Mixed functions and BooleanTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+
+-- Mixed functions and CoerceToDomain expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_no_check(x_vlt()::my_integer_no_check) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_not_null(x_vlt()::my_integer_not_null) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_stl_check(x_vlt()::my_integer_stl_check) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer_imm_check(x_vlt()::my_integer_imm_check) FROM x;
+
+SELECT x_stl2_my_integer_no_check(x_stl()::my_integer_no_check) FROM x;
+SELECT x_stl2_my_integer_not_null(x_stl()::my_integer_not_null) FROM x;
+SELECT x_stl2_my_integer_stl_check(x_stl()::my_integer_stl_check) FROM x;
+SELECT x_stl2_my_integer_imm_check(x_stl()::my_integer_imm_check) FROM x;
+
+SET track_functions TO DEFAULT;
+
+-- ROW() expressions with dropped columns testing
+
+ALTER TABLE wxyz DROP COLUMN z;
+
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+
+BEGIN;
+SELECT simple();
+INSERT INTO x VALUES (5);
+SELECT simple();
+ROLLBACK;
+
+-- Prepared statements testing
+
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+
+-- Drop tables and domains for testing
+
+DROP TABLE x;
+
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
+
+DROP FUNCTION x_stl2_my_integer_no_check;
+DROP DOMAIN my_integer_no_check;
+
+DROP FUNCTION x_stl2_my_integer_not_null;
+DROP DOMAIN my_integer_not_null;
+
+DROP FUNCTION x_stl2_my_integer_vlt_check;
+DROP FUNCTION x_stl2_array_my_integer_vlt_check;
+DROP DOMAIN my_integer_vlt_check;
+
+DROP FUNCTION x_stl2_my_integer_stl_check;
+DROP FUNCTION x_stl2_array_my_integer_stl_check;
+DROP DOMAIN my_integer_stl_check;
+
+DROP FUNCTION x_stl2_my_integer_imm_check;
+DROP DOMAIN my_integer_imm_check;
-- 
2.7.4

#20Aleksander Alekseev
a.alekseev@postgrespro.ru
In reply to: Marina Polyakova (#19)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed

I can confirm this code works. However, since this is quite a large patch, I believe we better have a second reviewer or a very attentive committer.

The new status of this patch is: Ready for Committer

#21Tom Lane
tgl@sss.pgh.pa.us
In reply to: Aleksander Alekseev (#20)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Aleksander Alekseev <a.alekseev@postgrespro.ru> writes:

I can confirm this code works. However, since this is quite a large patch, I believe we better have a second reviewer or a very attentive committer.
The new status of this patch is: Ready for Committer

This is indeed quite a large patch, but it seems to me it could become
smaller. After a bit of review:

1. I do not like what you've done with ParamListInfo. The changes around
that are invasive, accounting for a noticeable part of the patch bulk,
and I don't think they're well designed. Having to cast back and forth
between ParamListInfo and ParamListInfoCommon and so on is ugly and prone
to cause (or hide) errors. And I don't really understand why
ParamListInfoPrecalculationData exists at all. Couldn't you have gotten
the same result with far fewer notational changes by defining another
PARAM_FLAG bit in the existing pflags field? (Or alternatively, maybe
the real need here is for another ParamKind value for Param nodes?)

I also dislike this approach because it effectively throws away the
support for "virtual" param arrays that I added in commit 6719b238e:
ParamListInfoPrecalculationData has no support for dynamically determined
parameter properties, which is surely something that somebody will need.
(It's just luck that the patch doesn't break plpgsql today.) I realize
that that's a recent commit and the code I'm complaining about predates
it, but we need to adjust this so that it fits in with the new approach.
See comment block at lines 25ff in params.h.

2. I don't follow the need for the also-rather-invasive changes to domain
constraint data structures. I do see that the patch attempts to make
CoerceToDomain nodes cacheable, which is flat wrong and has to be ripped
out. You *cannot* assume that the planner has access to the same domain
constraints that will apply at runtime.

I've occasionally thought that we should hook domain constraint changes
into the plan invalidation mechanism, which would make it possible for
the planner to assume that the constraints seen at planning time will
apply at execution. Whereupon we could have the planner insert domain
constraint expressions into the plan rather than leaving those to be
collected at query startup by execExpr.c, and then do things like
constant-folding and cacheing CoerceToDomain nodes. But that would be
a rather large and very domain-specific change, and so it would be fit
material for a different patch IMO. I recommend that for now you just
treat CoerceToDomain as an uncacheable expression type and rip all the
domain-related changes out of this patch.

3. I think you should also try hard to get rid of the need for
PlannedStmt.hasCachedExpr. AFAICS there's only one place that is
using that flag, which is exec_simple_check_plan, and I have to
think there are better ways we could deal with that. In particular,
I don't understand why you haven't simply set up plpgsql parameter
references to be noncacheable. Or maybe what we'd better do is
disable CacheExpr insertions into potentially-simple plans in the
first place. As you have it here, it's possible for recompilation
of an expression to result in a change in whether it should be deemed
simple or not, which will break things (cf commit 00418c612).

4. I don't like the way that you've inserted
"replace_qual_cached_expressions" and
"replace_pathtarget_cached_expressions" calls into seemingly random places
in the planner. Why isn't that being done uniformly during expression
preprocessing? There's no apparent structure to where you've put these
calls, and so they seem really vulnerable to errors of omission. Also,
if this were done in expression preprocessing, there'd be a chance of
combining it with some existing pass over expression trees instead of
having to do a separate (and expensive) expression tree mutation.
I can't help suspecting that eval_const_expressions could take this on
as an additional responsibility with a lot less than a thousand new lines
of code.

5. BTW, cost_eval_cacheable_expr seems like useless restructuring as well.
Why aren't you just recursively applying the regular costing function?

If you did all of the above it would result in a pretty significant
reduction of the number of places touched by the patch, which would make
it easier to see what's going on. Then we could start to discuss, for
instance, what does the "isConstParam" flag actually *mean* and why
is it different from PARAM_FLAG_CONST? And what in the world is
CheckBoundParams about? The internal documentation in this patch
isn't quite nonexistent, but it's well short of being in a
committable state IMO.

regards, tom lane

#22Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#21)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

[ I'm sending this comment separately because I think it's an issue
Andres might take an interest in. ]

Marina Polyakova <m.polyakova@postgrespro.ru> writes:

[ v7-0001-Precalculate-stable-and-immutable-functions.patch ]

Another thing that's bothering me is that the execution semantics
you're proposing for CachedExpr seem rather inflexible. AFAICS, once a
CachedExpr has run once, it will hang on to the result value and keep
returning that for the entire lifespan of the compiled expression.
We already noted that that breaks plpgsql's "simple expression"
logic, and it seems inevitable to me that it will be an issue for
other places as well. I think it'd be a better design if we had some
provision for resetting the cached values, short of recompiling the
expression from scratch.

One way that occurs to me to do this is to replace the simple boolean
isExecuted flags with a generation counter, and add a master generation
counter to ExprState. The rule for executing CachedExpr would be "if my
generation counter is different from the ExprState's counter, then
evaluate the subexpression and copy the ExprState's counter into mine".
Then the procedure for forcing recalculation of cached values is just to
increment the ExprState's counter. There are other ways one could imagine
doing this --- for instance, I initially thought of keeping the master
counter in the ExprContext being used to run the expression. But you need
some way to remember what counter value was used last with a particular
expression, so probably keeping it in ExprState is better.

Or we could just brute-force it by providing a function that runs through
a compiled expression step list and resets the isExecuted flag for each
EEOP_CACHEDEXPR_IF_CACHED step it finds. A slightly less brute-force
way is to link those steps together in a list, so that the function
doesn't have to visit irrelevant steps. If the reset function were seldom
used then the extra cycles for this wouldn't be very expensive. But I'm
not sure it will be seldom used --- it seems like plpgsql simple
expressions will be doing this every time --- so I think the counter
approach might be a better idea.

I'm curious to know whether Andres has some other ideas, or whether he
feels this is all a big wart on the compiled-expression concept. I don't
think there are any existing cases where we keep any meaningful state
across executions of a compiled-expression data structure; maybe that's
a bad idea in itself.

regards, tom lane

#23Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Tom Lane (#22)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Thank you so much for your comments!! I'll answer a bit later because
now I'm trying to find a test for int128 on Solaris 10.. [1]/messages/by-id/18209.1516059711@sss.pgh.pa.us

On 17-01-2018 1:05, Tom Lane wrote:

[ I'm sending this comment separately because I think it's an issue
Andres might take an interest in. ]

Marina Polyakova <m.polyakova@postgrespro.ru> writes:

[ v7-0001-Precalculate-stable-and-immutable-functions.patch ]

Another thing that's bothering me is that the execution semantics
you're proposing for CachedExpr seem rather inflexible. AFAICS, once a
CachedExpr has run once, it will hang on to the result value and keep
returning that for the entire lifespan of the compiled expression.
We already noted that that breaks plpgsql's "simple expression"
logic, and it seems inevitable to me that it will be an issue for
other places as well. I think it'd be a better design if we had some
provision for resetting the cached values, short of recompiling the
expression from scratch.

One way that occurs to me to do this is to replace the simple boolean
isExecuted flags with a generation counter, and add a master generation
counter to ExprState. The rule for executing CachedExpr would be "if
my
generation counter is different from the ExprState's counter, then
evaluate the subexpression and copy the ExprState's counter into mine".
Then the procedure for forcing recalculation of cached values is just
to
increment the ExprState's counter. There are other ways one could
imagine
doing this --- for instance, I initially thought of keeping the master
counter in the ExprContext being used to run the expression. But you
need
some way to remember what counter value was used last with a particular
expression, so probably keeping it in ExprState is better.

Or we could just brute-force it by providing a function that runs
through
a compiled expression step list and resets the isExecuted flag for each
EEOP_CACHEDEXPR_IF_CACHED step it finds. A slightly less brute-force
way is to link those steps together in a list, so that the function
doesn't have to visit irrelevant steps. If the reset function were
seldom
used then the extra cycles for this wouldn't be very expensive. But
I'm
not sure it will be seldom used --- it seems like plpgsql simple
expressions will be doing this every time --- so I think the counter
approach might be a better idea.

I'm curious to know whether Andres has some other ideas, or whether he
feels this is all a big wart on the compiled-expression concept. I
don't
think there are any existing cases where we keep any meaningful state
across executions of a compiled-expression data structure; maybe that's
a bad idea in itself.

regards, tom lane

[1]: /messages/by-id/18209.1516059711@sss.pgh.pa.us
/messages/by-id/18209.1516059711@sss.pgh.pa.us

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#24Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Tom Lane (#21)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

As I said, thank you so much for your comments!!

On 17-01-2018 0:30, Tom Lane wrote:

...
This is indeed quite a large patch, but it seems to me it could become
smaller. After a bit of review:

1. I do not like what you've done with ParamListInfo. The changes
around
that are invasive, accounting for a noticeable part of the patch bulk,
and I don't think they're well designed. Having to cast back and forth
between ParamListInfo and ParamListInfoCommon and so on is ugly and
prone
to cause (or hide) errors. And I don't really understand why
ParamListInfoPrecalculationData exists at all. Couldn't you have gotten
the same result with far fewer notational changes by defining another
PARAM_FLAG bit in the existing pflags field? (Or alternatively, maybe
the real need here is for another ParamKind value for Param nodes?)

I also dislike this approach because it effectively throws away the
support for "virtual" param arrays that I added in commit 6719b238e:
ParamListInfoPrecalculationData has no support for dynamically
determined
parameter properties, which is surely something that somebody will
need.
(It's just luck that the patch doesn't break plpgsql today.) I realize
that that's a recent commit and the code I'm complaining about predates
it, but we need to adjust this so that it fits in with the new
approach.
See comment block at lines 25ff in params.h.

I'll try to use ParamListInfoData for generic plans (= to get cached
expressions for params of prepared statements where possible) without
changing its infrastructure.

2. I don't follow the need for the also-rather-invasive changes to
domain
constraint data structures. I do see that the patch attempts to make
CoerceToDomain nodes cacheable, which is flat wrong and has to be
ripped
out. You *cannot* assume that the planner has access to the same domain
constraints that will apply at runtime.

I'm sorry, I did not know about this :-[

I've occasionally thought that we should hook domain constraint changes
into the plan invalidation mechanism, which would make it possible for
the planner to assume that the constraints seen at planning time will
apply at execution. Whereupon we could have the planner insert domain
constraint expressions into the plan rather than leaving those to be
collected at query startup by execExpr.c, and then do things like
constant-folding and cacheing CoerceToDomain nodes. But that would be
a rather large and very domain-specific change, and so it would be fit
material for a different patch IMO. I recommend that for now you just
treat CoerceToDomain as an uncacheable expression type and rip all the
domain-related changes out of this patch.

I'll fix this.

3. I think you should also try hard to get rid of the need for
PlannedStmt.hasCachedExpr. AFAICS there's only one place that is
using that flag, which is exec_simple_check_plan, and I have to
think there are better ways we could deal with that. In particular,
I don't understand why you haven't simply set up plpgsql parameter
references to be noncacheable. Or maybe what we'd better do is
disable CacheExpr insertions into potentially-simple plans in the
first place. As you have it here, it's possible for recompilation
of an expression to result in a change in whether it should be deemed
simple or not, which will break things (cf commit 00418c612).

I'm sorry, I'll fix the use of parameters in this case. And I'll think
how to get rid of the need for PlannedStmt.hasCachedExpr when there're
possible cached expressions without parameters.

4. I don't like the way that you've inserted
"replace_qual_cached_expressions" and
"replace_pathtarget_cached_expressions" calls into seemingly random
places
in the planner. Why isn't that being done uniformly during expression
preprocessing? There's no apparent structure to where you've put these
calls, and so they seem really vulnerable to errors of omission.

I'll fix this.

Also,
if this were done in expression preprocessing, there'd be a chance of
combining it with some existing pass over expression trees instead of
having to do a separate (and expensive) expression tree mutation.
I can't help suspecting that eval_const_expressions could take this on
as an additional responsibility with a lot less than a thousand new
lines
of code.

From quick look I see no contradictions so I'll try to implement it.

5. BTW, cost_eval_cacheable_expr seems like useless restructuring as
well.
Why aren't you just recursively applying the regular costing function?

Such a stupid mistake :( I'll fix this.

If you did all of the above it would result in a pretty significant
reduction of the number of places touched by the patch, which would
make
it easier to see what's going on. Then we could start to discuss, for
instance, what does the "isConstParam" flag actually *mean* and why
is it different from PARAM_FLAG_CONST?

AFAIU they do not differ, and as I said above I'll try not to change the
infrastructure of ParamListInfoData.

And what in the world is
CheckBoundParams about? The internal documentation in this patch
isn't quite nonexistent, but it's well short of being in a
committable state IMO.

I'll try to improve it, for CheckBoundParams (if I understood you
correctly) and others.

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#25Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Tom Lane (#22)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

On 17-01-2018 1:05, Tom Lane wrote:

[ I'm sending this comment separately because I think it's an issue
Andres might take an interest in. ]

Marina Polyakova <m.polyakova@postgrespro.ru> writes:

[ v7-0001-Precalculate-stable-and-immutable-functions.patch ]

Another thing that's bothering me is that the execution semantics
you're proposing for CachedExpr seem rather inflexible. AFAICS, once a
CachedExpr has run once, it will hang on to the result value and keep
returning that for the entire lifespan of the compiled expression.
We already noted that that breaks plpgsql's "simple expression"
logic, and it seems inevitable to me that it will be an issue for
other places as well. I think it'd be a better design if we had some
provision for resetting the cached values, short of recompiling the
expression from scratch.

One way that occurs to me to do this is to replace the simple boolean
isExecuted flags with a generation counter, and add a master generation
counter to ExprState. The rule for executing CachedExpr would be "if
my
generation counter is different from the ExprState's counter, then
evaluate the subexpression and copy the ExprState's counter into mine".
Then the procedure for forcing recalculation of cached values is just
to
increment the ExprState's counter. There are other ways one could
imagine
doing this --- for instance, I initially thought of keeping the master
counter in the ExprContext being used to run the expression. But you
need
some way to remember what counter value was used last with a particular
expression, so probably keeping it in ExprState is better.

Or we could just brute-force it by providing a function that runs
through
a compiled expression step list and resets the isExecuted flag for each
EEOP_CACHEDEXPR_IF_CACHED step it finds. A slightly less brute-force
way is to link those steps together in a list, so that the function
doesn't have to visit irrelevant steps. If the reset function were
seldom
used then the extra cycles for this wouldn't be very expensive. But
I'm
not sure it will be seldom used --- it seems like plpgsql simple
expressions will be doing this every time --- so I think the counter
approach might be a better idea.

Thank you very much! I'll try to implement something from this.

I'm curious to know whether Andres has some other ideas, or whether he
feels this is all a big wart on the compiled-expression concept. I
don't
think there are any existing cases where we keep any meaningful state
across executions of a compiled-expression data structure; maybe that's
a bad idea in itself.

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#26Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#22)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hi,

On 2018-01-16 17:05:01 -0500, Tom Lane wrote:

[ I'm sending this comment separately because I think it's an issue
Andres might take an interest in. ]

Thanks for that. I indeed am interested. Sorry for the late response,
was very deep into the JIT patch.

Marina Polyakova <m.polyakova@postgrespro.ru> writes:

[ v7-0001-Precalculate-stable-and-immutable-functions.patch ]

Another thing that's bothering me is that the execution semantics
you're proposing for CachedExpr seem rather inflexible. AFAICS, once a
CachedExpr has run once, it will hang on to the result value and keep
returning that for the entire lifespan of the compiled expression.
We already noted that that breaks plpgsql's "simple expression"
logic, and it seems inevitable to me that it will be an issue for
other places as well. I think it'd be a better design if we had some
provision for resetting the cached values, short of recompiling the
expression from scratch.

Hm. Yes, that makes me uncomfortable as well.

One way that occurs to me to do this is to replace the simple boolean
isExecuted flags with a generation counter, and add a master generation
counter to ExprState. The rule for executing CachedExpr would be "if my
generation counter is different from the ExprState's counter, then
evaluate the subexpression and copy the ExprState's counter into mine".
Then the procedure for forcing recalculation of cached values is just to
increment the ExprState's counter. There are other ways one could imagine
doing this --- for instance, I initially thought of keeping the master
counter in the ExprContext being used to run the expression. But you need
some way to remember what counter value was used last with a particular
expression, so probably keeping it in ExprState is better.

I'm not a big fan of this solution. We seem to be inventing more and
more places we keep state, rather than the contrary.

Or we could just brute-force it by providing a function that runs through
a compiled expression step list and resets the isExecuted flag for each
EEOP_CACHEDEXPR_IF_CACHED step it finds. A slightly less brute-force
way is to link those steps together in a list, so that the function
doesn't have to visit irrelevant steps. If the reset function were seldom
used then the extra cycles for this wouldn't be very expensive. But I'm
not sure it will be seldom used --- it seems like plpgsql simple
expressions will be doing this every time --- so I think the counter
approach might be a better idea.

Hm, that sounds like it'd not be cheap.

I'm curious to know whether Andres has some other ideas, or whether he
feels this is all a big wart on the compiled-expression concept.

I don't have too many "artistic" concerns from the compiled expression
POV. The biggest issue I see is that it'll make it a bit harder to
separate out the expression compilation phase from the expression
instantiation phase - something I think we definitely want.

I don't think there are any existing cases where we keep any
meaningful state across executions of a compiled-expression data
structure; maybe that's a bad idea in itself.

To me, who has *not* followed the thread in detail, it sounds like the
relevant data shouldn't be stored inside the expression itself. For
one, we do not want to have to visit every single simple expression and
reset them, for another it architecturally doesn't seem the right place
to me. Storing all cached values in an EState or ExprContext (the
latter referring to the former) somewhat alike the values for Param's
sounds a lot more reasonable to me.

Besides that it seems to make it a lot easier to reset the values, it
also seems like it makes it a lot cleaner to cache stable functions
across multiple expressions in different places in a query? ISTM having
expression steps to actually compute the expression value in every
referencing expression is quite the waste.

This all reminds me a lot of the infrastructure for Params...

Greetings,

Andres Freund

#27Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#26)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Andres Freund <andres@anarazel.de> writes:

On 2018-01-16 17:05:01 -0500, Tom Lane wrote:

I'm curious to know whether Andres has some other ideas, or whether he
feels this is all a big wart on the compiled-expression concept.

I don't have too many "artistic" concerns from the compiled expression
POV. The biggest issue I see is that it'll make it a bit harder to
separate out the expression compilation phase from the expression
instantiation phase - something I think we definitely want.

Hmm, there's no such distinction now, so could you explain what you
have in mind there?

I don't think there are any existing cases where we keep any
meaningful state across executions of a compiled-expression data
structure; maybe that's a bad idea in itself.

Storing all cached values in an EState or ExprContext (the
latter referring to the former) somewhat alike the values for Param's
sounds a lot more reasonable to me.
...
This all reminds me a lot of the infrastructure for Params...

Yeah, one thing I was thinking about in connection with this is the
stuff associated with propagating changes in outer-reference Params
(the extParam/allParam/chgParam mess). I wonder if we could find
a way to unify that with this feature.

Keeping the stored value of a CachedExpr in a Param slot is an
interesting idea indeed.

regards, tom lane

#28Andres Freund
andres@anarazel.de
In reply to: Tom Lane (#27)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

On 2018-01-24 15:10:56 -0500, Tom Lane wrote:

Andres Freund <andres@anarazel.de> writes:

On 2018-01-16 17:05:01 -0500, Tom Lane wrote:

I'm curious to know whether Andres has some other ideas, or whether he
feels this is all a big wart on the compiled-expression concept.

I don't have too many "artistic" concerns from the compiled expression
POV. The biggest issue I see is that it'll make it a bit harder to
separate out the expression compilation phase from the expression
instantiation phase - something I think we definitely want.

Hmm, there's no such distinction now, so could you explain what you
have in mind there?

There's a few related concerns I have:
- For OLTP workloads using prepared statements, we often spend the
majority of the time doing ExecInitExpr() and related tasks like
computing tupledescs.
- For OLTP workloads the low allocation density for things hanging off
ExprState's and PlanState nodes is a significant concern. The number
of allocations cause overhead, the overhead wastes memory and lowers
cache hit ratios.
- For JIT we currently end up encoding specific pointer values into the
generated code. As these obviously prevent reuse of the generated
function, this noticeably reduces the applicability of JITing to fewer
usecases. JITing is actually quite beneficial for a lot of OLTP
workloads too, but it's too expensive to do every query.

To address these, I think we may want to split the the division of labor
a bit. Expression instantiation (i.e. ExecReadyExpr()) should happen at
executor startup, but in a lot of cases "compiling" the steps itself
should happen at plan time. Obviously that means the steps themselves
can't contain plain pointers, as the per-execution memory will be
located in different places. So I think what we should have is that
expression initialization just computes the size of required memory for
all steps and puts *offsets* into that in the steps. After that
expression instantiation either leaves them alone and evaluation uses
relative pointers (cheap-ish e.g. on x86 due to lea), or just turn the
relative pointers into absolute ones.
That means that all the memory for all steps of an ExprState would be
allocated in one chunk, reducing allocation overhead and increasing
cache hit ratios considerably.

I've experimented a bit with a rough rough hack of the above (purely at
execution time), and it doesn't seem too hard.

Keeping the stored value of a CachedExpr in a Param slot is an
interesting idea indeed.

We keep coming back to this, IIRC we had a pretty similar discussion
around redesigning caseValue_datum/isNull domainValue_datum/isNull to be
less ugly. There also was
/messages/by-id/20171116182208.kcvf75nfaldv36uh@alap3.anarazel.de
where we discussed using something similar to PARAM_EXEC Param nodes to
allow inlining of volatile functions.

ISTM, there might be some value to consider all of them in the design of
the new mechanism.

Greetings,

Andres Freund

#29Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Andres Freund (#28)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hello!

On 24-01-2018 23:36, Andres Freund wrote:

On 2018-01-24 15:10:56 -0500, Tom Lane wrote:
...

Keeping the stored value of a CachedExpr in a Param slot is an
interesting idea indeed.

We keep coming back to this, IIRC we had a pretty similar discussion
around redesigning caseValue_datum/isNull domainValue_datum/isNull to
be
less ugly. There also was
/messages/by-id/20171116182208.kcvf75nfaldv36uh@alap3.anarazel.de
where we discussed using something similar to PARAM_EXEC Param nodes to
allow inlining of volatile functions.

ISTM, there might be some value to consider all of them in the design
of
the new mechanism.

Thank you both very much for this discussion and for the link on that
thread! Now I'm working on the patch, thanks to Tom Lane's comments
earlier [1]/messages/by-id/403e0ae329c6868b3f3467eac92cc04d@postgrespro.ru, and I'll try to implement something of this..

[1]: /messages/by-id/403e0ae329c6868b3f3467eac92cc04d@postgrespro.ru
/messages/by-id/403e0ae329c6868b3f3467eac92cc04d@postgrespro.ru

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#30Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Marina Polyakova (#29)
1 attachment(s)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hello, hackers!

This is the 8-th version of the patch for the precalculation of stable
or immutable functions, stable or immutable operators and other
nonvolatile expressions. This is a try to fix the most problems (I'm
sorry, it took some time..) that Tom Lane and Andres Freund mentioned in
[1]: /messages/by-id/27837.1516138246@sss.pgh.pa.us
make check-world passes. And I'll continue work on it.

Any suggestions are welcome!

[1]: /messages/by-id/27837.1516138246@sss.pgh.pa.us
/messages/by-id/27837.1516138246@sss.pgh.pa.us
[2]: /messages/by-id/29643.1516140301@sss.pgh.pa.us
/messages/by-id/29643.1516140301@sss.pgh.pa.us
[3]: /messages/by-id/20180124203616.3gx4vm45hpoijpw3@alap3.anarazel.de
/messages/by-id/20180124203616.3gx4vm45hpoijpw3@alap3.anarazel.de

On 17-01-2018 0:30, Tom Lane wrote:
...
This is indeed quite a large patch, but it seems to me it could become
smaller. After a bit of review:

1. I do not like what you've done with ParamListInfo. The changes
around
that are invasive, accounting for a noticeable part of the patch bulk,
and I don't think they're well designed. Having to cast back and forth
between ParamListInfo and ParamListInfoCommon and so on is ugly and
prone
to cause (or hide) errors. And I don't really understand why
ParamListInfoPrecalculationData exists at all. Couldn't you have gotten
the same result with far fewer notational changes by defining another
PARAM_FLAG bit in the existing pflags field? (Or alternatively, maybe
the real need here is for another ParamKind value for Param nodes?)

I also dislike this approach because it effectively throws away the
support for "virtual" param arrays that I added in commit 6719b238e:
ParamListInfoPrecalculationData has no support for dynamically
determined
parameter properties, which is surely something that somebody will
need.
(It's just luck that the patch doesn't break plpgsql today.) I realize
that that's a recent commit and the code I'm complaining about predates
it, but we need to adjust this so that it fits in with the new
approach.
See comment block at lines 25ff in params.h.

Changed, now only the new flag PARAM_FLAG_PRECALCULATED

2. I don't follow the need for the also-rather-invasive changes to
domain
constraint data structures. I do see that the patch attempts to make
CoerceToDomain nodes cacheable, which is flat wrong and has to be
ripped
out. You *cannot* assume that the planner has access to the same
domain
constraints that will apply at runtime.

Removed

4. I don't like the way that you've inserted
"replace_qual_cached_expressions" and
"replace_pathtarget_cached_expressions" calls into seemingly random
places
in the planner. Why isn't that being done uniformly during expression
preprocessing? There's no apparent structure to where you've put these
calls, and so they seem really vulnerable to errors of omission. Also,
if this were done in expression preprocessing, there'd be a chance of
combining it with some existing pass over expression trees instead of
having to do a separate (and expensive) expression tree mutation.
I can't help suspecting that eval_const_expressions could take this on
as an additional responsibility with a lot less than a thousand new
lines
of code.

eval_const_expressions is changed accordingly and, thank you, now
there're fewer omissions)

5. BTW, cost_eval_cacheable_expr seems like useless restructuring as
well.
Why aren't you just recursively applying the regular costing function?

Fixed

And what in the world is
CheckBoundParams about? The internal documentation in this patch
isn't quite nonexistent, but it's well short of being in a
committable state IMO.

This is a try to improve it..

3. I think you should also try hard to get rid of the need for
PlannedStmt.hasCachedExpr. AFAICS there's only one place that is
using that flag, which is exec_simple_check_plan, and I have to
think there are better ways we could deal with that. In particular,
I don't understand why you haven't simply set up plpgsql parameter
references to be noncacheable. Or maybe what we'd better do is
disable CacheExpr insertions into potentially-simple plans in the
first place. As you have it here, it's possible for recompilation
of an expression to result in a change in whether it should be deemed
simple or not, which will break things (cf commit 00418c612).

<...>

Another thing that's bothering me is that the execution semantics
you're proposing for CachedExpr seem rather inflexible. AFAICS, once a
CachedExpr has run once, it will hang on to the result value and keep
returning that for the entire lifespan of the compiled expression.
We already noted that that breaks plpgsql's "simple expression"
logic, and it seems inevitable to me that it will be an issue for
other places as well. I think it'd be a better design if we had some
provision for resetting the cached values, short of recompiling the
expression from scratch.

One way that occurs to me to do this is to replace the simple boolean
isExecuted flags with a generation counter, and add a master generation
counter to ExprState. The rule for executing CachedExpr would be "if
my
generation counter is different from the ExprState's counter, then
evaluate the subexpression and copy the ExprState's counter into mine".
Then the procedure for forcing recalculation of cached values is just
to
increment the ExprState's counter. There are other ways one could
imagine
doing this --- for instance, I initially thought of keeping the master
counter in the ExprContext being used to run the expression. But you
need
some way to remember what counter value was used last with a particular
expression, so probably keeping it in ExprState is better.

I did something like that..

Keeping the stored value of a CachedExpr in a Param slot is an
interesting idea indeed.

We keep coming back to this, IIRC we had a pretty similar discussion
around redesigning caseValue_datum/isNull domainValue_datum/isNull to
be
less ugly. There also was
/messages/by-id/20171116182208.kcvf75nfaldv36uh@alap3.anarazel.de
where we discussed using something similar to PARAM_EXEC Param nodes to
allow inlining of volatile functions.

ISTM, there might be some value to consider all of them in the design
of
the new mechanism.

I'm sorry, the other parts have occupied all the time, and I'll work on
it..

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v8-0001-Precalculate-stable-and-immutable-functions.patchtext/x-diff; name=v8-0001-Precalculate-stable-and-immutable-functions.patchDownload
From 9d7e504b50bdb8f709d51227aa9904fbcae7a61e Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Thu, 1 Feb 2018 07:32:39 +0300
Subject: [PATCH v8] Precalculate stable and immutable functions

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.

In this patch the function / operator / another expression is precalculated (=
calculated once for all output rows, but as many times as the expression is
mentioned in the query) if:
1) it doesn't return a set,
2) it's not volatile itself,
3) its arguments are also constants or precalculated expressions.

Costs are changed to reflect the changed behaviour.
Tests, small notes in the documentation and support for the prepared statements
are included.

Note: caching of domain constraints is not currently supported, as we cannot
assume that the planner has access to the same domain constraints that will
apply at runtime.
---
 contrib/postgres_fdw/deparse.c                     |   11 +
 doc/src/sgml/ref/create_function.sgml              |   14 +
 doc/src/sgml/xfunc.sgml                            |   13 +-
 src/backend/commands/explain.c                     |    5 +-
 src/backend/commands/prepare.c                     |    4 +-
 src/backend/executor/execExpr.c                    |   87 +
 src/backend/executor/execExprInterp.c              |   70 +
 src/backend/executor/execSRF.c                     |   10 +
 src/backend/executor/spi.c                         |    8 +-
 src/backend/nodes/copyfuncs.c                      |   16 +
 src/backend/nodes/equalfuncs.c                     |   11 +
 src/backend/nodes/nodeFuncs.c                      |   96 +
 src/backend/nodes/outfuncs.c                       |   11 +
 src/backend/nodes/readfuncs.c                      |   15 +
 src/backend/optimizer/path/clausesel.c             |   12 +
 src/backend/optimizer/path/costsize.c              |   21 +
 src/backend/optimizer/prep/prepqual.c              |   28 +
 src/backend/optimizer/util/clauses.c               | 1123 +++-
 src/backend/optimizer/util/var.c                   |   40 +
 src/backend/tcop/postgres.c                        |    2 +-
 src/backend/utils/adt/arrayfuncs.c                 |   13 +
 src/backend/utils/adt/ruleutils.c                  |   25 +-
 src/backend/utils/cache/plancache.c                |  295 +-
 src/backend/utils/fmgr/funcapi.c                   |   10 +-
 src/include/executor/execExpr.h                    |   31 +
 src/include/executor/executor.h                    |   14 +-
 src/include/nodes/execnodes.h                      |   19 +
 src/include/nodes/nodeFuncs.h                      |    8 +
 src/include/nodes/nodes.h                          |    2 +
 src/include/nodes/params.h                         |   15 +-
 src/include/nodes/primnodes.h                      |   76 +-
 src/include/utils/plancache.h                      |   13 +-
 src/pl/plpgsql/src/pl_exec.c                       |    7 +
 .../expected/precalculate_stable_functions.out     | 6194 ++++++++++++++++++++
 .../expected/precalculate_stable_functions_1.out   | 5840 ++++++++++++++++++
 src/test/regress/parallel_schedule                 |    2 +-
 src/test/regress/serial_schedule                   |    1 +
 .../regress/sql/precalculate_stable_functions.sql  | 1946 ++++++
 38 files changed, 15907 insertions(+), 201 deletions(-)
 create mode 100644 src/test/regress/expected/precalculate_stable_functions.out
 create mode 100644 src/test/regress/expected/precalculate_stable_functions_1.out
 create mode 100644 src/test/regress/sql/precalculate_stable_functions.sql

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index e111b09..63088c4 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -771,6 +771,14 @@ foreign_expr_walker(Node *node,
 					state = FDW_COLLATE_UNSAFE;
 			}
 			break;
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+
+				return foreign_expr_walker((Node *) cachedexpr->subexpr,
+										   glob_cxt, outer_cxt);
+			}
+			break;
 		default:
 
 			/*
@@ -2162,6 +2170,9 @@ deparseExpr(Expr *node, deparse_expr_cxt *context)
 		case T_Aggref:
 			deparseAggref((Aggref *) node, context);
 			break;
+		case T_CachedExpr:
+			deparseExpr((Expr *) ((CachedExpr *) node)->subexpr, context);
+			break;
 		default:
 			elog(ERROR, "unsupported expression type for deparse: %d",
 				 (int) nodeTag(node));
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index c0adb8c..21e2f5f 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -336,6 +336,20 @@ CREATE [ OR REPLACE ] FUNCTION
        <literal>setval()</literal>.
       </para>
 
+      <note>
+       <para>
+        Stable, immutable functions and other nonvolatile expressions are
+        precalculated (= calculated once for all output rows, but as many times
+        as expression is mentioned in query), if they don't return a set and
+        their arguments are constants or recursively precalculated expressions.
+       </para>
+
+       <para>
+        Now this feature is not supported for domain constraints (see <xref
+        linkend="sql-createdomain"/>).
+       </para>
+      </note>
+
       <para>
        For additional details see <xref linkend="xfunc-volatility"/>.
       </para>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index bbc3766..169513f 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1533,9 +1533,20 @@ CREATE FUNCTION test(int, int) RETURNS int
 
    <para>
     For best optimization results, you should label your functions with the
-    strictest volatility category that is valid for them.
+    strictest volatility category that is valid for them. Stable, immutable
+    functions and other nonvolatile expressions are precalculated (= calculated
+    once for all output rows, but as many times as expression is mentioned in
+    query), if they don't return a set and their arguments are constants or
+    recursively precalculated expressions.
    </para>
 
+   <note>
+    <para>
+     Now this feature is not supported for domain constraints (see <xref
+     linkend="sql-createdomain"/>).
+    </para>
+   </note>
+
    <para>
     Any function with side-effects <emphasis>must</emphasis> be labeled
     <literal>VOLATILE</literal>, so that calls to it cannot be optimized away.
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 41cd47e..f7d56f6 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2822,10 +2822,11 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 				if (list_length(fscan->functions) == 1)
 				{
 					RangeTblFunction *rtfunc = (RangeTblFunction *) linitial(fscan->functions);
+					FuncExpr   *funcexpr = cast_node_if_cached(rtfunc->funcexpr,
+															   FuncExpr);
 
-					if (IsA(rtfunc->funcexpr, FuncExpr))
+					if (funcexpr)
 					{
-						FuncExpr   *funcexpr = (FuncExpr *) rtfunc->funcexpr;
 						Oid			funcid = funcexpr->funcid;
 
 						objectname = get_func_name(funcid);
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index b945b15..ef9f134 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -243,7 +243,7 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
 									   entry->plansource->query_string);
 
 	/* Replan if needed, and increment plan refcount for portal */
-	cplan = GetCachedPlan(entry->plansource, paramLI, false, NULL);
+	cplan = GetCachedPlan(entry->plansource, paramLI, false, NULL, true);
 	plan_list = cplan->stmt_list;
 
 	/*
@@ -670,7 +670,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	}
 
 	/* Replan if needed, and acquire a transient refcount */
-	cplan = GetCachedPlan(entry->plansource, paramLI, true, queryEnv);
+	cplan = GetCachedPlan(entry->plansource, paramLI, true, queryEnv, true);
 
 	INSTR_TIME_SET_CURRENT(planduration);
 	INSTR_TIME_SUBTRACT(planduration, planstart);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c6eb3eb..39110b9 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -130,6 +130,12 @@ ExecInitExpr(Expr *node, PlanState *parent)
 	state->parent = parent;
 	state->ext_params = NULL;
 
+	/* after the first run, set it to false */
+	state->own_execute_cached_expressions = palloc(sizeof(bool));
+	*(state->own_execute_cached_expressions) = true;
+	/* there's no no upper state */
+	state->top_execute_cached_expressions = NULL;
+
 	/* Insert EEOP_*_FETCHSOME steps as needed */
 	ExecInitExprSlots(state, (Node *) node);
 
@@ -167,6 +173,12 @@ ExecInitExprWithParams(Expr *node, ParamListInfo ext_params)
 	state->parent = NULL;
 	state->ext_params = ext_params;
 
+	/* after the first run, set it to false */
+	state->own_execute_cached_expressions = palloc(sizeof(bool));
+	*(state->own_execute_cached_expressions) = true;
+	/* there's no no upper state */
+	state->top_execute_cached_expressions = NULL;
+
 	/* Insert EEOP_*_FETCHSOME steps as needed */
 	ExecInitExprSlots(state, (Node *) node);
 
@@ -222,6 +234,12 @@ ExecInitQual(List *qual, PlanState *parent)
 	/* mark expression as to be used with ExecQual() */
 	state->flags = EEO_FLAG_IS_QUAL;
 
+	/* after the first run, set it to false */
+	state->own_execute_cached_expressions = palloc(sizeof(bool));
+	*(state->own_execute_cached_expressions) = true;
+	/* there's no no upper state */
+	state->top_execute_cached_expressions = NULL;
+
 	/* Insert EEOP_*_FETCHSOME steps as needed */
 	ExecInitExprSlots(state, (Node *) qual);
 
@@ -366,6 +384,12 @@ ExecBuildProjectionInfo(List *targetList,
 
 	state->resultslot = slot;
 
+	/* after the first run, set it to false */
+	state->own_execute_cached_expressions = palloc(sizeof(bool));
+	*(state->own_execute_cached_expressions) = true;
+	/* there's no no upper state */
+	state->top_execute_cached_expressions = NULL;
+
 	/* Insert EEOP_*_FETCHSOME steps as needed */
 	ExecInitExprSlots(state, (Node *) targetList);
 
@@ -863,6 +887,38 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		case T_CachedExpr:
+			{
+				/*
+				 * Allocate CachedExprState used by all steps of CachedExpr
+				 * evaluation.
+				 */
+				scratch.d.cachedexpr.state = (CachedExprState *) palloc(
+					sizeof(CachedExprState));
+				scratch.d.cachedexpr.state->resnull = false;
+				scratch.d.cachedexpr.state->resvalue = (Datum) 0;
+				scratch.d.cachedexpr.state->isExecuted = false;
+				scratch.d.cachedexpr.state->restypid = exprType(
+					(const Node *) node);
+
+				/* add EEOP_CACHEDEXPR_IF_CACHED step */
+				scratch.opcode = EEOP_CACHEDEXPR_IF_CACHED;
+				ExprEvalPushStep(state, &scratch);
+
+				/* add subexpression steps */
+				ExecInitExprRec((Expr *) ((CachedExpr *) node)->subexpr, state,
+								resv, resnull);
+
+				/* add EEOP_CACHEDEXPR_SUBEXPR_END step */
+				scratch.opcode = EEOP_CACHEDEXPR_SUBEXPR_END;
+				ExprEvalPushStep(state, &scratch);
+
+				/* adjust jump target */
+				scratch.d.cachedexpr.state->jumpdone = state->steps_len;
+
+				break;
+			}
+
 		case T_ArrayRef:
 			{
 				ArrayRef   *aref = (ArrayRef *) node;
@@ -1320,6 +1376,31 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				elemstate->parent = state->parent;
 				elemstate->ext_params = state->ext_params;
 
+				/*
+				 * Use the information from the upper state to get the first
+				 * element, but for other elements always set it to false. This
+				 * means that we will use our own information about it during
+				 * execution.
+				 */
+
+				elemstate->own_execute_cached_expressions =
+					palloc(sizeof(bool));
+
+				/* does not matter, set it at runtime */
+				*(elemstate->own_execute_cached_expressions) = true;
+
+				if (state->top_execute_cached_expressions)
+				{
+					elemstate->top_execute_cached_expressions =
+						state->top_execute_cached_expressions;
+				}
+				else
+				{
+					Assert(state->own_execute_cached_expressions);
+					elemstate->top_execute_cached_expressions =
+						state->own_execute_cached_expressions;
+				}
+
 				elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
 				elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
 
@@ -2823,6 +2904,12 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
 	state->expr = (Expr *) aggstate;
 	state->parent = parent;
 
+	/* after the first run, set it to false */
+	state->own_execute_cached_expressions = palloc(sizeof(bool));
+	*(state->own_execute_cached_expressions) = true;
+	/* there's no no upper state */
+	state->top_execute_cached_expressions = NULL;
+
 	scratch.resvalue = &state->resvalue;
 	scratch.resnull = &state->resnull;
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f646fd9..c13df03 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -391,6 +391,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_AGG_PLAIN_TRANS,
 		&&CASE_EEOP_AGG_ORDERED_TRANS_DATUM,
 		&&CASE_EEOP_AGG_ORDERED_TRANS_TUPLE,
+		&&CASE_EEOP_CACHEDEXPR_IF_CACHED,
+		&&CASE_EEOP_CACHEDEXPR_SUBEXPR_END,
 		&&CASE_EEOP_LAST
 	};
 
@@ -1755,6 +1757,74 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_CACHEDEXPR_IF_CACHED)
+		{
+			/*
+			 * Always execute if we do not have a cached result...
+			 */
+			bool		execute = !op->d.cachedexpr.state->isExecuted;
+
+			/*
+			 * ...but sometimes we must execute even if we have the cached
+			 * result.
+			 */
+			if (state->own_execute_cached_expressions)
+			{
+				execute |= *(state->own_execute_cached_expressions);
+			}
+			else
+			{
+				Assert(state->top_execute_cached_expressions);
+				execute |= *(state->top_execute_cached_expressions);
+			}
+
+			if (!execute)
+			{
+				/* use saved result and skip subexpression evaluation */
+				*op->resnull = op->d.cachedexpr.state->resnull;
+				if (!(*op->resnull))
+					*op->resvalue = op->d.cachedexpr.state->resvalue;
+
+				EEO_JUMP(op->d.cachedexpr.state->jumpdone);
+			}
+
+			/* we are ready for subexpression evaluation */
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CACHEDEXPR_SUBEXPR_END)
+		{
+			int16		restyplen;
+			bool		restypbyval;
+			MemoryContext oldContext;
+
+			/* save result */
+			op->d.cachedexpr.state->resnull = *op->resnull;
+			if (!(*op->resnull))
+			{
+				get_typlenbyval(op->d.cachedexpr.state->restypid, &restyplen,
+								&restypbyval);
+
+				/*
+				 * Switch per-query memory context. It is necessary to save the
+				 * subexpression result between all tuples if its value datum is
+				 * a pointer.
+				 */
+				oldContext = MemoryContextSwitchTo(
+					econtext->ecxt_per_query_memory);
+
+				op->d.cachedexpr.state->resvalue = datumCopy(*op->resvalue,
+															 restypbyval,
+															 restyplen);
+
+				/* switch memory context back */
+				MemoryContextSwitchTo(oldContext);
+			}
+			op->d.cachedexpr.state->isExecuted = true;
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_LAST)
 		{
 			/* unreachable */
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index b97b8d7..bd081e3 100644
--- a/src/backend/executor/execSRF.c
+++ b/src/backend/executor/execSRF.c
@@ -58,6 +58,16 @@ ExecInitTableFunctionResult(Expr *expr,
 {
 	SetExprState *state = makeNode(SetExprState);
 
+	/*
+	 * Although SRF expressions are not cached, expressions that return RECORD
+	 * can be cached. If such cached expression is executed in FROM (ROWS FROM),
+	 * this means that it is executed only once so we can treat it as a
+	 * non-cached expression (note that cached expressions always are calculated
+	 * as many times as they are mentioned in the query).
+	 */
+	if (IsA(expr, CachedExpr))
+		expr = (Expr *) ((CachedExpr *) expr)->subexpr;
+
 	state->funcReturnsSet = false;
 	state->expr = expr;
 	state->func.fn_oid = InvalidOid;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 9fc4431..e67f365 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1290,7 +1290,8 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 	 */
 
 	/* Replan if needed, and increment plan refcount for portal */
-	cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv);
+	cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv,
+						  false);
 	stmt_list = cplan->stmt_list;
 
 	if (!plan->saved)
@@ -1724,7 +1725,7 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
 
 	/* Get the generic plan for the query */
 	cplan = GetCachedPlan(plansource, NULL, plan->saved,
-						  _SPI_current->queryEnv);
+						  _SPI_current->queryEnv, false);
 	Assert(cplan == plansource->gplan);
 
 	/* Pop the error context stack */
@@ -2114,7 +2115,8 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 		 * Replan if needed, and increment plan refcount.  If it's a saved
 		 * plan, the refcount must be backed by the CurrentResourceOwner.
 		 */
-		cplan = GetCachedPlan(plansource, paramLI, plan->saved, _SPI_current->queryEnv);
+		cplan = GetCachedPlan(plansource, paramLI, plan->saved,
+							  _SPI_current->queryEnv, false);
 		stmt_list = cplan->stmt_list;
 
 		/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index fd3001c..5783f09 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1332,6 +1332,19 @@ _copyConst(const Const *from)
 }
 
 /*
+ * _copyCachedExpr
+ */
+static CachedExpr *
+_copyCachedExpr(const CachedExpr *from)
+{
+	CachedExpr *newnode = makeNode(CachedExpr);
+
+	COPY_NODE_FIELD(subexpr);
+
+	return newnode;
+}
+
+/*
  * _copyParam
  */
 static Param *
@@ -4874,6 +4887,9 @@ copyObjectImpl(const void *from)
 		case T_Const:
 			retval = _copyConst(from);
 			break;
+		case T_CachedExpr:
+			retval = _copyCachedExpr(from);
+			break;
 		case T_Param:
 			retval = _copyParam(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7d2aa1a..b8d5b31 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -195,6 +195,14 @@ _equalConst(const Const *a, const Const *b)
 }
 
 static bool
+_equalCachedExpr(const CachedExpr *a, const CachedExpr *b)
+{
+	COMPARE_NODE_FIELD(subexpr);
+
+	return true;
+}
+
+static bool
 _equalParam(const Param *a, const Param *b)
 {
 	COMPARE_SCALAR_FIELD(paramkind);
@@ -3024,6 +3032,9 @@ equal(const void *a, const void *b)
 		case T_Const:
 			retval = _equalConst(a, b);
 			break;
+		case T_CachedExpr:
+			retval = _equalCachedExpr(a, b);
+			break;
 		case T_Param:
 			retval = _equalParam(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6c76c41..0b0137c 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -54,6 +54,10 @@ exprType(const Node *expr)
 		case T_Const:
 			type = ((const Const *) expr)->consttype;
 			break;
+		case T_CachedExpr:
+			type =
+				exprType((const Node *) ((const CachedExpr *) expr)->subexpr);
+			break;
 		case T_Param:
 			type = ((const Param *) expr)->paramtype;
 			break;
@@ -284,6 +288,9 @@ exprTypmod(const Node *expr)
 			return ((const Var *) expr)->vartypmod;
 		case T_Const:
 			return ((const Const *) expr)->consttypmod;
+		case T_CachedExpr:
+			return
+				exprTypmod((const Node *) ((const CachedExpr *) expr)->subexpr);
 		case T_Param:
 			return ((const Param *) expr)->paramtypmod;
 		case T_ArrayRef:
@@ -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);
@@ -732,6 +751,10 @@ exprCollation(const Node *expr)
 		case T_Const:
 			coll = ((const Const *) expr)->constcollid;
 			break;
+		case T_CachedExpr:
+			coll = exprCollation(
+				(const Node *) ((const CachedExpr *) expr)->subexpr);
+			break;
 		case T_Param:
 			coll = ((const Param *) expr)->paramcollid;
 			break;
@@ -927,6 +950,10 @@ exprInputCollation(const Node *expr)
 
 	switch (nodeTag(expr))
 	{
+		case T_CachedExpr:
+			coll = exprInputCollation(
+				(const Node *) ((const CachedExpr *) expr)->subexpr);
+			break;
 		case T_Aggref:
 			coll = ((const Aggref *) expr)->inputcollid;
 			break;
@@ -976,6 +1003,10 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_Const:
 			((Const *) expr)->constcollid = collation;
 			break;
+		case T_CachedExpr:
+			exprSetCollation((Node *) ((CachedExpr *) expr)->subexpr,
+							 collation);
+			break;
 		case T_Param:
 			((Param *) expr)->paramcollid = collation;
 			break;
@@ -1123,6 +1154,10 @@ exprSetInputCollation(Node *expr, Oid inputcollation)
 {
 	switch (nodeTag(expr))
 	{
+		case T_CachedExpr:
+			exprSetInputCollation((Node *) ((CachedExpr *) expr)->subexpr,
+								  inputcollation);
+			break;
 		case T_Aggref:
 			((Aggref *) expr)->inputcollid = inputcollation;
 			break;
@@ -1203,6 +1238,10 @@ exprLocation(const Node *expr)
 		case T_Const:
 			loc = ((const Const *) expr)->location;
 			break;
+		case T_CachedExpr:
+			loc = exprLocation(
+				(const Node *) ((const CachedExpr *) expr)->subexpr);
+			break;
 		case T_Param:
 			loc = ((const Param *) expr)->location;
 			break;
@@ -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))
@@ -1653,6 +1695,9 @@ check_functions_in_node(Node *node, check_function_callback checker,
 {
 	switch (nodeTag(node))
 	{
+		case T_CachedExpr:
+			return check_functions_in_node(
+				(Node *) ((CachedExpr *) node)->subexpr, checker, context);
 		case T_Aggref:
 			{
 				Aggref	   *expr = (Aggref *) node;
@@ -1868,6 +1913,18 @@ expression_tree_walker(Node *node,
 			break;
 		case T_WithCheckOption:
 			return walker(((WithCheckOption *) node)->qual, context);
+		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_Aggref:
 			{
 				Aggref	   *expr = (Aggref *) node;
@@ -2478,6 +2535,25 @@ expression_tree_mutator(Node *node,
 				MUTATE(newnode->qual, wco->qual, Node *);
 				return (Node *) newnode;
 			}
+		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_Aggref:
 			{
 				Aggref	   *aggref = (Aggref *) node;
@@ -3817,3 +3893,23 @@ planstate_walk_members(List *plans, PlanState **planstates,
 
 	return false;
 }
+
+/*
+ * cast_node_if_cached_impl: return a node of this type (perhaps get it from the
+ * source cached expression) or NULL.
+ */
+Node *
+cast_node_if_cached_impl(Node *node, NodeTag tag)
+{
+	if (nodeTag(node) == tag)
+		return node;
+
+	if (nodeTag(node) == T_CachedExpr)
+	{
+		return cast_node_if_cached_impl(
+							(Node *) (castNode(CachedExpr, node))->subexpr,
+							tag);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e0f4bef..b9aa22b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1115,6 +1115,14 @@ _outConst(StringInfo str, const Const *node)
 }
 
 static void
+_outCachedExpr(StringInfo str, const CachedExpr *node)
+{
+	WRITE_NODE_TYPE("CACHEDEXPR");
+
+	WRITE_NODE_FIELD(subexpr);
+}
+
+static void
 _outParam(StringInfo str, const Param *node)
 {
 	WRITE_NODE_TYPE("PARAM");
@@ -3785,6 +3793,9 @@ outNode(StringInfo str, const void *obj)
 			case T_Const:
 				_outConst(str, obj);
 				break;
+			case T_CachedExpr:
+				_outCachedExpr(str, obj);
+				break;
 			case T_Param:
 				_outParam(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 22d8b9d..362cd9d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -554,6 +554,19 @@ _readConst(void)
 }
 
 /*
+ * _readCachedExpr
+ */
+static CachedExpr *
+_readCachedExpr(void)
+{
+	READ_LOCALS(CachedExpr);
+
+	READ_NODE_FIELD(subexpr);
+
+	READ_DONE();
+}
+
+/*
  * _readParam
  */
 static Param *
@@ -2475,6 +2488,8 @@ parseNodeString(void)
 		return_value = _readVar();
 	else if (MATCH("CONST", 5))
 		return_value = _readConst();
+	else if (MATCH("CACHEDEXPR", 10))
+		return_value = _readCachedExpr();
 	else if (MATCH("PARAM", 5))
 		return_value = _readParam();
 	else if (MATCH("AGGREF", 6))
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index f471794..3122db1 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -827,6 +827,18 @@ clause_selectivity(PlannerInfo *root,
 								jointype,
 								sjinfo);
 	}
+	else if (IsA(clause, CachedExpr))
+	{
+		/*
+		 * Not sure this case is needed, but it can't hurt.
+		 * Calculate selectivity of subexpression.
+		 */
+		s1 = clause_selectivity(root,
+								(Node *) ((CachedExpr *) clause)->subexpr,
+								varRelid,
+								jointype,
+								sjinfo);
+	}
 	else
 	{
 		/*
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 8679b14..d5cf07d 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3958,6 +3958,27 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 		 */
 		return false;
 	}
+	else if (IsA(node, CachedExpr))
+	{
+		/*
+		 * Calculate subexpression cost as usual and add it to startup cost
+		 * (because subexpression will be executed only once for all tuples).
+		 */
+		cost_qual_eval_context subexpr_context;
+
+		subexpr_context.root = context->root;
+		subexpr_context.total.startup = 0;
+		subexpr_context.total.per_tuple = 0;
+
+		cost_qual_eval_walker((Node *) ((CachedExpr *) node)->subexpr,
+							  &subexpr_context);
+
+		context->total.startup +=
+			(subexpr_context.total.startup + subexpr_context.total.per_tuple);
+
+		/* do NOT recurse into children */
+		return false;
+	}
 
 	/* recurse into children */
 	return expression_tree_walker(node, cost_qual_eval_walker,
diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c
index cb1f485..d02c655 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -252,6 +252,34 @@ negate_clause(Node *node)
 				return (Node *) newexpr;
 			}
 			break;
+		case T_CachedExpr:
+			{
+				CachedExpr *expr = (CachedExpr *) node;
+				/* Try to simplify its subexpression */
+				Node	   *newsubnode = negate_clause((Node *) expr->subexpr);
+
+				if (IsA(newsubnode, BoolExpr) &&
+					((BoolExpr *) newsubnode)->boolop == NOT_EXPR)
+				{
+					/*
+					 * Simplifying its subexpression did not help so return the
+					 * cached negation of the original node.
+					 */
+					CachedExpr *newexpr = makeNode(CachedExpr);
+					newexpr->subexpr =
+						(CacheableExpr *) make_notclause((Expr *) node);
+					return (Node *) newexpr;
+				}
+				else
+				{
+					/*
+					 * A simplified subexpression may be non-cacheable, so
+					 * return it by itself.
+					 */
+					return newsubnode;
+				}
+			}
+			break;
 		default:
 			/* else fall through */
 			break;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 89f27ce..0febf06 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -96,6 +96,45 @@ typedef struct
 	List	   *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
 } max_parallel_hazard_context;
 
+/*
+ * A detailed result of checking that the subnodes/functions of the node are
+ * safe for evaluation or caching. This is used in the functions
+ * ece_all_arguments_const (which checks the subnodes) and
+ * ece_functions_are_safe (which checks the node functions).
+ *
+ * When investigating subnodes:
+ * - SAFE_FOR_EVALUATION indicates that all the children of this node are Consts.
+ * - SAFE_FOR_CACHING_ONLY indicates that all the children of this node are
+ * Consts or CachedExprs.
+ * - SAFE_FOR_NOTHING indicates that this node has a non-Const and
+ * non-CachedExpr child node.
+ *
+ * When investigating node functions:
+ * - SAFE_FOR_EVALUATION that the node contains only immutable functions (or
+ * also stable functions in the case of estimation).
+ * - SAFE_FOR_CACHING_ONLY indicates the node contains only immutable or stable
+ * functions (so in the case of estimation there's no difference between
+ * SAFE_FOR_EVALUATION and SAFE_FOR_CACHING_ONLY, and the returned value is
+ * always SAFE_FOR_EVALUATION because it is more strict in general).
+ * - SAFE_FOR_NOTHING indicates that the node contains a volatile function.
+ */
+typedef enum
+{
+	SAFE_FOR_EVALUATION,
+	SAFE_FOR_CACHING_ONLY,
+	SAFE_FOR_NOTHING,
+} ece_check_node_safety_detailed;
+
+#define SAFE_FOR_CACHING(detailed) \
+	((detailed) == SAFE_FOR_EVALUATION || (detailed) == SAFE_FOR_CACHING_ONLY)
+
+typedef struct
+{
+	ece_check_node_safety_detailed detailed;	/* detailed result */
+	bool		recurse;		/* we should also check the subnodes? */
+	bool		estimate;		/* are stable functions safe for evaluation? */
+} ece_functions_are_safe_context;
+
 static bool contain_agg_clause_walker(Node *node, void *context);
 static bool get_agg_clause_costs_walker(Node *node,
 							get_agg_clause_costs_context *context);
@@ -113,23 +152,33 @@ static bool contain_leaked_vars_walker(Node *node, void *context);
 static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
 static List *find_nonnullable_vars_walker(Node *node, bool top_level);
 static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
+static ece_check_node_safety_detailed ece_all_arguments_const(Node *node);
 static Node *eval_const_expressions_mutator(Node *node,
 							   eval_const_expressions_context *context);
-static bool contain_non_const_walker(Node *node, void *context);
-static bool ece_function_is_safe(Oid funcid,
-					 eval_const_expressions_context *context);
+static bool contain_non_const_walker(Node *node,
+									 ece_check_node_safety_detailed *detailed);
+static ece_check_node_safety_detailed ece_functions_are_safe(Node *node,
+															 bool recurse,
+															 bool estimate);
+static bool ece_functions_are_safe_walker(
+									Node *node,
+									ece_functions_are_safe_context *context);
 static List *simplify_or_arguments(List *args,
 					  eval_const_expressions_context *context,
-					  bool *haveNull, bool *forceTrue);
+					  bool *haveNull, bool *forceTrue,
+					  bool *all_consts_or_cached);
 static List *simplify_and_arguments(List *args,
 					   eval_const_expressions_context *context,
-					   bool *haveNull, bool *forceFalse);
+					   bool *haveNull, bool *forceFalse,
+					   bool *all_consts_or_cached);
 static Node *simplify_boolean_equality(Oid opno, List *args);
 static Expr *simplify_function(Oid funcid,
 				  Oid result_type, int32 result_typmod,
 				  Oid result_collid, Oid input_collid, List **args_p,
-				  bool funcvariadic, bool process_args, bool allow_non_const,
-				  eval_const_expressions_context *context);
+				  bool funcvariadic, bool process_args,
+				  bool allow_only_consts_and_simple_caching,
+				  eval_const_expressions_context *context,
+				  CoercionForm funcformat, Oid opno, int location);
 static List *expand_function_arguments(List *args, Oid result_type,
 						  HeapTuple func_tuple);
 static List *reorder_function_arguments(List *args, HeapTuple func_tuple);
@@ -159,6 +208,13 @@ static Query *substitute_actual_srf_parameters(Query *expr,
 static Node *substitute_actual_srf_parameters_mutator(Node *node,
 										 substitute_actual_srf_parameters_context *context);
 static bool tlist_matches_coltypelist(List *tlist, List *coltypelist);
+static Expr *get_cached_expr_node(CacheableExpr *subexpr);
+static bool is_const_or_cached(const Node *node);
+static Expr *cache_function(Oid funcid, Oid result_type, Oid result_collid,
+							Oid input_collid, List *args, bool funcvariadic,
+							HeapTuple func_tuple,
+							eval_const_expressions_context *context,
+							CoercionForm funcformat, Oid opno, int location);
 
 
 /*****************************************************************************
@@ -394,6 +450,8 @@ make_ands_implicit(Expr *clause)
 			 !((Const *) clause)->constisnull &&
 			 DatumGetBool(((Const *) clause)->constvalue))
 		return NIL;				/* constant TRUE input -> NIL list */
+	else if (IsA(clause, CachedExpr))
+		return make_ands_implicit((Expr *) ((CachedExpr *) clause)->subexpr);
 	else
 		return list_make1(clause);
 }
@@ -857,6 +915,8 @@ contain_subplans_walker(Node *node, void *context)
 		IsA(node, AlternativeSubPlan) ||
 		IsA(node, SubLink))
 		return true;			/* abort the tree traversal and return true */
+	if (IsA(node, CachedExpr))
+		return false;			/* subplans are not cacheable */
 	return expression_tree_walker(node, contain_subplans_walker, context);
 }
 
@@ -983,6 +1043,11 @@ contain_volatile_functions_walker(Node *node, void *context)
 		/* NextValueExpr is volatile */
 		return true;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* CachedExpr is not volatile */
+		return false;
+	}
 
 	/*
 	 * See notes in contain_mutable_functions_walker about why we treat
@@ -1030,6 +1095,12 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
 								context))
 		return true;
 
+	if (IsA(node, CachedExpr))
+	{
+		/* CachedExpr is not volatile */
+		return false;
+	}
+
 	/*
 	 * See notes in contain_mutable_functions_walker about why we treat
 	 * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable, while
@@ -1290,6 +1361,14 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 								 context, 0);
 	}
 
+	else if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		return max_parallel_hazard_walker((Node *) cachedexpr->subexpr,
+										  context);
+	}
+
 	/* Recurse to check arguments */
 	return expression_tree_walker(node,
 								  max_parallel_hazard_walker,
@@ -1495,6 +1574,14 @@ contain_context_dependent_node_walker(Node *node, int *flags)
 			return res;
 		}
 	}
+	if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		return contain_context_dependent_node_walker(
+												(Node *) cachedexpr->subexpr,
+												flags);
+	}
 	return expression_tree_walker(node, contain_context_dependent_node_walker,
 								  (void *) flags);
 }
@@ -1615,6 +1702,12 @@ contain_leaked_vars_walker(Node *node, void *context)
 			 */
 			return false;
 
+		case T_CachedExpr:
+
+			return contain_leaked_vars_walker(
+										(Node *) ((CachedExpr *) node)->subexpr,
+										context);
+
 		default:
 
 			/*
@@ -2489,6 +2582,9 @@ eval_const_expressions(PlannerInfo *root, Node *node)
  *	  value of the Param.
  * 2. Fold stable, as well as immutable, functions to constants.
  * 3. Reduce PlaceHolderVar nodes to their contained expressions.
+ *
+ * Unlike eval_const_expressions, the cached expressions are never used for
+ * estimation.
  *--------------------
  */
 Node *
@@ -2508,11 +2604,13 @@ estimate_expression_value(PlannerInfo *root, Node *node)
 /*
  * The generic case in eval_const_expressions_mutator is to recurse using
  * expression_tree_mutator, which will copy the given node unchanged but
- * const-simplify its arguments (if any) as far as possible.  If the node
+ * simplify its arguments (if any) as far as possible.  If the node
  * itself does immutable processing, and each of its arguments were reduced
  * to a Const, we can then reduce it to a Const using evaluate_expr.  (Some
  * node types need more complicated logic; for example, a CASE expression
- * might be reducible to a constant even if not all its subtrees are.)
+ * might be reducible to a constant even if not all its subtrees are.) Also if
+ * the note itself does not volatile processing, and each of its arguments were
+ * reduced to Const or CachedExpr nodes, we can then reduce it to a CachedExpr.
  */
 #define ece_generic_processing(node) \
 	expression_tree_mutator((Node *) (node), eval_const_expressions_mutator, \
@@ -2523,8 +2621,13 @@ estimate_expression_value(PlannerInfo *root, Node *node)
  * By going directly to expression_tree_walker, contain_non_const_walker
  * is not applied to the node itself, only to its children.
  */
-#define ece_all_arguments_const(node) \
-	(!expression_tree_walker((Node *) (node), contain_non_const_walker, NULL))
+static ece_check_node_safety_detailed
+ece_all_arguments_const(Node *node)
+{
+	ece_check_node_safety_detailed detailed = SAFE_FOR_EVALUATION;
+	expression_tree_walker(node, contain_non_const_walker, (void *) &detailed);
+	return detailed;
+}
 
 /* Generic macro for applying evaluate_expr */
 #define ece_evaluate_expr(node) \
@@ -2573,7 +2676,8 @@ eval_const_expressions_mutator(Node *node,
 					{
 						/* OK to substitute parameter value? */
 						if (context->estimate ||
-							(prm->pflags & PARAM_FLAG_CONST))
+							((prm->pflags & PARAM_FLAG_CONST) &&
+							 !(prm->pflags & PARAM_FLAG_PRECALCULATED)))
 						{
 							/*
 							 * Return a Const representing the param value.
@@ -2600,6 +2704,13 @@ eval_const_expressions_mutator(Node *node,
 													  prm->isnull,
 													  typByVal);
 						}
+						/* Otherwise OK to cache parameter value? */
+						else if (!context->estimate &&
+								 prm->pflags & PARAM_FLAG_PRECALCULATED)
+						{
+							return (Node *) get_cached_expr_node(
+										(CacheableExpr *) copyObject(param));
+						}
 					}
 				}
 
@@ -2681,8 +2792,11 @@ eval_const_expressions_mutator(Node *node,
 										   &args,
 										   expr->funcvariadic,
 										   true,
-										   true,
-										   context);
+										   false,
+										   context,
+										   expr->funcformat,
+										   InvalidOid,
+										   expr->location);
 				if (simple)		/* successfully simplified it */
 					return (Node *) simple;
 
@@ -2728,26 +2842,15 @@ eval_const_expressions_mutator(Node *node,
 										   &args,
 										   false,
 										   true,
-										   true,
-										   context);
+										   false,
+										   context,
+										   COERCE_EXPLICIT_CALL,
+										   expr->opno,
+										   expr->location);
 				if (simple)		/* successfully simplified it */
 					return (Node *) simple;
 
 				/*
-				 * If the operator is boolean equality or inequality, we know
-				 * how to simplify cases involving one constant and one
-				 * non-constant argument.
-				 */
-				if (expr->opno == BooleanEqualOperator ||
-					expr->opno == BooleanNotEqualOperator)
-				{
-					simple = (Expr *) simplify_boolean_equality(expr->opno,
-																args);
-					if (simple) /* successfully simplified it */
-						return (Node *) simple;
-				}
-
-				/*
 				 * The expression cannot be simplified any further, so build
 				 * and return a replacement OpExpr node using the
 				 * possibly-simplified arguments.
@@ -2810,31 +2913,37 @@ eval_const_expressions_mutator(Node *node,
 					/* one null? then distinct */
 					if (has_null_input)
 						return makeBoolConst(true, false);
+				}
 
-					/* otherwise try to evaluate the '=' operator */
-					/* (NOT okay to try to inline it, though!) */
+				/* otherwise try to evaluate the '=' operator */
+				/* (NOT okay to try to inline it, though!) */
 
-					/*
-					 * Need to get OID of underlying function.  Okay to
-					 * scribble on input to this extent.
-					 */
-					set_opfuncid((OpExpr *) expr);	/* rely on struct
-													 * equivalence */
+				/*
+				 * Need to get OID of underlying function.  Okay to
+				 * scribble on input to this extent.
+				 */
+				set_opfuncid((OpExpr *) expr);	/* rely on struct
+												 * equivalence */
 
-					/*
-					 * Code for op/func reduction is pretty bulky, so split it
-					 * out as a separate function.
-					 */
-					simple = simplify_function(expr->opfuncid,
-											   expr->opresulttype, -1,
-											   expr->opcollid,
-											   expr->inputcollid,
-											   &args,
-											   false,
-											   false,
-											   false,
-											   context);
-					if (simple) /* successfully simplified it */
+				/*
+				 * Code for op/func reduction is pretty bulky, so split it
+				 * out as a separate function.
+				 */
+				simple = simplify_function(expr->opfuncid,
+										   expr->opresulttype, -1,
+										   expr->opcollid,
+										   expr->inputcollid,
+										   &args,
+										   false,
+										   false,
+										   true,
+										   context,
+										   COERCE_EXPLICIT_CALL,
+										   expr->opno,
+										   expr->location);
+				if (simple) /* successfully simplified it */
+				{
+					if (IsA(simple, Const))
 					{
 						/*
 						 * Since the underlying operator is "=", must negate
@@ -2846,6 +2955,31 @@ eval_const_expressions_mutator(Node *node,
 							BoolGetDatum(!DatumGetBool(csimple->constvalue));
 						return (Node *) csimple;
 					}
+
+					/* Else CachedExpr */
+					if (!context->estimate)		/* sanity checks */
+					{
+						/*
+						 * Cache DistinctExpr using information from its cached
+						 * operator.
+						 */
+						CachedExpr *csimple = castNode(CachedExpr, simple);
+						OpExpr	   *subexpr = castNode(OpExpr,
+													   csimple->subexpr);
+						DistinctExpr *newsubexpr = makeNode(DistinctExpr);
+
+						newsubexpr->opno = subexpr->opno;
+						newsubexpr->opfuncid = subexpr->opfuncid;
+						newsubexpr->opresulttype = subexpr->opresulttype;
+						newsubexpr->opretset = subexpr->opretset;
+						newsubexpr->opcollid = subexpr->opcollid;
+						newsubexpr->inputcollid = subexpr->inputcollid;
+						newsubexpr->args = subexpr->args;
+						newsubexpr->location = subexpr->location;
+
+						csimple->subexpr = (CacheableExpr *) newsubexpr;
+						return (Node *) simple;
+					}
 				}
 
 				/*
@@ -2864,24 +2998,180 @@ eval_const_expressions_mutator(Node *node,
 				newexpr->location = expr->location;
 				return (Node *) newexpr;
 			}
+		case T_NullIfExpr:
+			{
+				NullIfExpr *expr = (NullIfExpr *) node;
+				List	   *args = expr->args;
+				ListCell   *arg;
+				bool		has_null_input = false;
+				bool		has_nonconst_input = false;
+				Expr	   *simple;
+				NullIfExpr *newexpr;
+
+				/*
+				 * Reduce constants in the NullIfExpr's arguments.  We know
+				 * args is either NIL or a List node, so we can call
+				 * expression_tree_mutator directly rather than recursing to
+				 * self.
+				 */
+				args = (List *) expression_tree_mutator((Node *) expr->args,
+														eval_const_expressions_mutator,
+														(void *) context);
+
+				/*
+				 * We must do our own check for NULLs because NullIfExpr has
+				 * different results for NULL input than the underlying
+				 * operator does.
+				 */
+				foreach(arg, args)
+				{
+					if (IsA(lfirst(arg), Const))
+						has_null_input |= ((Const *) lfirst(arg))->constisnull;
+					else
+						has_nonconst_input = true;
+				}
+
+				/*
+				 * All constants and has null? Then const arguments aren't
+				 * equal, so return the first one.
+				 */
+				if (!has_nonconst_input && has_null_input)
+					return linitial(args);
+
+				/* Try to evaluate the '=' operator */
+				/* (NOT okay to try to inline it, though!) */
+
+				/*
+				 * Need to get OID of underlying function.  Okay to scribble
+				 * on input to this extent.
+				 */
+				set_opfuncid((OpExpr *) expr);	/* rely on struct
+												 * equivalence */
+
+				/*
+				 * Code for op/func reduction is pretty bulky, so split it out
+				 * as a separate function.
+				 */
+				simple = simplify_function(expr->opfuncid,
+										   BOOLOID, -1,
+										   InvalidOid,
+										   expr->inputcollid,
+										   &args,
+										   false,
+										   false,
+										   true,
+										   context,
+										   COERCE_EXPLICIT_CALL,
+										   expr->opno,
+										   expr->location);
+
+				if (simple) /* successfully simplified it */
+				{
+					if (IsA(simple, Const))
+					{
+						/*
+						 * Since the underlying operator is "=", return the
+						 * first argument if the arguments are not equal and
+						 * NULL otherwise.
+						 */
+						Const	   *csimple = castNode(Const, simple);
+
+						if (DatumGetBool(csimple->constvalue))
+						{
+							return (Node *) makeNullConst(
+													expr->opresulttype,
+													exprTypmod(linitial(args)),
+													expr->opcollid);
+						}
+						else
+						{
+							return linitial(args);
+						}
+					}
+
+					/* Else CachedExpr */
+					if (!context->estimate)		/* sanity checks */
+					{
+						/*
+						 * Cache NullIfExpr using information from its cached
+						 * operator.
+						 */
+						CachedExpr *csimple = castNode(CachedExpr, simple);
+						OpExpr	   *subexpr = castNode(OpExpr,
+													   csimple->subexpr);
+						NullIfExpr *newsubexpr = makeNode(NullIfExpr);
+
+						newsubexpr->opno = subexpr->opno;
+						newsubexpr->opfuncid = subexpr->opfuncid;
+						newsubexpr->opresulttype = expr->opresulttype;
+						newsubexpr->opretset = subexpr->opretset;
+						newsubexpr->opcollid = expr->opcollid;
+						newsubexpr->inputcollid = subexpr->inputcollid;
+						newsubexpr->args = subexpr->args;
+						newsubexpr->location = subexpr->location;
+
+						csimple->subexpr = (CacheableExpr *) newsubexpr;
+						return (Node *) simple;
+					}
+				}
+
+				/*
+				 * The expression cannot be simplified any further, so build
+				 * and return a replacement NullIfExpr node using the
+				 * possibly-simplified arguments.
+				 */
+				newexpr = makeNode(NullIfExpr);
+				newexpr->opno = expr->opno;
+				newexpr->opfuncid = expr->opfuncid;
+				newexpr->opresulttype = expr->opresulttype;
+				newexpr->opretset = expr->opretset;
+				newexpr->opcollid = expr->opcollid;
+				newexpr->inputcollid = expr->inputcollid;
+				newexpr->args = args;
+				newexpr->location = expr->location;
+				return (Node *) newexpr;
+			}
 		case T_ScalarArrayOpExpr:
+		case T_RowCompareExpr:
 			{
-				ScalarArrayOpExpr *saop;
+				/*
+				 * Generic handling for node types whose own processing checks
+				 * that their functions and arguments are safe for evaluation or
+				 * caching.
+				 */
+				ece_check_node_safety_detailed args_detailed,
+											   func_detailed;
 
-				/* Copy the node and const-simplify its arguments */
-				saop = (ScalarArrayOpExpr *) ece_generic_processing(node);
+				/* Copy the node and simplify its arguments */
+				node = ece_generic_processing(node);
 
-				/* Make sure we know underlying function */
-				set_sa_opfuncid(saop);
+				/* Check node args and functions */
+				args_detailed = ece_all_arguments_const(node);
+				func_detailed = ece_functions_are_safe(node, false,
+													   context->estimate);
 
 				/*
-				 * If all arguments are Consts, and it's a safe function, we
-				 * can fold to a constant
+				 * If all arguments are Consts, and node functions are safe for
+				 * evaluation, we can fold to a constant
 				 */
-				if (ece_all_arguments_const(saop) &&
-					ece_function_is_safe(saop->opfuncid, context))
-					return ece_evaluate_expr(saop);
-				return (Node *) saop;
+				if (args_detailed == SAFE_FOR_EVALUATION &&
+					func_detailed == SAFE_FOR_EVALUATION)
+					return ece_evaluate_expr(node);
+
+				/*
+				 * If we do not perform estimation, all arguments are Consts or
+				 * CachedExprs, and node functions are safe for caching, we can
+				 * fold to a CachedExpr
+				 */
+				if (!context->estimate &&
+					SAFE_FOR_CACHING(args_detailed) &&
+					SAFE_FOR_CACHING(func_detailed))
+				{
+					return (Node *) get_cached_expr_node(
+													(CacheableExpr *) node);
+				}
+
+				return node;
 			}
 		case T_BoolExpr:
 			{
@@ -2894,11 +3184,14 @@ eval_const_expressions_mutator(Node *node,
 							List	   *newargs;
 							bool		haveNull = false;
 							bool		forceTrue = false;
-
-							newargs = simplify_or_arguments(expr->args,
-															context,
-															&haveNull,
-															&forceTrue);
+							bool		all_consts_or_cached = true;
+
+							newargs = simplify_or_arguments(
+														expr->args,
+														context,
+														&haveNull,
+														&forceTrue,
+														&all_consts_or_cached);
 							if (forceTrue)
 								return makeBoolConst(true, false);
 							if (haveNull)
@@ -2913,20 +3206,37 @@ eval_const_expressions_mutator(Node *node,
 							 * result
 							 */
 							if (list_length(newargs) == 1)
+							{
 								return (Node *) linitial(newargs);
-							/* Else we still need an OR node */
-							return (Node *) make_orclause(newargs);
+							}
+							else
+							{
+								/* We still need an OR node */
+								Expr	   *newexpr = make_orclause(newargs);
+
+								/* Check if we can cache the new expression */
+								if (!context->estimate &&
+									all_consts_or_cached)
+								{
+									return (Node *) get_cached_expr_node(
+													(CacheableExpr *) newexpr);
+								}
+								return (Node *) newexpr;
+							}
 						}
 					case AND_EXPR:
 						{
 							List	   *newargs;
 							bool		haveNull = false;
 							bool		forceFalse = false;
-
-							newargs = simplify_and_arguments(expr->args,
-															 context,
-															 &haveNull,
-															 &forceFalse);
+							bool		all_consts_or_cached = true;
+
+							newargs = simplify_and_arguments(
+														expr->args,
+														context,
+														&haveNull,
+														&forceFalse,
+														&all_consts_or_cached);
 							if (forceFalse)
 								return makeBoolConst(false, false);
 							if (haveNull)
@@ -2941,13 +3251,28 @@ eval_const_expressions_mutator(Node *node,
 							 * result
 							 */
 							if (list_length(newargs) == 1)
+							{
 								return (Node *) linitial(newargs);
-							/* Else we still need an AND node */
-							return (Node *) make_andclause(newargs);
+							}
+							else
+							{
+								/* We still need an AND node */
+								Expr	   *newexpr = make_andclause(newargs);
+
+								/* Check if we can cache the new expression */
+								if (!context->estimate &&
+									all_consts_or_cached)
+								{
+									return (Node *) get_cached_expr_node(
+													(CacheableExpr *) newexpr);
+								}
+								return (Node *) newexpr;
+							}
 						}
 					case NOT_EXPR:
 						{
 							Node	   *arg;
+							Expr	   *newexpr;
 
 							Assert(list_length(expr->args) == 1);
 							arg = eval_const_expressions_mutator(linitial(expr->args),
@@ -2957,7 +3282,24 @@ eval_const_expressions_mutator(Node *node,
 							 * Use negate_clause() to see if we can simplify
 							 * away the NOT.
 							 */
-							return negate_clause(arg);
+							newexpr = (Expr *) negate_clause(arg);
+
+							if ((IsA(newexpr, BoolExpr) &&
+								 ((BoolExpr *) newexpr)->boolop == NOT_EXPR) ||
+								IsA(newexpr, CachedExpr))
+							{
+								/*
+								 * Simplification did not help and we again got
+								 * the NOT node, or the expression is already
+								 * cached.
+								 */
+								return (Node *) newexpr;
+							}
+
+							/* Try to simplify the whole new expression */
+							return eval_const_expressions_mutator(
+															(Node *) newexpr,
+															context);
 						}
 					default:
 						elog(ERROR, "unrecognized boolop: %d",
@@ -2982,7 +3324,8 @@ eval_const_expressions_mutator(Node *node,
 				 * If we can simplify the input to a constant, then we don't
 				 * need the RelabelType node anymore: just change the type
 				 * field of the Const node.  Otherwise, must copy the
-				 * RelabelType node.
+				 * RelabelType node; cache it if its arg is also cached and we
+				 * do not perform estimation.
 				 */
 				RelabelType *relabel = (RelabelType *) node;
 				Node	   *arg;
@@ -3016,6 +3359,13 @@ eval_const_expressions_mutator(Node *node,
 					newrelabel->resultcollid = relabel->resultcollid;
 					newrelabel->relabelformat = relabel->relabelformat;
 					newrelabel->location = relabel->location;
+
+					/* Check if we can cache this node */
+					if (!context->estimate && arg && IsA(arg, CachedExpr))
+					{
+						return (Node *) get_cached_expr_node(
+												(CacheableExpr *) newrelabel);
+					}
 					return (Node *) newrelabel;
 				}
 			}
@@ -3029,6 +3379,7 @@ eval_const_expressions_mutator(Node *node,
 				Oid			intypioparam;
 				Expr	   *simple;
 				CoerceViaIO *newexpr;
+				bool		cache = false;
 
 				/* Make a List so we can use simplify_function */
 				args = list_make1(expr->arg);
@@ -3054,8 +3405,11 @@ eval_const_expressions_mutator(Node *node,
 										   &args,
 										   false,
 										   true,
-										   true,
-										   context);
+										   false,
+										   context,
+										   COERCE_EXPLICIT_CALL,
+										   InvalidOid,
+										   -1);
 				if (simple)		/* successfully simplified output fn */
 				{
 					/*
@@ -3086,10 +3440,23 @@ eval_const_expressions_mutator(Node *node,
 											   &args,
 											   false,
 											   false,
-											   true,
-											   context);
+											   false,
+											   context,
+											   COERCE_EXPLICIT_CALL,
+											   InvalidOid,
+											   -1);
 					if (simple) /* successfully simplified input fn */
-						return (Node *) simple;
+					{
+						if (IsA(simple, CachedExpr))
+						{
+							/* return later cached CoerceViaIO node */
+							cache = true;
+						}
+						else
+						{
+							return (Node *) simple;
+						}
+					}
 				}
 
 				/*
@@ -3103,27 +3470,50 @@ eval_const_expressions_mutator(Node *node,
 				newexpr->resultcollid = expr->resultcollid;
 				newexpr->coerceformat = expr->coerceformat;
 				newexpr->location = expr->location;
+				/* Check if we can cache the new expression */
+				if (cache)
+				{
+					return (Node *) get_cached_expr_node(
+													(CacheableExpr *) newexpr);
+				}
 				return (Node *) newexpr;
 			}
 		case T_ArrayCoerceExpr:
 			{
 				ArrayCoerceExpr *ac;
+				ece_check_node_safety_detailed func_detailed;
 
-				/* Copy the node and const-simplify its arguments */
+				/* Copy the node and simplify its arguments */
 				ac = (ArrayCoerceExpr *) ece_generic_processing(node);
 
+				/* Check the functions in the per-element expression */
+				func_detailed = ece_functions_are_safe((Node *) ac->elemexpr,
+													   true, false);
+
 				/*
 				 * If constant argument and the per-element expression is
 				 * immutable, we can simplify the whole thing to a constant.
-				 * Exception: although contain_mutable_functions considers
+				 * Exception: although ece_functions_are_safe considers
 				 * CoerceToDomain immutable for historical reasons, let's not
 				 * do so here; this ensures coercion to an array-over-domain
 				 * does not apply the domain's constraints until runtime.
 				 */
 				if (ac->arg && IsA(ac->arg, Const) &&
 					ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) &&
-					!contain_mutable_functions((Node *) ac->elemexpr))
+					func_detailed == SAFE_FOR_EVALUATION)
 					return ece_evaluate_expr(ac);
+
+				/*
+				 * If not estimation mode, constant or cached argument, and the
+				 * per-element expression is immutable or stable, we can cache
+				 * the whole thing.
+				 */
+				if (!context->estimate &&
+					is_const_or_cached((Node *) ac->arg) &&
+					ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) &&
+					SAFE_FOR_CACHING(func_detailed))
+					return (Node *) get_cached_expr_node((CacheableExpr *) ac);
+
 				return (Node *) ac;
 			}
 		case T_CollateExpr:
@@ -3169,7 +3559,9 @@ eval_const_expressions_mutator(Node *node,
 						arg = (Node *) ((RelabelType *) arg)->arg;
 					relabel->arg = (Expr *) arg;
 
-					return (Node *) relabel;
+					/* Try to cache the new expression */
+					return eval_const_expressions_mutator((Node *) relabel,
+														  context);
 				}
 			}
 		case T_CaseExpr:
@@ -3202,6 +3594,10 @@ eval_const_expressions_mutator(Node *node,
 				 * expression when executing the CASE, since any contained
 				 * CaseTestExprs that might have referred to it will have been
 				 * replaced by the constant.
+				 *
+				 * If we do not perform estimation and  all expressions in the
+				 * CASE expression are constant or cached, the CASE expression
+				 * will also be cached.
 				 *----------
 				 */
 				CaseExpr   *caseexpr = (CaseExpr *) node;
@@ -3212,6 +3608,7 @@ eval_const_expressions_mutator(Node *node,
 				bool		const_true_cond;
 				Node	   *defresult = NULL;
 				ListCell   *arg;
+				bool		all_subexpr_const_or_cached = true;
 
 				/* Simplify the test expression, if any */
 				newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
@@ -3224,8 +3621,17 @@ eval_const_expressions_mutator(Node *node,
 					context->case_val = newarg;
 					newarg = NULL;	/* not needed anymore, see above */
 				}
+				else if (newarg && IsA(newarg, CachedExpr))
+				{
+					/* create dummy CachedExpr node */
+					context->case_val = (Node *) get_cached_expr_node(NULL);
+				}
 				else
+				{
 					context->case_val = NULL;
+					if (newarg)
+						all_subexpr_const_or_cached = false;
+				}
 
 				/* Simplify the WHEN clauses */
 				newargs = NIL;
@@ -3240,26 +3646,43 @@ eval_const_expressions_mutator(Node *node,
 					casecond = eval_const_expressions_mutator((Node *) oldcasewhen->expr,
 															  context);
 
-					/*
-					 * If the test condition is constant FALSE (or NULL), then
-					 * drop this WHEN clause completely, without processing
-					 * the result.
-					 */
-					if (casecond && IsA(casecond, Const))
+					if (casecond)
 					{
-						Const	   *const_input = (Const *) casecond;
-
-						if (const_input->constisnull ||
-							!DatumGetBool(const_input->constvalue))
-							continue;	/* drop alternative with FALSE cond */
-						/* Else it's constant TRUE */
-						const_true_cond = true;
+						/*
+						 * If the test condition is constant FALSE (or NULL),
+						 * then drop this WHEN clause completely, without
+						 * processing the result.
+						 */
+						if (IsA(casecond, Const))
+						{
+							Const	   *const_input = (Const *) casecond;
+
+							if (const_input->constisnull ||
+								!DatumGetBool(const_input->constvalue))
+							{
+								/* Drop alternative with FALSE cond */
+								continue;
+							}
+							/* Else it's constant TRUE */
+							const_true_cond = true;
+						}
+						else if (IsA(casecond, CachedExpr))
+						{
+							/* OK */
+						}
+						else
+						{
+							all_subexpr_const_or_cached = false;
+						}
 					}
 
 					/* Simplify this alternative's result value */
 					caseresult = eval_const_expressions_mutator((Node *) oldcasewhen->result,
 																context);
 
+					all_subexpr_const_or_cached &=
+						is_const_or_cached(caseresult);
+
 					/* If non-constant test condition, emit a new WHEN node */
 					if (!const_true_cond)
 					{
@@ -3283,9 +3706,14 @@ eval_const_expressions_mutator(Node *node,
 
 				/* Simplify the default result, unless we replaced it above */
 				if (!const_true_cond)
+				{
 					defresult = eval_const_expressions_mutator((Node *) caseexpr->defresult,
 															   context);
 
+					all_subexpr_const_or_cached &=
+						is_const_or_cached(defresult);
+				}
+
 				context->case_val = save_case_val;
 
 				/*
@@ -3302,35 +3730,67 @@ eval_const_expressions_mutator(Node *node,
 				newcase->args = newargs;
 				newcase->defresult = (Expr *) defresult;
 				newcase->location = caseexpr->location;
+				/* Check if we can cache this node */
+				if (!context->estimate && all_subexpr_const_or_cached)
+				{
+					return (Node *) get_cached_expr_node(
+													(CacheableExpr *) newcase);
+				}
 				return (Node *) newcase;
 			}
 		case T_CaseTestExpr:
 			{
+				Node	   *case_val = context->case_val;
+
 				/*
-				 * If we know a constant test value for the current CASE
+				 * If we know that the test node for the current CASE is cached,
+				 * return the cached current node.
+				 * Else if we know a constant test value for the current CASE
 				 * construct, substitute it for the placeholder.  Else just
 				 * return the placeholder as-is.
 				 */
-				if (context->case_val)
-					return copyObject(context->case_val);
-				else
-					return copyObject(node);
+				if (case_val)
+				{
+					if (IsA(case_val, CachedExpr))
+					{
+						return (Node *) get_cached_expr_node(
+											(CacheableExpr *) copyObject(node));
+					}
+					return copyObject(case_val);
+				}
+				return copyObject(node);
 			}
 		case T_ArrayRef:
 		case T_ArrayExpr:
 		case T_RowExpr:
+		case T_ConvertRowtypeExpr:
+		case T_MinMaxExpr:
+		case T_XmlExpr:
 			{
 				/*
 				 * Generic handling for node types whose own processing is
 				 * known to be immutable, and for which we need no smarts
-				 * beyond "simplify if all inputs are constants".
+				 * beyond "simplify if all inputs are constants or cached
+				 * expressions".
 				 */
+				ece_check_node_safety_detailed args_detailed;
 
-				/* Copy the node and const-simplify its arguments */
+				/* Copy the node and simplify its arguments */
 				node = ece_generic_processing(node);
 				/* If all arguments are Consts, we can fold to a constant */
-				if (ece_all_arguments_const(node))
+				args_detailed = ece_all_arguments_const(node);
+				if (args_detailed == SAFE_FOR_EVALUATION)
 					return ece_evaluate_expr(node);
+				/*
+				 * If we do not perform estimation, and all arguments are Consts
+				 * or CachedExprs, we can cache the result of this node.
+				 */
+				if (!context->estimate &&
+					args_detailed == SAFE_FOR_CACHING_ONLY)
+				{
+					return (Node *) get_cached_expr_node(
+													(CacheableExpr *) node);
+				}
 				return node;
 			}
 		case T_CoalesceExpr:
@@ -3339,6 +3799,7 @@ eval_const_expressions_mutator(Node *node,
 				CoalesceExpr *newcoalesce;
 				List	   *newargs;
 				ListCell   *arg;
+				bool		all_args_are_const_or_cached = true;
 
 				newargs = NIL;
 				foreach(arg, coalesceexpr->args)
@@ -3365,6 +3826,14 @@ eval_const_expressions_mutator(Node *node,
 						newargs = lappend(newargs, e);
 						break;
 					}
+					else if (IsA(e, CachedExpr))
+					{
+						/* OK */
+					}
+					else
+					{
+						all_args_are_const_or_cached = false;
+					}
 					newargs = lappend(newargs, e);
 				}
 
@@ -3382,6 +3851,12 @@ eval_const_expressions_mutator(Node *node,
 				newcoalesce->coalescecollid = coalesceexpr->coalescecollid;
 				newcoalesce->args = newargs;
 				newcoalesce->location = coalesceexpr->location;
+				/* Check if we can cache this node */
+				if (!context->estimate && all_args_are_const_or_cached)
+				{
+					return (Node *) get_cached_expr_node(
+												(CacheableExpr *) newcoalesce);
+				}
 				return (Node *) newcoalesce;
 			}
 		case T_SQLValueFunction:
@@ -3389,7 +3864,7 @@ eval_const_expressions_mutator(Node *node,
 				/*
 				 * All variants of SQLValueFunction are stable, so if we are
 				 * estimating the expression's value, we should evaluate the
-				 * current function value.  Otherwise just copy.
+				 * current function value.  Otherwise copy and cache it.
 				 */
 				SQLValueFunction *svf = (SQLValueFunction *) node;
 
@@ -3399,7 +3874,8 @@ eval_const_expressions_mutator(Node *node,
 												  svf->typmod,
 												  InvalidOid);
 				else
-					return copyObject((Node *) svf);
+					return (Node *) get_cached_expr_node(
+											(CacheableExpr *) copyObject(node));
 			}
 		case T_FieldSelect:
 			{
@@ -3487,6 +3963,13 @@ eval_const_expressions_mutator(Node *node,
 											  newfselect->resultcollid))
 						return ece_evaluate_expr(newfselect);
 				}
+				/* Check if we can cache this node */
+				if (!context->estimate &&
+					arg && (IsA(arg, Const) || IsA(arg, CachedExpr)))
+				{
+					return (Node *) get_cached_expr_node(
+												(CacheableExpr *) newfselect);
+				}
 				return (Node *) newfselect;
 			}
 		case T_NullTest:
@@ -3494,10 +3977,28 @@ eval_const_expressions_mutator(Node *node,
 				NullTest   *ntest = (NullTest *) node;
 				NullTest   *newntest;
 				Node	   *arg;
+				RowExpr    *rarg = NULL;
+				bool		rarg_cached = false;
 
 				arg = eval_const_expressions_mutator((Node *) ntest->arg,
 													 context);
-				if (ntest->argisrow && arg && IsA(arg, RowExpr))
+
+				/* Check if arg is RowExpr or cached RowExpr */
+				if (arg && IsA(arg, RowExpr))
+				{
+					rarg = (RowExpr *) arg;
+				}
+				else if (arg && IsA(arg, CachedExpr))
+				{
+					CacheableExpr *subexpr = ((CachedExpr *) arg)->subexpr;
+					if (IsA(subexpr, RowExpr))
+					{
+						rarg = (RowExpr *) subexpr;
+						rarg_cached = true;
+					}
+				}
+
+				if (ntest->argisrow && rarg)
 				{
 					/*
 					 * We break ROW(...) IS [NOT] NULL into separate tests on
@@ -3505,7 +4006,6 @@ eval_const_expressions_mutator(Node *node,
 					 * efficient to evaluate, as well as being more amenable
 					 * to optimization.
 					 */
-					RowExpr    *rarg = (RowExpr *) arg;
 					List	   *newargs = NIL;
 					ListCell   *l;
 
@@ -3546,9 +4046,27 @@ eval_const_expressions_mutator(Node *node,
 						return makeBoolConst(true, false);
 					/* If only one nonconst input, it's the result */
 					if (list_length(newargs) == 1)
+					{
 						return (Node *) linitial(newargs);
-					/* Else we need an AND node */
-					return (Node *) make_andclause(newargs);
+					}
+					else
+					{
+						/* We need an AND node */
+						Node	   *newnode = (Node *) make_andclause(newargs);
+
+						/*
+						 * We can cache the result if we do not perform
+						 * estimation, and the input also was cached (since only
+						 * the const args were ommitted and they do
+						 * not change this).
+						 */
+						if (!context->estimate && rarg_cached)
+						{
+							return (Node *) get_cached_expr_node(
+													(CacheableExpr *) newnode);
+						}
+						return newnode;
+					}
 				}
 				if (!ntest->argisrow && arg && IsA(arg, Const))
 				{
@@ -3578,6 +4096,12 @@ eval_const_expressions_mutator(Node *node,
 				newntest->nulltesttype = ntest->nulltesttype;
 				newntest->argisrow = ntest->argisrow;
 				newntest->location = ntest->location;
+				/* Check if we can cache this node */
+				if (!context->estimate && is_const_or_cached(arg))
+				{
+					return (Node *) get_cached_expr_node(
+													(CacheableExpr *) newntest);
+				}
 				return (Node *) newntest;
 			}
 		case T_BooleanTest:
@@ -3638,6 +4162,12 @@ eval_const_expressions_mutator(Node *node,
 				newbtest->arg = (Expr *) arg;
 				newbtest->booltesttype = btest->booltesttype;
 				newbtest->location = btest->location;
+				/* Check if we can cache this node */
+				if (!context->estimate && is_const_or_cached(arg))
+				{
+					return (Node *) get_cached_expr_node(
+													(CacheableExpr *) newbtest);
+				}
 				return (Node *) newbtest;
 			}
 		case T_PlaceHolderVar:
@@ -3657,13 +4187,23 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_CachedExpr:
+			{
+				/*
+				 * Process it, because maybe we can get the Const node or we
+				 * perform estimation and there should be no cached expressions.
+				 */
+				return eval_const_expressions_mutator(
+										(Node *) ((CachedExpr *) node)->subexpr,
+										context);
+			}
 		default:
 			break;
 	}
 
 	/*
 	 * For any node type not handled above, copy the node unchanged but
-	 * const-simplify its subexpressions.  This is the correct thing for node
+	 * simplify its subexpressions.  This is the correct thing for node
 	 * types whose behavior might change between planning and execution, such
 	 * as CoerceToDomain.  It's also a safe default for new node types not
 	 * known to this routine.
@@ -3672,33 +4212,80 @@ eval_const_expressions_mutator(Node *node,
 }
 
 /*
- * Subroutine for eval_const_expressions: check for non-Const nodes.
+ * Subroutine for eval_const_expressions: check for non-Const or non-CachedExpr
+ * nodes.
  *
- * We can abort recursion immediately on finding a non-Const node.  This is
+ * We can abort recursion immediately on finding a non-Const and non-CachedExpr
+ * node.  This is
  * critical for performance, else eval_const_expressions_mutator would take
  * O(N^2) time on non-simplifiable trees.  However, we do need to descend
  * into List nodes since expression_tree_walker sometimes invokes the walker
  * function directly on List subtrees.
+ *
+ * The output argument *detailed must be initialized SAFE_FOR_EVALUATION by the
+ * caller. It will be set SAFE_FOR_CACHING_ONLY if only constant and cached
+ * nodes are detected anywhere in the argument list. It will be set
+ * SAFE_FOR_NOTHING if a non-constant or non-cached node is detected anywhere in
+ * the argument list.
  */
 static bool
-contain_non_const_walker(Node *node, void *context)
+contain_non_const_walker(Node *node, ece_check_node_safety_detailed *detailed)
 {
 	if (node == NULL)
+	{
+		/* this does not affect the value of the detailed result */
 		return false;
+	}
 	if (IsA(node, Const))
+	{
+		/* this does not affect the value of the detailed result */
 		return false;
+	}
+	if (IsA(node, CachedExpr))
+	{
+		*detailed = SAFE_FOR_CACHING_ONLY;
+		return false;
+	}
 	if (IsA(node, List))
-		return expression_tree_walker(node, contain_non_const_walker, context);
+	{
+		return expression_tree_walker(node, contain_non_const_walker,
+									  (void *) detailed);
+	}
 	/* Otherwise, abort the tree traversal and return true */
+	*detailed = SAFE_FOR_NOTHING;
 	return true;
 }
 
 /*
- * Subroutine for eval_const_expressions: check if a function is OK to evaluate
+ * ece_functions_are_safe
+ *	  Search for noncacheable functions within a clause (possibly recursively).
+ *
+ *	  Returns true if any non-cacheable function found.
+ *
+ * The output argument context->detailed must be initialized
+ * SAFE_FOR_EVALUATION by the caller. It will be set SAFE_FOR_CACHING_ONLY if
+ * only immutable functions (or also stable functions in case of estimation)
+ * are found. It will be set SAFE_FOR_NOTHING if a volatile function is detected
+ * anywhere in the subexpressions.
  */
+static ece_check_node_safety_detailed
+ece_functions_are_safe(Node *node, bool recurse, bool estimate)
+{
+	ece_functions_are_safe_context context;
+
+	context.detailed = SAFE_FOR_EVALUATION;
+	context.recurse = recurse;
+	context.estimate = estimate;
+
+	ece_functions_are_safe_walker(node, &context);
+	return context.detailed;
+}
+
 static bool
-ece_function_is_safe(Oid funcid, eval_const_expressions_context *context)
+ece_functions_are_safe_checker(Oid funcid, void *context)
 {
+	ece_functions_are_safe_context *safe_context =
+		(ece_functions_are_safe_context *) context;
 	char		provolatile = func_volatile(funcid);
 
 	/*
@@ -3709,10 +4296,74 @@ ece_function_is_safe(Oid funcid, eval_const_expressions_context *context)
 	 * to estimate the value at all.
 	 */
 	if (provolatile == PROVOLATILE_IMMUTABLE)
+	{
+		/* this does not affect the value of the detailed result */
+		return false;
+	}
+	else if (provolatile == PROVOLATILE_STABLE)
+	{
+		if (safe_context->estimate)
+		{
+			/* this does not affect the value of the detailed result */
+			return false;
+		}
+		else
+		{
+			safe_context->detailed = SAFE_FOR_CACHING_ONLY;
+			return false;
+		}
+	}
+	else
+	{
+		safe_context->detailed = SAFE_FOR_NOTHING;
 		return true;
-	if (context->estimate && provolatile == PROVOLATILE_STABLE)
+	}
+}
+
+static bool
+ece_functions_are_safe_walker(Node *node,
+							  ece_functions_are_safe_context *context)
+{
+	if (node == NULL)
+		return false;
+	/* Check for functions in node itself */
+	if (check_functions_in_node(node, ece_functions_are_safe_checker,
+								(void *) context))
 		return true;
-	return false;
+
+	if (IsA(node, SQLValueFunction))
+	{
+		/* all variants of SQLValueFunction are stable */
+		if (!context->estimate)
+			context->detailed = SAFE_FOR_CACHING_ONLY;
+		return false;
+	}
+
+	if (IsA(node, NextValueExpr))
+	{
+		/* NextValueExpr is volatile */
+		context->detailed = SAFE_FOR_NOTHING;
+		return true;
+	}
+
+	/*
+	 * See notes in contain_mutable_functions_walker about why we treat
+	 * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable.  Hence, immutable
+	 * functions do not affect the value of the detailed result.
+	 */
+
+	if (!context->recurse)
+		return false;
+
+	/* Recurse to check arguments */
+	if (IsA(node, Query))
+	{
+		/* Recurse into subselects */
+		return query_tree_walker((Query *) node, ece_functions_are_safe_walker,
+								 (void *) context, 0);
+	}
+	return expression_tree_walker(node, ece_functions_are_safe_walker,
+								  (void *) context);
 }
 
 /*
@@ -3732,12 +4383,16 @@ ece_function_is_safe(Oid funcid, eval_const_expressions_context *context)
  *
  * The output arguments *haveNull and *forceTrue must be initialized false
  * by the caller.  They will be set true if a NULL constant or TRUE constant,
- * respectively, is detected anywhere in the argument list.
+ * respectively, is detected anywhere in the argument list. The output argument
+ * *all_consts_or_cached must be initialized true by the caller. It will be set
+ * false if a non-constant or non-cached node is detected anywhere in the
+ * argument list.
  */
 static List *
 simplify_or_arguments(List *args,
 					  eval_const_expressions_context *context,
-					  bool *haveNull, bool *forceTrue)
+					  bool *haveNull, bool *forceTrue,
+					  bool *all_consts_or_cached)
 {
 	List	   *newargs = NIL;
 	List	   *unprocessed_args;
@@ -3819,6 +4474,14 @@ simplify_or_arguments(List *args,
 			/* otherwise, we can drop the constant-false input */
 			continue;
 		}
+		else if (IsA(arg, CachedExpr))
+		{
+			/* OK */
+		}
+		else
+		{
+			*all_consts_or_cached = false;
+		}
 
 		/* else emit the simplified arg into the result list */
 		newargs = lappend(newargs, arg);
@@ -3844,12 +4507,16 @@ simplify_or_arguments(List *args,
  *
  * The output arguments *haveNull and *forceFalse must be initialized false
  * by the caller.  They will be set true if a null constant or false constant,
- * respectively, is detected anywhere in the argument list.
+ * respectively, is detected anywhere in the argument list. The output argument
+ * *all_consts_or_cached must be initialized true by the caller. It will be set
+ * false if a non-constant or non-cached node is detected anywhere in the
+ * argument list.
  */
 static List *
 simplify_and_arguments(List *args,
 					   eval_const_expressions_context *context,
-					   bool *haveNull, bool *forceFalse)
+					   bool *haveNull, bool *forceFalse,
+					   bool *all_consts_or_cached)
 {
 	List	   *newargs = NIL;
 	List	   *unprocessed_args;
@@ -3921,6 +4588,14 @@ simplify_and_arguments(List *args,
 			/* otherwise, we can drop the constant-true input */
 			continue;
 		}
+		else if (IsA(arg, CachedExpr))
+		{
+			/* OK */
+		}
+		else
+		{
+			*all_consts_or_cached = false;
+		}
 
 		/* else emit the simplified arg into the result list */
 		newargs = lappend(newargs, arg);
@@ -4001,8 +4676,10 @@ simplify_boolean_equality(Oid opno, List *args)
  * Inputs are the function OID, actual result type OID (which is needed for
  * polymorphic functions), result typmod, result collation, the input
  * collation to use for the function, the original argument list (not
- * const-simplified yet, unless process_args is false), and some flags;
- * also the context data for eval_const_expressions.
+ * simplified yet, unless process_args is false), the coercion form of the
+ * function output, the operator OID (InvalidOid if the function itself), the
+ * location (used only for simple caching), and some flags; also the context
+ * data for eval_const_expressions.
  *
  * Returns a simplified expression if successful, or NULL if cannot
  * simplify the function call.
@@ -4011,15 +4688,17 @@ simplify_boolean_equality(Oid opno, List *args)
  * lists into positional notation and/or adding any needed default argument
  * expressions; which is a bit grotty, but it avoids extra fetches of the
  * function's pg_proc tuple.  For this reason, the args list is
- * pass-by-reference.  Conversion and const-simplification of the args list
+ * pass-by-reference.  Conversion and simplification of the args list
  * will be done even if simplification of the function call itself is not
  * possible.
  */
 static Expr *
 simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 				  Oid result_collid, Oid input_collid, List **args_p,
-				  bool funcvariadic, bool process_args, bool allow_non_const,
-				  eval_const_expressions_context *context)
+				  bool funcvariadic, bool process_args,
+				  bool allow_only_consts_and_simple_caching,
+				  eval_const_expressions_context *context,
+				  CoercionForm funcformat, Oid opno, int location)
 {
 	List	   *args = *args_p;
 	HeapTuple	func_tuple;
@@ -4027,16 +4706,18 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 	Expr	   *newexpr;
 
 	/*
-	 * We have three strategies for simplification: execute the function to
+	 * We have four strategies for simplification: execute the function to
 	 * deliver a constant result, use a transform function to generate a
-	 * substitute node tree, or expand in-line the body of the function
+	 * substitute node tree, expand in-line the body of the function
 	 * definition (which only works for simple SQL-language functions, but
-	 * that is a common case).  Each case needs access to the function's
-	 * pg_proc tuple, so fetch it just once.
+	 * that is a common case), or cache this function call.
+	 * Each case needs access to the function's pg_proc tuple, so fetch it just
+	 * once.
 	 *
-	 * Note: the allow_non_const flag suppresses both the second and third
-	 * strategies; so if !allow_non_const, simplify_function can only return a
-	 * Const or NULL.  Argument-list rewriting happens anyway, though.
+	 * Note: the allow_only_consts_and_simple_caching flag suppresses both the
+	 * second and third strategies; so if allow_only_consts_and_simple_caching,
+	 * simplify_function can only return a Const, CachedExpr or NULL.
+	 * Argument-list rewriting happens anyway, though.
 	 */
 	func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
 	if (!HeapTupleIsValid(func_tuple))
@@ -4066,7 +4747,9 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 								args, funcvariadic,
 								func_tuple, context);
 
-	if (!newexpr && allow_non_const && OidIsValid(func_form->protransform))
+	if (!newexpr &&
+		!allow_only_consts_and_simple_caching &&
+		OidIsValid(func_form->protransform))
 	{
 		/*
 		 * Build a dummy FuncExpr node containing the simplified arg list.  We
@@ -4089,13 +4772,46 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 		newexpr = (Expr *)
 			DatumGetPointer(OidFunctionCall1(func_form->protransform,
 											 PointerGetDatum(&fexpr)));
+
+		if (newexpr && !context->estimate && !IsA(newexpr, CachedExpr))
+		{
+			/* Try to cache the new expression. */
+			newexpr = (Expr *) eval_const_expressions_mutator((Node *) newexpr,
+															  context);
+		}
 	}
 
-	if (!newexpr && allow_non_const)
+	if (!newexpr && !allow_only_consts_and_simple_caching)
 		newexpr = inline_function(funcid, result_type, result_collid,
 								  input_collid, args, funcvariadic,
 								  func_tuple, context);
 
+	/*
+	 * If the operator is boolean equality or inequality, we know
+	 * how to simplify cases involving one constant and one
+	 * non-constant argument.
+	 */
+	if (!newexpr &&
+		(opno == BooleanEqualOperator ||
+		 opno == BooleanNotEqualOperator))
+	{
+		newexpr = (Expr *) simplify_boolean_equality(opno, args);
+
+		if (newexpr && !context->estimate && !IsA(newexpr, CachedExpr))
+		{
+			/* Try to cache the new expression. */
+			newexpr = (Expr *) eval_const_expressions_mutator((Node *) newexpr,
+															  context);
+		}
+	}
+
+	if (!newexpr && !context->estimate)
+	{
+		newexpr = cache_function(funcid, result_type, result_collid,
+								 input_collid, args, funcvariadic, func_tuple,
+								 context, funcformat, opno, location);
+	}
+
 	ReleaseSysCache(func_tuple);
 
 	return newexpr;
@@ -4802,6 +5518,14 @@ substitute_actual_parameters_mutator(Node *node,
 		/* We don't need to copy at this time (it'll get done later) */
 		return list_nth(context->args, param->paramid - 1);
 	}
+	if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		return substitute_actual_parameters_mutator(
+												(Node *) cachedexpr->subexpr,
+												context);
+	}
 	return expression_tree_mutator(node, substitute_actual_parameters_mutator,
 								   (void *) context);
 }
@@ -4962,6 +5686,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 		return NULL;
 	rtfunc = (RangeTblFunction *) linitial(rte->functions);
 
+	/*
+	 * Do not check whether this is CachedExpr with a FuncExpr subexpression
+	 * because STF are not cached.
+	 */
 	if (!IsA(rtfunc->funcexpr, FuncExpr))
 		return NULL;
 	fexpr = (FuncExpr *) rtfunc->funcexpr;
@@ -5261,6 +5989,14 @@ substitute_actual_srf_parameters_mutator(Node *node,
 			return result;
 		}
 	}
+	if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		return substitute_actual_srf_parameters_mutator(
+													(Node *) cachedexpr->subexpr,
+													context);
+	}
 	return expression_tree_mutator(node,
 								   substitute_actual_srf_parameters_mutator,
 								   (void *) context);
@@ -5308,3 +6044,106 @@ tlist_matches_coltypelist(List *tlist, List *coltypelist)
 
 	return true;
 }
+
+/*
+ * Return the new CachedExpr node.
+ *
+ * This is an auxiliary function for the functions cache_function and
+ * eval_const_expressions_mutator: checking that this subexpression can be
+ * cached is performed earlier in them.
+ */
+static Expr *
+get_cached_expr_node(CacheableExpr *subexpr)
+{
+	CachedExpr *cached_expr = makeNode(CachedExpr);
+	cached_expr->subexpr = subexpr;
+	return (Expr *) cached_expr;
+}
+
+/*
+ * Return true if node is null or Const or CachedExpr.
+ */
+static bool
+is_const_or_cached(const Node *node)
+{
+	if (node == NULL)
+		return false;
+
+	return (IsA(node, Const) || IsA(node, CachedExpr));
+}
+
+/*
+ * cache_function: try to cache a result of calling this function
+ *
+ * We can do this if the function does not return a set, is not volatile itself,
+ * and its arguments are constants or recursively cached expressions. Also we do
+ * not cache during estimation.
+ *
+ * Returns a cached expression if successful, or NULL if cannot cache the
+ * function. Returns an expression for the cached operator if opno is not
+ * InvalidOid.
+ */
+static Expr *
+cache_function(Oid funcid, Oid result_type, Oid result_collid, Oid input_collid,
+			   List *args, bool funcvariadic, HeapTuple func_tuple,
+			   eval_const_expressions_context *context, CoercionForm funcformat,
+			   Oid opno, int location)
+{
+	Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
+	Expr	   *newexpr;
+
+	/* Sanity checks */
+	if (context->estimate)
+		return NULL;
+
+	/* Can't cache the result of the function if it returns a set */
+	if (funcform->proretset)
+		return NULL;
+
+	/* Check for non-constant or non-cached inputs */
+	if (!SAFE_FOR_CACHING(ece_all_arguments_const((Node *) args)))
+		return NULL;
+
+	/* Can't cache volatile functions */
+	if (funcform->provolatile == PROVOLATILE_IMMUTABLE)
+		/* okay */ ;
+	else if (funcform->provolatile == PROVOLATILE_STABLE)
+		/* okay */ ;
+	else
+		return NULL;
+
+	/* Create an expression for this function/operator call */
+	if (opno == InvalidOid)
+	{
+		FuncExpr   *funcexpr = makeNode(FuncExpr);
+
+		funcexpr->funcid = funcid;
+		funcexpr->funcresulttype = result_type;
+		funcexpr->funcretset = false;
+		funcexpr->funcvariadic = funcvariadic;
+		funcexpr->funcformat = funcformat;
+		funcexpr->funccollid = result_collid;
+		funcexpr->inputcollid = input_collid;
+		funcexpr->args = args;
+		funcexpr->location = location;
+
+		newexpr = (Expr *) funcexpr;
+	}
+	else
+	{
+		OpExpr	   *opexpr = makeNode(OpExpr);
+
+		opexpr->opno = opno;
+		opexpr->opfuncid = funcid;
+		opexpr->opresulttype = result_type;
+		opexpr->opretset = false;
+		opexpr->opcollid = result_collid;
+		opexpr->inputcollid = input_collid;
+		opexpr->args = args;
+		opexpr->location = location;
+
+		newexpr = (Expr *) opexpr;
+	}
+
+	return get_cached_expr_node((CacheableExpr *) newexpr);
+}
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index b16b1e4..6cd9845 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -196,6 +196,11 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
 		context->sublevels_up--;
 		return result;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node, pull_varnos_walker,
 								  (void *) context);
 }
@@ -243,6 +248,11 @@ pull_varattnos_walker(Node *node, pull_varattnos_context *context)
 							   var->varattno - FirstLowInvalidHeapAttributeNumber);
 		return false;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 
 	/* Should not find an unplanned subquery */
 	Assert(!IsA(node, Query));
@@ -312,6 +322,11 @@ pull_vars_walker(Node *node, pull_vars_context *context)
 		context->sublevels_up--;
 		return result;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node, pull_vars_walker,
 								  (void *) context);
 }
@@ -352,6 +367,11 @@ contain_var_clause_walker(Node *node, void *context)
 			return true;		/* abort the tree traversal and return true */
 		/* else fall through to check the contained expr */
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node, contain_var_clause_walker, context);
 }
 
@@ -412,6 +432,11 @@ contain_vars_of_level_walker(Node *node, int *sublevels_up)
 		(*sublevels_up)--;
 		return result;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node,
 								  contain_vars_of_level_walker,
 								  (void *) sublevels_up);
@@ -486,6 +511,11 @@ locate_var_of_level_walker(Node *node,
 		context->sublevels_up--;
 		return result;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node,
 								  locate_var_of_level_walker,
 								  (void *) context);
@@ -636,6 +666,11 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
 		else
 			elog(ERROR, "PlaceHolderVar found where not expected");
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node, pull_var_clause_walker,
 								  (void *) context);
 }
@@ -807,6 +842,11 @@ flatten_join_alias_vars_mutator(Node *node,
 		context->sublevels_up--;
 		return (Node *) newnode;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	/* Already-planned tree not supported */
 	Assert(!IsA(node, SubPlan));
 	/* Shouldn't need to handle these planner auxiliary nodes here */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index ddc3ec8..a20d5a9 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1794,7 +1794,7 @@ exec_bind_message(StringInfo input_message)
 	 * will be generated in MessageContext.  The plan refcount will be
 	 * assigned to the Portal, so it will be released at portal destruction.
 	 */
-	cplan = GetCachedPlan(psrc, params, false, NULL);
+	cplan = GetCachedPlan(psrc, params, false, NULL, false);
 
 	/*
 	 * Now we can define the portal.
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 0cbdbe5..cdeda0b 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -3196,6 +3196,19 @@ array_map(Datum arrayd,
 	array_iter_setup(&iter, v);
 	hasnulls = false;
 
+	/* we must have our own information for executing cached expressions */
+	Assert(exprstate->own_execute_cached_expressions);
+	/* for the first element get it from the top */
+	if (exprstate->top_execute_cached_expressions)
+	{
+		*(exprstate->own_execute_cached_expressions) =
+			*(exprstate->top_execute_cached_expressions);
+	}
+	else
+	{
+		/* We are top, so all is OK */
+	}
+
 	for (i = 0; i < nitems; i++)
 	{
 		/* Get source element, checking for NULL */
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c5f5a1c..9779efb 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7642,6 +7642,19 @@ get_rule_expr(Node *node, deparse_context *context,
 			get_const_expr((Const *) node, context, 0);
 			break;
 
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+
+				/*
+				 * Since the cached expression is created only for internal use,
+				 * forget about it and deparse its subexpression.
+				 */
+				get_rule_expr((Node *) cachedexpr->subexpr, context,
+							  showimplicit);
+			}
+			break;
+
 		case T_Param:
 			get_parameter((Param *) node, context);
 			break;
@@ -8867,6 +8880,8 @@ looks_like_function(Node *node)
 		case T_XmlExpr:
 			/* these are all accepted by func_expr_common_subexpr */
 			return true;
+		case T_CachedExpr:
+			return looks_like_function((Node *) ((CachedExpr *) node)->subexpr);
 		default:
 			break;
 	}
@@ -9872,9 +9887,11 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 					foreach(lc, rte->functions)
 					{
 						RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+						FuncExpr   *funcexpr =
+							cast_node_if_cached(rtfunc->funcexpr, FuncExpr);
 
-						if (!IsA(rtfunc->funcexpr, FuncExpr) ||
-							((FuncExpr *) rtfunc->funcexpr)->funcid != F_ARRAY_UNNEST ||
+						if (funcexpr == NULL ||
+							funcexpr->funcid != F_ARRAY_UNNEST ||
 							rtfunc->funccolnames != NIL)
 						{
 							all_unnest = false;
@@ -9889,7 +9906,9 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 						foreach(lc, rte->functions)
 						{
 							RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
-							List	   *args = ((FuncExpr *) rtfunc->funcexpr)->args;
+							FuncExpr   *funcexpr =
+								cast_node_if_cached(rtfunc->funcexpr, FuncExpr);
+							List	   *args = funcexpr->args;
 
 							allargs = list_concat(allargs, list_copy(args));
 						}
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 8d7d8e0..7282413 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -80,6 +80,15 @@
 	 IsA((plansource)->raw_parse_tree->stmt, TransactionStmt))
 
 /*
+ * This flag is used to reuse the already created generic plan with
+ * precalculated bound parameters: we need it to check if the parameter was
+ * precalculated and will be precalculated because the flag PARAM_FLAG_CONST is
+ * replaced by the flag PARAM_FLAG_PRECALCULATED in the generic plan.
+ */
+#define PARAM_FLAG_ALWAYS_PRECALCULATED ( PARAM_FLAG_CONST | \
+										  PARAM_FLAG_PRECALCULATED )
+
+/*
  * This is the head of the backend's list of "saved" CachedPlanSources (i.e.,
  * those that are in long-lived storage and are examined for sinval events).
  * We thread the structs manually instead of using List cells so that we can
@@ -90,9 +99,11 @@ static CachedPlanSource *first_saved_plan = NULL;
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
 static List *RevalidateCachedQuery(CachedPlanSource *plansource,
 					  QueryEnvironment *queryEnv);
-static bool CheckCachedPlan(CachedPlanSource *plansource);
+static bool CheckCachedPlan(CachedPlanSource *plansource,
+							ParamListInfo boundParams);
 static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
-				ParamListInfo boundParams, QueryEnvironment *queryEnv);
+				ParamListInfo boundParams, QueryEnvironment *queryEnv,
+				bool is_generic);
 static bool choose_custom_plan(CachedPlanSource *plansource,
 				   ParamListInfo boundParams);
 static double cached_plan_cost(CachedPlan *plan, bool include_planner);
@@ -105,6 +116,12 @@ static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
 static void PlanCacheRelCallback(Datum arg, Oid relid);
 static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
+static ParamExternData *GetParamExternData(ParamListInfo boundParams,
+										   int paramid,
+										   ParamExternData *workspace);
+static bool IsParamValid(const ParamExternData *prm);
+static bool CheckBoundParams(ParamListInfo firstBoundParams,
+							 ParamListInfo secondBoundParams);
 
 
 /*
@@ -794,7 +811,7 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
  * (We must do this for the "true" result to be race-condition-free.)
  */
 static bool
-CheckCachedPlan(CachedPlanSource *plansource)
+CheckCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams)
 {
 	CachedPlan *plan = plansource->gplan;
 
@@ -845,8 +862,10 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		 */
 		if (plan->is_valid)
 		{
-			/* Successfully revalidated and locked the query. */
-			return true;
+			/*
+			 * Successfully revalidated and locked the query. Check boundParams.
+			 */
+			return CheckBoundParams(plan->boundParams, boundParams);
 		}
 
 		/* Oops, the race case happened.  Release useless locks. */
@@ -867,9 +886,13 @@ CheckCachedPlan(CachedPlanSource *plansource)
  * qlist should be the result value from a previous RevalidateCachedQuery,
  * or it can be set to NIL if we need to re-copy the plansource's query_list.
  *
- * To build a generic, parameter-value-independent plan, pass NULL for
- * boundParams.  To build a custom plan, pass the actual parameter values via
- * boundParams.  For best effect, the PARAM_FLAG_CONST flag should be set on
+ * To build a generic, absolutely parameter-value-independent plan, pass NULL
+ * for boundParams.  To build a generic, parameter-value-independent plan with
+ * CachedExpr nodes for constant parameters, pass the actual parameter values
+ * via boundParams and set genericPlanPrecalculateConstBoundParams to true.  To
+ * build a custom plan, pass the actual parameter values via boundParams and set
+ * genericPlanPrecalculateConstBoundParams to false.
+ * For best effect, the PARAM_FLAG_CONST flag should be set on
  * each parameter value; otherwise the planner will treat the value as a
  * hint rather than a hard constant.
  *
@@ -879,7 +902,8 @@ CheckCachedPlan(CachedPlanSource *plansource)
  */
 static CachedPlan *
 BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
-				ParamListInfo boundParams, QueryEnvironment *queryEnv)
+				ParamListInfo boundParams, QueryEnvironment *queryEnv,
+				bool genericPlanPrecalculateConstBoundParams)
 {
 	CachedPlan *plan;
 	List	   *plist;
@@ -888,6 +912,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	MemoryContext plan_context;
 	MemoryContext oldcxt = CurrentMemoryContext;
 	ListCell   *lc;
+	ParamListInfo planBoundParams;
 
 	/*
 	 * Normally the querytree should be valid already, but if it's not,
@@ -931,10 +956,72 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 		snapshot_set = true;
 	}
 
+	/* Specify boundParams for the planner */
+	if (boundParams == NULL)
+	{
+		/* Absolutely parameter-value-independent generic plan */
+		planBoundParams = NULL;
+	}
+	else if (!genericPlanPrecalculateConstBoundParams)
+	{
+		/* Custom plan */
+		planBoundParams = boundParams;
+	}
+	else
+	{
+		/*
+		 * Parameter-value-independent generic plan with precalculated
+		 * parameters.
+		 */
+
+		Size		size = offsetof(ParamListInfoData, params) +
+			boundParams->numParams * sizeof(ParamExternData);
+
+		planBoundParams = (ParamListInfo) palloc(size);
+		memcpy(planBoundParams, boundParams, size);
+
+		/*
+		 * The generic plan should know as little as possible about the
+		 * parameters, and ideally we should pass boundParams as NULL. But
+		 * on ther other hand we need to know whether they are constant or
+		 * not, so that we can insert the CachedExpr nodes into the plan
+		 * where possible.
+		 *
+		 * Therefore let's put PARAM_FLAG_PRECALCULATED instead of
+		 * PARAM_FLAG_CONST for all parameters (for example, to prevent the
+		 * creation of Const nodes instead of them).
+		 */
+		if (planBoundParams->paramFetch)
+		{
+			/*
+			 * Use the same fetch function, but put PARAM_FLAG_PRECALCULATED
+			 * instead of PARAM_FLAG_CONST after its call.
+			 */
+			planBoundParams->paramFetchArg =
+				(void *) planBoundParams->paramFetch;
+			planBoundParams->paramFetch = ParamFetchPrecalculated;
+		}
+		else
+		{
+			int			index;
+
+			for (index = 0; index < planBoundParams->numParams; ++index)
+			{
+				ParamExternData *prm = &planBoundParams->params[index];
+
+				if (OidIsValid(prm->ptype) && (prm->pflags & PARAM_FLAG_CONST))
+				{
+					prm->pflags &= ~PARAM_FLAG_CONST;
+					prm->pflags |= PARAM_FLAG_PRECALCULATED;
+				}
+			}
+		}
+	}
+
 	/*
 	 * Generate the plan.
 	 */
-	plist = pg_plan_queries(qlist, plansource->cursor_options, boundParams);
+	plist = pg_plan_queries(qlist, plansource->cursor_options, planBoundParams);
 
 	/* Release snapshot if we got one */
 	if (snapshot_set)
@@ -1002,6 +1089,30 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_saved = false;
 	plan->is_valid = true;
 
+	/*
+	 * Set the precalculation parameters of the generic plan. Copy them into the
+	 * new context if the plan is not one-shot.
+	 */
+	if (planBoundParams != NULL && genericPlanPrecalculateConstBoundParams)
+	{
+		if (!plansource->is_oneshot)
+		{
+			Size		size = offsetof(ParamListInfoData, params) +
+				planBoundParams->numParams * sizeof(ParamExternData);
+
+			plan->boundParams = (ParamListInfo) palloc(size);
+			memcpy(plan->boundParams, planBoundParams, size);
+		}
+		else
+		{
+			plan->boundParams = planBoundParams;
+		}
+	}
+	else
+	{
+		plan->boundParams = NULL;
+	}
+
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
 
@@ -1130,14 +1241,22 @@ cached_plan_cost(CachedPlan *plan, bool include_planner)
  *
  * Note: if any replanning activity is required, the caller's memory context
  * is used for that work.
+ *
+ * Note: set genericPlanPrecalculateConstBoundParams to true only if you are
+ * sure that the bound parameters will remain (non)constant quite often for the
+ * next calls to this function.  Otherwise the generic plan will be rebuilt each
+ * time when there's a bound parameter that was constant/precalculated for the
+ * previous generic plan and is not constant/precalculated now or vice versa.
  */
 CachedPlan *
 GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
-			  bool useResOwner, QueryEnvironment *queryEnv)
+			  bool useResOwner, QueryEnvironment *queryEnv,
+			  bool genericPlanPrecalculateConstBoundParams)
 {
 	CachedPlan *plan = NULL;
 	List	   *qlist;
 	bool		customplan;
+	ParamListInfo genericPlanBoundParams;
 
 	/* Assert caller is doing things in a sane order */
 	Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
@@ -1154,7 +1273,13 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	if (!customplan)
 	{
-		if (CheckCachedPlan(plansource))
+		/* set the parameters for the generic plan */
+		if (genericPlanPrecalculateConstBoundParams)
+			genericPlanBoundParams = boundParams;
+		else
+			genericPlanBoundParams = NULL;
+
+		if (CheckCachedPlan(plansource, genericPlanBoundParams))
 		{
 			/* We want a generic plan, and we already have a valid one */
 			plan = plansource->gplan;
@@ -1163,7 +1288,8 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 		else
 		{
 			/* Build a new generic plan */
-			plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv);
+			plan = BuildCachedPlan(plansource, qlist, genericPlanBoundParams,
+								   queryEnv, true);
 			/* Just make real sure plansource->gplan is clear */
 			ReleaseGenericPlan(plansource);
 			/* Link the new generic plan into the plansource */
@@ -1208,7 +1334,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 	if (customplan)
 	{
 		/* Build a custom plan */
-		plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv);
+		plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv, false);
 		/* Accumulate total costs of custom plans, but 'ware overflow */
 		if (plansource->num_custom_plans < INT_MAX)
 		{
@@ -1903,3 +2029,144 @@ ResetPlanCache(void)
 		}
 	}
 }
+
+/*
+ * ParamFetchPrecalculated
+ *		Fetch of parameters in generic plans. Use the same fetch function as in
+ *		the custom plan, but after its call put PARAM_FLAG_PRECALCULATED instead
+ *		of PARAM_FLAG_CONST to keep the plan as generic and prevent, for
+ *		example, the creation of a Const node for this parameter.
+ */
+ParamExternData *
+ParamFetchPrecalculated(ParamListInfo params, int paramid, bool speculative,
+						ParamExternData *workspace)
+{
+	/* Fetch back the hook data */
+	ParamFetchHook paramFetch = (ParamFetchHook) params->paramFetchArg;
+	ParamExternData *prm;
+
+	Assert(paramFetch != NULL);
+
+	prm = (*paramFetch) (params, paramid, speculative, workspace);
+	Assert(prm);
+
+	if (OidIsValid(prm->ptype) && (prm->pflags & PARAM_FLAG_CONST))
+	{
+		prm->pflags &= ~PARAM_FLAG_CONST;
+		prm->pflags |= PARAM_FLAG_PRECALCULATED;
+	}
+
+	return prm;
+}
+
+/*
+ * GetParamExternData: get ParamExternData with this paramid from ParamListInfo.
+ *
+ * If the parameter is dynamic, use speculative fetching, so it should avoid
+ * erroring out if parameter is unavailable.
+ */
+static ParamExternData *
+GetParamExternData(ParamListInfo boundParams, int paramid,
+				   ParamExternData *workspace)
+{
+	if (boundParams == NULL)
+		return NULL;
+
+	/*
+	 * Give hook a chance in case parameter is dynamic.  Tell it that this fetch
+	 * is speculative, so it should avoid erroring out if parameter is
+	 * unavailable.
+	 */
+	if (boundParams->paramFetch != NULL)
+		return boundParams->paramFetch(boundParams, paramid, true, workspace);
+
+	return &boundParams->params[paramid - 1];
+}
+
+/*
+ * IsParamValid: return true if prm is not NULL and its ptype is valid.
+ */
+static bool
+IsParamValid(const ParamExternData *prm)
+{
+	return prm && OidIsValid(prm->ptype);
+}
+
+/*
+ * CheckBoundParams
+ *		Check if bound params are compatible in the generic plan:
+ *		1) Check that the parameters with the same paramid are equal in terms of
+ *		   the CachedExpr node: both are constants/precalculated so they have
+ *		   previously been precalculated and will be precalculated, or both are
+ *		   not.
+ *		2) Check that the other parameters are not constants or precalculated,
+ *		   so they have not previously been precalculated and will not be
+ *		   precalculated.
+ */
+static bool
+CheckBoundParams(ParamListInfo firstBoundParams,
+				 ParamListInfo secondBoundParams)
+{
+	int			maxNumParams = 0,
+				paramid;
+	ParamExternData *first_prm,
+					*second_prm;
+	ParamExternData first_prmdata,
+					second_prmdata;
+
+	/* Get the maximum number of parameters to check */
+	if (firstBoundParams && firstBoundParams->numParams > maxNumParams)
+		maxNumParams = firstBoundParams->numParams;
+	if (secondBoundParams && secondBoundParams->numParams > maxNumParams)
+		maxNumParams = secondBoundParams->numParams;
+
+	/*
+	 * If there're parameters with the same paramid, check that they are equal
+	 * in terms of the CachedExpr node: both are constants/precalculated so they
+	 * have previously been precalculated and will be precalculated, or both are
+	 * not.
+	 *
+	 * Check that the other parameters are not constants or precalculated, so
+	 * they have not previously been precalculated and will not be
+	 * precalculated.
+	 */
+	for (paramid = 1; paramid <= maxNumParams; ++paramid)
+	{
+		first_prm = GetParamExternData(firstBoundParams, paramid,
+									   &first_prmdata);
+		second_prm = GetParamExternData(secondBoundParams, paramid,
+										&second_prmdata);
+
+		if (IsParamValid(first_prm) && IsParamValid(second_prm))
+		{
+			/*
+			 * Check that both are constants/precalculated or both are not.
+			 */
+			if ((first_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED) !=
+				(second_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED))
+				return false;
+		}
+		else if (IsParamValid(first_prm))
+		{
+			/*
+			 * The second parameter with this paramid is not
+			 * constant/precalculated, so check that the first one is also not
+			 * constant/precalculated.
+			 */
+			if (first_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED)
+				return false;
+		}
+		else if (IsParamValid(second_prm))
+		{
+			/*
+			 * The first parameter with this paramid is not
+			 * constant/precalculated, so check that the second one is also not
+			 * constant/precalculated.
+			 */
+			if (second_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED)
+				return false;
+		}
+	}
+
+	return true;
+}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index c0076bf..8968a1c 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -229,15 +229,17 @@ get_expr_result_type(Node *expr,
 					 TupleDesc *resultTupleDesc)
 {
 	TypeFuncClass result;
+	FuncExpr   *funcexpr = cast_node_if_cached(expr, FuncExpr);
+	OpExpr	   *opexpr = cast_node_if_cached(expr, OpExpr);
 
-	if (expr && IsA(expr, FuncExpr))
-		result = internal_get_result_type(((FuncExpr *) expr)->funcid,
+	if (funcexpr)
+		result = internal_get_result_type(funcexpr->funcid,
 										  expr,
 										  NULL,
 										  resultTypeId,
 										  resultTupleDesc);
-	else if (expr && IsA(expr, OpExpr))
-		result = internal_get_result_type(get_opcode(((OpExpr *) expr)->opno),
+	else if (opexpr)
+		result = internal_get_result_type(get_opcode(opexpr->opno),
 										  expr,
 										  NULL,
 										  resultTypeId,
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 117fc89..f895fe9 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -230,6 +230,16 @@ typedef enum ExprEvalOp
 	EEOP_AGG_ORDERED_TRANS_DATUM,
 	EEOP_AGG_ORDERED_TRANS_TUPLE,
 
+	/*
+	 * Evaluate CachedExpr.  EEOP_CACHEDEXPR_IF_CACHED is used before
+	 * subexpression evaluation (if subexpression was evaluated use cached value
+	 * and jump to next state or get prepared to subexpression evaluation
+	 * otherwise).  EEOP_CACHEDEXPR_SUBEXPR_END is used after subexpression
+	 * evaluation for caching its result.
+	 */
+	EEOP_CACHEDEXPR_IF_CACHED,
+	EEOP_CACHEDEXPR_SUBEXPR_END,
+
 	/* non-existent operation, used e.g. to check array lengths */
 	EEOP_LAST
 } ExprEvalOp;
@@ -634,6 +644,13 @@ typedef struct ExprEvalStep
 			int			transno;
 			int			setoff;
 		}			agg_trans;
+
+		/* for EEOP_CACHEDEXPR_* */
+		struct
+		{
+			/* steps for evaluation the same CachedExpr have the same state */
+			struct CachedExprState *state;
+		}			cachedexpr;
 	}			d;
 } ExprEvalStep;
 
@@ -674,6 +691,20 @@ typedef struct ArrayRefState
 } ArrayRefState;
 
 
+/*
+ * Non-inline data for EEOP_CACHEDEXPR_* operations (steps for evaluation the
+ * same CachedExpr have the same state).
+ */
+typedef struct CachedExprState
+{
+	bool		resnull;
+	Datum		resvalue;
+	bool		isExecuted;		/* in case upper expression jumps over it */
+	Oid 		restypid;		/* for copying resvalue of subexpression */
+	int			jumpdone;		/* jump here if result determined */
+} CachedExprState;
+
+
 /* functions in execExpr.c */
 extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
 
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 1d824ef..403b78c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -285,7 +285,14 @@ ExecEvalExpr(ExprState *state,
 			 ExprContext *econtext,
 			 bool *isNull)
 {
-	return state->evalfunc(state, econtext, isNull);
+	Datum		retDatum;
+
+	retDatum = state->evalfunc(state, econtext, isNull);
+	/* update the infomation about cached expressions */
+	if (state->own_execute_cached_expressions)
+		*(state->own_execute_cached_expressions) = false;
+
+	return retDatum;
 }
 #endif
 
@@ -304,7 +311,12 @@ ExecEvalExprSwitchContext(ExprState *state,
 	MemoryContext oldContext;
 
 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
 	retDatum = state->evalfunc(state, econtext, isNull);
+	/* update the infomation about cached expressions */
+	if (state->own_execute_cached_expressions)
+		*(state->own_execute_cached_expressions) = false;
+
 	MemoryContextSwitchTo(oldContext);
 	return retDatum;
 }
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1bf6745..370c759 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -105,6 +105,25 @@ typedef struct ExprState
 
 	Datum	   *innermost_domainval;
 	bool	   *innermost_domainnull;
+
+	/*
+	 * Usually cached expressions are executed only once and then they use
+	 * cached value until the end of execution. But sometimes the executor is
+	 * used again from the very beginning (for example, for PL/pgSQL simple
+	 * expressions), so the cached expressions must be recalculated in this
+	 * case.
+	 *
+	 * If we do not have an upper state, top_execute_cached_expressions is NULL
+	 * and we use the information from own_execute_cached_expressions.
+	 *
+	 * If not upper states use the information from the upper expression, their
+	 * own_execute_cached_expressions are NULLs. But sometimes we have have
+	 * several own runs (for example, an executor state for the array
+	 * per-element expression) and therefore we need to separate this
+	 * information from the runs of the upper state.
+	 */
+	bool	   *own_execute_cached_expressions;
+	bool	   *top_execute_cached_expressions;
 } ExprState;
 
 
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index 849f34d..2787beb 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -77,4 +77,12 @@ struct PlanState;
 extern bool planstate_tree_walker(struct PlanState *planstate, bool (*walker) (),
 								  void *context);
 
+extern Node *cast_node_if_cached_impl(Node *node, NodeTag tag);
+
+/*
+ * A more convenient macro for using the function cast_node_if_cached_impl.
+ */
+#define cast_node_if_cached(node, _type_) \
+	(_type_ *) cast_node_if_cached_impl((Node *) (node), T_##_type_)
+
 #endif							/* NODEFUNCS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 74b094a..fd643a8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -145,6 +145,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/params.h b/src/include/nodes/params.h
index 04b03c7..eee6b5c 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -37,10 +37,15 @@ struct ParseState;
  *	  Although parameter numbers are normally consecutive, we allow
  *	  ptype == InvalidOid to signal an unused array entry.
  *
- *	  pflags is a flags field.  Currently the only used bit is:
+ *	  pflags is a flags field.  Currently used bits are:
+ *
  *	  PARAM_FLAG_CONST signals the planner that it may treat this parameter
- *	  as a constant (i.e., generate a plan that works only for this value
- *	  of the parameter).
+ *			as a constant (i.e., generate a plan that works only for this value
+ *			of the parameter).
+ *
+ *	  PARAM_FLAG_PRECALCULATED signals the planner that it cannot be treated as
+ *			a pure constant, such as PARAM_FLAG_CONST. But it can be used as an
+ *			argument to the CachedExpr node.
  *
  *	  In the dynamic approach, all access to parameter values is done through
  *	  hook functions found in the ParamListInfo struct.  In this case,
@@ -85,7 +90,9 @@ struct ParseState;
  *	  and paramCompileArg is rather arbitrary.
  */
 
-#define PARAM_FLAG_CONST	0x0001	/* parameter is constant */
+#define PARAM_FLAG_CONST			0x0001	/* parameter is constant */
+#define PARAM_FLAG_PRECALCULATED	0x0002	/* parameter is precalculated: it is
+											 * cached in the generic plan */
 
 typedef struct ParamExternData
 {
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1b4b0d7..3ef16ab 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -204,6 +204,38 @@ 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 */
+} CachedExpr;
+
+/*
  * Param
  *
  *		paramkind specifies the kind of parameter. The possible values
@@ -240,7 +272,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 */
@@ -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
@@ -790,7 +822,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) */
@@ -810,7 +842,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 */
@@ -834,7 +866,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) */
@@ -859,7 +891,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 */
@@ -906,7 +938,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 */
@@ -936,7 +968,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 */
@@ -952,7 +984,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 */
@@ -986,7 +1018,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 */
 
@@ -1034,7 +1066,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 */
@@ -1048,7 +1080,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 */
@@ -1066,7 +1098,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 */
@@ -1107,7 +1139,7 @@ typedef enum SQLValueFunctionOp
 
 typedef struct SQLValueFunction
 {
-	Expr		xpr;
+	CacheableExpr xpr;
 	SQLValueFunctionOp op;		/* which function this is */
 	Oid			type;			/* result type/typmod */
 	int32		typmod;
@@ -1145,7 +1177,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 */
@@ -1183,7 +1215,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 */
@@ -1206,7 +1238,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/utils/plancache.h b/src/include/utils/plancache.h
index ab20aa0..0743048 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -138,6 +138,13 @@ typedef struct CachedPlan
 	bool		dependsOnRole;	/* is plan specific to that role? */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
+
+	/*
+	 * Used to check whether the generic plan is valid for the new boundParams;
+	 * NULL for the custom plans.
+	 */
+	ParamListInfo boundParams;
+
 	int			generation;		/* parent's generation number for this plan */
 	int			refcount;		/* count of live references to this struct */
 	MemoryContext context;		/* context containing this CachedPlan */
@@ -179,7 +186,11 @@ extern List *CachedPlanGetTargetList(CachedPlanSource *plansource,
 extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
 			  ParamListInfo boundParams,
 			  bool useResOwner,
-			  QueryEnvironment *queryEnv);
+			  QueryEnvironment *queryEnv,
+			  bool genericPlanPrecalculateConstBoundParams);
 extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
+extern ParamExternData *ParamFetchPrecalculated(ParamListInfo params,
+												int paramid, bool speculative,
+												ParamExternData *workspace);
 
 #endif							/* PLANCACHE_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 4478c53..e7f0f33 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5530,6 +5530,13 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 	expr->expr_simple_in_use = true;
 
 	/*
+	 * The executor is used (again) from the very beginning, so the cached
+	 * expressions must be (re)calculated.
+	 */
+	Assert(expr->expr_simple_state->own_execute_cached_expressions);
+	*(expr->expr_simple_state->own_execute_cached_expressions) = true;
+
+	/*
 	 * Finally we can call the executor to evaluate the expression
 	 */
 	*result = ExecEvalExpr(expr->expr_simple_state,
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
new file mode 100644
index 0000000..2beee2e
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -0,0 +1,6194 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+-- Functions testing
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Multidimensional ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+-- Array subscripting operations testing
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- FieldSelect expressions testing
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- RelabelType expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- CoerceViaIO expressions testing
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- RowCompareExpr testing
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- SQLValueFunction testing
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_name(current_role) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(current_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(session_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+(4 rows)
+
+SELECT x_stl2_name(current_catalog) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ regression
+ regression
+ regression
+ regression
+(4 rows)
+
+SELECT x_stl2_name(current_schema) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ public
+ public
+ public
+ public
+(4 rows)
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+-- Xml expressions testing
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+NOTICE:  s2 xml
+          x_stl2_xml          
+------------------------------
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+NOTICE:  s2 xml
+          x_stl2_xml          
+------------------------------
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+NOTICE:  s2 xml
+                          x_stl2_xml                          
+--------------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+NOTICE:  s2 text
+                       x_stl2_text                       
+---------------------------------------------------------
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+NOTICE:  s text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+NOTICE:  s text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- ROW() expressions with dropped columns testing
+ALTER TABLE wxyz DROP COLUMN z;
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      4
+(1 row)
+
+INSERT INTO x VALUES (5);
+SELECT simple();
+ simple 
+--------
+      5
+(1 row)
+
+ROLLBACK;
+-- Prepared statements testing
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+-- Drop tables for testing
+DROP TABLE x;
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
diff --git a/src/test/regress/expected/precalculate_stable_functions_1.out b/src/test/regress/expected/precalculate_stable_functions_1.out
new file mode 100644
index 0000000..9ff2530
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions_1.out
@@ -0,0 +1,5840 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+-- Functions testing
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Multidimensional ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+-- Array subscripting operations testing
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- FieldSelect expressions testing
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- RelabelType expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- CoerceViaIO expressions testing
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- RowCompareExpr testing
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- SQLValueFunction testing
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_name(current_role) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(current_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(session_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+(4 rows)
+
+SELECT x_stl2_name(current_catalog) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ regression
+ regression
+ regression
+ regression
+(4 rows)
+
+SELECT x_stl2_name(current_schema) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ public
+ public
+ public
+ public
+(4 rows)
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+-- Xml expressions testing
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FRO...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   '<?xml version="1.0"?><content>abc</content>',
+          ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   DOCUMENT '<?xml version="1.0"?><book><title>Manual</title>...
+                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+                  ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS D...
+                              ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+-- NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+-- Mixed functions and NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- ROW() expressions with dropped columns testing
+ALTER TABLE wxyz DROP COLUMN z;
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      4
+(1 row)
+
+INSERT INTO x VALUES (5);
+SELECT simple();
+ simple 
+--------
+      5
+(1 row)
+
+ROLLBACK;
+-- Prepared statements testing
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+-- Drop tables for testing
+DROP TABLE x;
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ad9434f..1c749d8 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -116,7 +116,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid c
 # ----------
 # Another group of parallel tests
 # ----------
-test: identity partition_join partition_prune reloptions hash_part indexing
+test: identity partition_join partition_prune reloptions hash_part indexing precalculate_stable_functions
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 27cd498..92044dc 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -185,5 +185,6 @@ test: partition_prune
 test: reloptions
 test: hash_part
 test: indexing
+test: precalculate_stable_functions
 test: event_trigger
 test: stats
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
new file mode 100644
index 0000000..44fcacb
--- /dev/null
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -0,0 +1,1946 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+
+-- Create volatile functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create stable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create immutable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create operators for testing
+
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+-- Functions testing
+
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+SELECT x_stl() FROM x;
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM x WHERE x_stl() < x;
+
+-- JOIN/ON clause testing
+
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+SELECT x_stl2(1) FROM x;
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2(x_stl()) FROM x;
+SELECT x_imm2(x_stl()) FROM x;
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+
+-- Operators testing
+
+SELECT 1 === 2 FROM x; -- should not be precalculated
+SELECT 1 ==== 2 FROM x;
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+SELECT x_stl() ==== 1 FROM x;
+
+-- IS (NOT) DISTINCT FROM expression testing
+
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- Boolean expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+
+-- ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+
+-- Multidimensional ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+
+-- Array subscripting operations testing
+
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+
+-- Mixed functions and array subscripting operations testing
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+
+-- FieldSelect expressions testing
+
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+
+-- ROW() expressions testing
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+
+-- RelabelType expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+
+-- CoerceViaIO expressions testing
+
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+
+-- Mixed functions and CoerceViaIO expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+
+-- ArrayCoerce expressions testing
+
+-- Binary-coercible types:
+
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+
+-- ConvertRowtypeExpr testing
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+
+-- Mixed functions and ConvertRowtypeExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+-- CASE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+
+-- RowCompareExpr testing
+
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+
+-- Mixed functions and RowCompareExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+
+-- COALESCE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+
+-- GREATEST and LEAST functions testing
+
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+
+-- Mixed functions and GREATEST and LEAST functions testing
+
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+
+-- SQLValueFunction testing
+
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+
+SELECT x_stl2_name(current_role) FROM x;
+SELECT x_stl2_name(current_user) FROM x;
+SELECT x_stl2_name(user) FROM x;
+SELECT x_stl2_name(session_user) FROM x;
+SELECT x_stl2_name(current_catalog) FROM x;
+SELECT x_stl2_name(current_schema) FROM x;
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+
+-- Xml expressions testing
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+
+-- NullTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+
+-- BooleanTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+
+-- Tracking functions testing
+
+SET track_functions TO 'all';
+
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+SELECT x_stl() FROM x;
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM x WHERE x_stl() < x;
+
+-- JOIN/ON clause testing
+
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+SELECT x_stl2(1) FROM x;
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2(x_stl()) FROM x;
+SELECT x_imm2(x_stl()) FROM x;
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+SELECT 1 ==== 2 FROM x;
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+SELECT x_stl() ==== 1 FROM x;
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- Mixed functions and boolean expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+
+-- Mixed functions and ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+
+-- Mixed functions and array subscripting operations testing
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+
+-- Mixed functions and CoerceViaIO expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+
+-- Mixed functions and ConvertRowtypeExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+-- Mixed functions and CASE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+
+-- Mixed functions and RowCompareExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+
+-- Mixed functions and COALESCE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+
+-- Mixed functions and NullTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+
+-- Mixed functions and BooleanTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+
+SET track_functions TO DEFAULT;
+
+-- ROW() expressions with dropped columns testing
+
+ALTER TABLE wxyz DROP COLUMN z;
+
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+
+BEGIN;
+SELECT simple();
+INSERT INTO x VALUES (5);
+SELECT simple();
+ROLLBACK;
+
+-- Prepared statements testing
+
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+
+-- Drop tables for testing
+
+DROP TABLE x;
+
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
-- 
2.7.4

#31Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Marina Polyakova (#30)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

On Thu, Feb 1, 2018 at 6:01 PM, Marina Polyakova
<m.polyakova@postgrespro.ru> wrote:

This is the 8-th version of the patch for the precalculation of stable or
immutable functions, stable or immutable operators and other nonvolatile
expressions. This is a try to fix the most problems (I'm sorry, it took some
time..) that Tom Lane and Andres Freund mentioned in [1], [2] and [3]. It is
based on the top of master, and on my computer make check-world passes. And
I'll continue work on it.

Hi Marina,

FYI I saw a repeatable crash in the contrib regression tests when
running make check-world with this patch applied.

test hstore_plperl ... FAILED (test process exited with exit code 2)
test hstore_plperlu ... FAILED (test process exited with exit code 2)
test create_transform ... FAILED

I'm not sure why it passes for you but fails here, but we can see from
the backtrace[1]https://travis-ci.org/postgresql-cfbot/postgresql/builds/337255374 that ExecInitExprRec is receiving a null node pointer
on this Ubuntu Trusty GCC 4.8 amd64 system.

[1]: https://travis-ci.org/postgresql-cfbot/postgresql/builds/337255374

--
Thomas Munro
http://www.enterprisedb.com

#32Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Thomas Munro (#31)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hello!

Thank you for reporting! I'll try to get it on our buildfarm..

On 05-02-2018 0:10, Thomas Munro wrote:

On Thu, Feb 1, 2018 at 6:01 PM, Marina Polyakova
<m.polyakova@postgrespro.ru> wrote:

This is the 8-th version of the patch for the precalculation of stable
or
immutable functions, stable or immutable operators and other
nonvolatile
expressions. This is a try to fix the most problems (I'm sorry, it
took some
time..) that Tom Lane and Andres Freund mentioned in [1], [2] and [3].
It is
based on the top of master, and on my computer make check-world
passes. And
I'll continue work on it.

Hi Marina,

FYI I saw a repeatable crash in the contrib regression tests when
running make check-world with this patch applied.

test hstore_plperl ... FAILED (test process exited with exit
code 2)
test hstore_plperlu ... FAILED (test process exited with exit
code 2)
test create_transform ... FAILED

I'm not sure why it passes for you but fails here, but we can see from
the backtrace[1] that ExecInitExprRec is receiving a null node pointer
on this Ubuntu Trusty GCC 4.8 amd64 system.

[1] https://travis-ci.org/postgresql-cfbot/postgresql/builds/337255374

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#33Andres Freund
andres@anarazel.de
In reply to: Marina Polyakova (#30)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hi,

On 2018-02-01 08:01:48 +0300, Marina Polyakova wrote:

ISTM, there might be some value to consider all of them in the design of
the new mechanism.

I'm sorry, the other parts have occupied all the time, and I'll work on it..

That has, as far as I can see, not happened. And the patch has been
reported as failing by Thomas a while ago. So I'm inclined to mark this
as returned with feedback for this CF?

- Andres

#34Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Andres Freund (#33)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hello!

I fixed the failure that Thomas pointed out to me, and I'm finishing
work on it, but it took me a while to study this part of the executor..

On 02-03-2018 0:11, Andres Freund wrote:

Hi,

On 2018-02-01 08:01:48 +0300, Marina Polyakova wrote:

ISTM, there might be some value to consider all of them in the design of
the new mechanism.

I'm sorry, the other parts have occupied all the time, and I'll work
on it..

That has, as far as I can see, not happened. And the patch has been
reported as failing by Thomas a while ago. So I'm inclined to mark
this
as returned with feedback for this CF?

- Andres

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#35Andres Freund
andres@anarazel.de
In reply to: Marina Polyakova (#34)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hi,

On 2018-03-02 11:22:01 +0300, Marina Polyakova wrote:

I fixed the failure that Thomas pointed out to me, and I'm finishing work on
it, but it took me a while to study this part of the executor..

I unfortunately think that makes this too late for v11, and we should
mark this as returned with feedback.

Greetings,

Andres Freund

#36Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: Andres Freund (#35)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Ok!

On 02-03-2018 22:56, Andres Freund wrote:

Hi,

On 2018-03-02 11:22:01 +0300, Marina Polyakova wrote:

I fixed the failure that Thomas pointed out to me, and I'm finishing
work on
it, but it took me a while to study this part of the executor..

I unfortunately think that makes this too late for v11, and we should
mark this as returned with feedback.

Greetings,

Andres Freund

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#37David Steele
david@pgmasters.net
In reply to: Marina Polyakova (#36)
Re: Re: WIP Patch: Precalculate stable functions, infrastructure v1

On 3/3/18 2:42 AM, Marina Polyakova wrote:

Ok!

On 02-03-2018 22:56, Andres Freund wrote:

Hi,

On 2018-03-02 11:22:01 +0300, Marina Polyakova wrote:

I fixed the failure that Thomas pointed out to me, and I'm finishing
work on
it, but it took me a while to study this part of the executor..

I unfortunately think that makes this too late for v11, and we should
mark this as returned with feedback.

Marked as Returned with Feedback.

Regards,
--
-David
david@pgmasters.net

#38Marina Polyakova
m.polyakova@postgrespro.ru
In reply to: David Steele (#37)
6 attachment(s)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hello, hackers!

Here there's a 9-th version of the patches for the precalculation of
stable or immutable functions, stable or immutable operators and other
nonvolatile expressions. This is a try to execute cached expressions as
PARAM_EXEC, thanks to the comments of Tom Lane and Andres Freund [1]On 24-01-2018 22:20, Andres Freund wrote:.

v9-0001-Move-the-FreeExecutorState-call-in-the-StoreAttrD.patch
- a patch in which the call of FreeExecutorState() is used only after
copying the result from the executor memory context.

v9-0002-Compile-check-constraints-for-domains-into-separa.patch
- a patch for compiling CoerceToDomain nodes into separate ExprStates so
they can be used with lists of their own cached expressions that are
compiled as PARAM_EXEC.

v9-0003-Precalculate-stable-immutable-expressions-infrast.patch
- a patch that simply adds new nodes / new fields to existing nodes and
accordingly adds / changes the main functions of the nodes for all of
them.

v9-0004-Precalculate-stable-immutable-expressions-executo.patch
- a patch for compiling cached expressions as PARAM_EXEC.

v9-0005-Precalculate-stable-and-immutable-functions-plann.patch
- the main patch that adds the CachedExpr nodes in
eval_const_expressions() and processes the cached expressions as
ordinary expressions in other parts of the code.

v9-0006-Precalculate-stable-immutable-expressions-prepare.patch
- a patch for supporting cached expressions in prepared statements.

As usual any suggestions are welcome!

[1]: On 24-01-2018 22:20, Andres Freund wrote:

To me, who has *not* followed the thread in detail, it sounds like the
relevant data shouldn't be stored inside the expression itself. For
one, we do not want to have to visit every single simple expression and
reset them, for another it architecturally doesn't seem the right place
to me. Storing all cached values in an EState or ExprContext (the
latter referring to the former) somewhat alike the values for Param's
sounds a lot more reasonable to me.

Besides that it seems to make it a lot easier to reset the values, it
also seems like it makes it a lot cleaner to cache stable functions
across multiple expressions in different places in a query? ISTM having
expression steps to actually compute the expression value in every
referencing expression is quite the waste.

The problem is that with the function expression_planner some
expressions are planned at compile time.. You can also use the function
ExecInitExpr without the parent PlanState node => without a pointer to
the corresponding EState.

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v9-0006-Precalculate-stable-immutable-expressions-prepare.patchtext/x-diff; name=v9-0006-Precalculate-stable-immutable-expressions-prepare.patchDownload
From 39fd91953e94947083b8382c10bb1c4b37b63149 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Thu, 24 May 2018 15:00:47 +0300
Subject: [PATCH v9] Precalculate stable/immutable expressions: prepared
 statements

Use the new flag PARAM_FLAG_PRECALCULATED for generic plans with precalculated
external parameters.
---
 src/backend/commands/prepare.c                     |   4 +-
 src/backend/executor/spi.c                         |   8 +-
 src/backend/optimizer/util/clauses.c               |   7 +
 src/backend/tcop/postgres.c                        |   2 +-
 src/backend/utils/cache/plancache.c                | 299 +++++++++++++++++++--
 src/include/nodes/params.h                         |  15 +-
 src/include/utils/plancache.h                      |  13 +-
 .../expected/precalculate_stable_functions.out     |  72 +++++
 .../expected/precalculate_stable_functions_1.out   |  72 +++++
 .../regress/sql/precalculate_stable_functions.sql  |  14 +
 10 files changed, 479 insertions(+), 27 deletions(-)

diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index b945b15..ef9f134 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -243,7 +243,7 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
 									   entry->plansource->query_string);
 
 	/* Replan if needed, and increment plan refcount for portal */
-	cplan = GetCachedPlan(entry->plansource, paramLI, false, NULL);
+	cplan = GetCachedPlan(entry->plansource, paramLI, false, NULL, true);
 	plan_list = cplan->stmt_list;
 
 	/*
@@ -670,7 +670,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	}
 
 	/* Replan if needed, and acquire a transient refcount */
-	cplan = GetCachedPlan(entry->plansource, paramLI, true, queryEnv);
+	cplan = GetCachedPlan(entry->plansource, paramLI, true, queryEnv, true);
 
 	INSTR_TIME_SET_CURRENT(planduration);
 	INSTR_TIME_SUBTRACT(planduration, planstart);
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 22dd55c..b4fd18d 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1292,7 +1292,8 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 	 */
 
 	/* Replan if needed, and increment plan refcount for portal */
-	cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv);
+	cplan = GetCachedPlan(plansource, paramLI, false, _SPI_current->queryEnv,
+						  false);
 	stmt_list = cplan->stmt_list;
 
 	if (!plan->saved)
@@ -1726,7 +1727,7 @@ SPI_plan_get_cached_plan(SPIPlanPtr plan)
 
 	/* Get the generic plan for the query */
 	cplan = GetCachedPlan(plansource, NULL, plan->saved,
-						  _SPI_current->queryEnv);
+						  _SPI_current->queryEnv, false);
 	Assert(cplan == plansource->gplan);
 
 	/* Pop the error context stack */
@@ -2119,7 +2120,8 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 		 * Replan if needed, and increment plan refcount.  If it's a saved
 		 * plan, the refcount must be backed by the CurrentResourceOwner.
 		 */
-		cplan = GetCachedPlan(plansource, paramLI, plan->saved, _SPI_current->queryEnv);
+		cplan = GetCachedPlan(plansource, paramLI, plan->saved,
+							  _SPI_current->queryEnv, false);
 		stmt_list = cplan->stmt_list;
 
 		/*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 883a31a..b3e8b7d 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2828,6 +2828,13 @@ eval_const_expressions_mutator(Node *node,
 													  prm->isnull,
 													  typByVal);
 						}
+						/* Otherwise OK to cache parameter value? */
+						else if (!context->estimate &&
+								 (prm->pflags & PARAM_FLAG_PRECALCULATED))
+						{
+							return (Node *) makeCachedExpr(
+										(CacheableExpr *) copyObject(param));
+						}
 					}
 				}
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index f413395..a98b8e3 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1796,7 +1796,7 @@ exec_bind_message(StringInfo input_message)
 	 * will be generated in MessageContext.  The plan refcount will be
 	 * assigned to the Portal, so it will be released at portal destruction.
 	 */
-	cplan = GetCachedPlan(psrc, params, false, NULL);
+	cplan = GetCachedPlan(psrc, params, false, NULL, false);
 
 	/*
 	 * Now we can define the portal.
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 0ad3e3c..c921ea0 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -80,6 +80,15 @@
 	 IsA((plansource)->raw_parse_tree->stmt, TransactionStmt))
 
 /*
+ * This flag is used to reuse the already created generic plan with
+ * precalculated bound parameters: we need it to check if the parameter was
+ * precalculated and will be precalculated because the flag PARAM_FLAG_CONST is
+ * replaced by the flag PARAM_FLAG_PRECALCULATED in the generic plan.
+ */
+#define PARAM_FLAG_ALWAYS_PRECALCULATED ( PARAM_FLAG_CONST | \
+										  PARAM_FLAG_PRECALCULATED )
+
+/*
  * This is the head of the backend's list of "saved" CachedPlanSources (i.e.,
  * those that are in long-lived storage and are examined for sinval events).
  * We thread the structs manually instead of using List cells so that we can
@@ -90,9 +99,11 @@ static CachedPlanSource *first_saved_plan = NULL;
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
 static List *RevalidateCachedQuery(CachedPlanSource *plansource,
 					  QueryEnvironment *queryEnv);
-static bool CheckCachedPlan(CachedPlanSource *plansource);
+static bool CheckCachedPlan(CachedPlanSource *plansource,
+				ParamListInfo boundParams);
 static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
-				ParamListInfo boundParams, QueryEnvironment *queryEnv);
+				ParamListInfo boundParams, QueryEnvironment *queryEnv,
+				bool genericPlanPrecalculateConstBoundParams);
 static bool choose_custom_plan(CachedPlanSource *plansource,
 				   ParamListInfo boundParams);
 static double cached_plan_cost(CachedPlan *plan, bool include_planner);
@@ -105,6 +116,12 @@ static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
 static void PlanCacheRelCallback(Datum arg, Oid relid);
 static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
+static ParamExternData *GetParamExternData(ParamListInfo boundParams,
+				   int paramid,
+				   ParamExternData *workspace);
+static bool IsParamValid(const ParamExternData *prm);
+static bool CheckBoundParams(ParamListInfo firstBoundParams,
+				 ParamListInfo secondBoundParams);
 
 
 /*
@@ -795,7 +812,7 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
  * (We must do this for the "true" result to be race-condition-free.)
  */
 static bool
-CheckCachedPlan(CachedPlanSource *plansource)
+CheckCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams)
 {
 	CachedPlan *plan = plansource->gplan;
 
@@ -846,8 +863,10 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		 */
 		if (plan->is_valid)
 		{
-			/* Successfully revalidated and locked the query. */
-			return true;
+			/*
+			 * Successfully revalidated and locked the query. Check boundParams.
+			 */
+			return CheckBoundParams(plan->boundParams, boundParams);
 		}
 
 		/* Oops, the race case happened.  Release useless locks. */
@@ -868,11 +887,15 @@ CheckCachedPlan(CachedPlanSource *plansource)
  * qlist should be the result value from a previous RevalidateCachedQuery,
  * or it can be set to NIL if we need to re-copy the plansource's query_list.
  *
- * To build a generic, parameter-value-independent plan, pass NULL for
- * boundParams.  To build a custom plan, pass the actual parameter values via
- * boundParams.  For best effect, the PARAM_FLAG_CONST flag should be set on
- * each parameter value; otherwise the planner will treat the value as a
- * hint rather than a hard constant.
+ * To build a generic, absolutely parameter-value-independent plan, pass NULL
+ * for boundParams.  To build a generic, parameter-value-independent plan with
+ * CachedExpr nodes for constant parameters, pass the actual parameter values
+ * via boundParams and set genericPlanPrecalculateConstBoundParams to true.  To
+ * build a custom plan, pass the actual parameter values via boundParams and set
+ * genericPlanPrecalculateConstBoundParams to false.
+ * For best effect, the PARAM_FLAG_CONST flag should be set on each parameter
+ * value; otherwise the planner will treat the value as a hint rather than a
+ * hard constant.
  *
  * Planning work is done in the caller's memory context.  The finished plan
  * is in a child memory context, which typically should get reparented
@@ -880,7 +903,8 @@ CheckCachedPlan(CachedPlanSource *plansource)
  */
 static CachedPlan *
 BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
-				ParamListInfo boundParams, QueryEnvironment *queryEnv)
+				ParamListInfo boundParams, QueryEnvironment *queryEnv,
+				bool genericPlanPrecalculateConstBoundParams)
 {
 	CachedPlan *plan;
 	List	   *plist;
@@ -889,6 +913,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	MemoryContext plan_context;
 	MemoryContext oldcxt = CurrentMemoryContext;
 	ListCell   *lc;
+	ParamListInfo planBoundParams;
 
 	/*
 	 * Normally the querytree should be valid already, but if it's not,
@@ -932,10 +957,72 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 		snapshot_set = true;
 	}
 
+	/* Specify boundParams for the planner */
+	if (boundParams == NULL)
+	{
+		/* Absolutely parameter-value-independent generic plan */
+		planBoundParams = NULL;
+	}
+	else if (!genericPlanPrecalculateConstBoundParams)
+	{
+		/* Custom plan */
+		planBoundParams = boundParams;
+	}
+	else
+	{
+		/*
+		 * Parameter-value-independent generic plan with precalculated
+		 * parameters.
+		 */
+
+		Size		size = offsetof(ParamListInfoData, params) +
+			boundParams->numParams * sizeof(ParamExternData);
+
+		planBoundParams = (ParamListInfo) palloc(size);
+		memcpy(planBoundParams, boundParams, size);
+
+		/*
+		 * The generic plan should know as little as possible about the
+		 * parameters, and ideally we should pass boundParams as NULL. But
+		 * on ther other hand we need to know whether they are constant or
+		 * not, so that we can insert the CachedExpr nodes into the plan
+		 * where possible.
+		 *
+		 * Therefore let's put PARAM_FLAG_PRECALCULATED instead of
+		 * PARAM_FLAG_CONST for all parameters (for example, to prevent the
+		 * creation of Const nodes instead of them).
+		 */
+		if (planBoundParams->paramFetch)
+		{
+			/*
+			 * Use the same fetch function, but put PARAM_FLAG_PRECALCULATED
+			 * instead of PARAM_FLAG_CONST after its call.
+			 */
+			planBoundParams->paramFetchArg =
+				(void *) planBoundParams->paramFetch;
+			planBoundParams->paramFetch = ParamFetchPrecalculated;
+		}
+		else
+		{
+			int			index;
+
+			for (index = 0; index < planBoundParams->numParams; ++index)
+			{
+				ParamExternData *prm = &planBoundParams->params[index];
+
+				if (OidIsValid(prm->ptype) && (prm->pflags & PARAM_FLAG_CONST))
+				{
+					prm->pflags &= ~PARAM_FLAG_CONST;
+					prm->pflags |= PARAM_FLAG_PRECALCULATED;
+				}
+			}
+		}
+	}
+
 	/*
 	 * Generate the plan.
 	 */
-	plist = pg_plan_queries(qlist, plansource->cursor_options, boundParams);
+	plist = pg_plan_queries(qlist, plansource->cursor_options, planBoundParams);
 
 	/* Release snapshot if we got one */
 	if (snapshot_set)
@@ -1004,6 +1091,30 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_saved = false;
 	plan->is_valid = true;
 
+	/*
+	 * Set the precalculation parameters of the generic plan. Copy them into the
+	 * new context if the plan is not one-shot.
+	 */
+	if (planBoundParams != NULL && genericPlanPrecalculateConstBoundParams)
+	{
+		if (!plansource->is_oneshot)
+		{
+			Size		size = offsetof(ParamListInfoData, params) +
+				planBoundParams->numParams * sizeof(ParamExternData);
+
+			plan->boundParams = (ParamListInfo) palloc(size);
+			memcpy(plan->boundParams, planBoundParams, size);
+		}
+		else
+		{
+			plan->boundParams = planBoundParams;
+		}
+	}
+	else
+	{
+		plan->boundParams = NULL;
+	}
+
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
 
@@ -1132,14 +1243,22 @@ cached_plan_cost(CachedPlan *plan, bool include_planner)
  *
  * Note: if any replanning activity is required, the caller's memory context
  * is used for that work.
+ *
+ * Note: set genericPlanPrecalculateConstBoundParams to true only if you are
+ * sure that the bound parameters will remain (non)constant quite often for the
+ * next calls to this function.  Otherwise the generic plan will be rebuilt each
+ * time when there's a bound parameter that was constant/precalculated for the
+ * previous generic plan and is not constant/precalculated now or vice versa.
  */
 CachedPlan *
 GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
-			  bool useResOwner, QueryEnvironment *queryEnv)
+			  bool useResOwner, QueryEnvironment *queryEnv,
+			  bool genericPlanPrecalculateConstBoundParams)
 {
 	CachedPlan *plan = NULL;
 	List	   *qlist;
 	bool		customplan;
+	ParamListInfo genericPlanBoundParams;
 
 	/* Assert caller is doing things in a sane order */
 	Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
@@ -1156,7 +1275,13 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	if (!customplan)
 	{
-		if (CheckCachedPlan(plansource))
+		/* set the parameters for the generic plan */
+		if (genericPlanPrecalculateConstBoundParams)
+			genericPlanBoundParams = boundParams;
+		else
+			genericPlanBoundParams = NULL;
+
+		if (CheckCachedPlan(plansource, genericPlanBoundParams))
 		{
 			/* We want a generic plan, and we already have a valid one */
 			plan = plansource->gplan;
@@ -1165,7 +1290,8 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 		else
 		{
 			/* Build a new generic plan */
-			plan = BuildCachedPlan(plansource, qlist, NULL, queryEnv);
+			plan = BuildCachedPlan(plansource, qlist, genericPlanBoundParams,
+								   queryEnv, true);
 			/* Just make real sure plansource->gplan is clear */
 			ReleaseGenericPlan(plansource);
 			/* Link the new generic plan into the plansource */
@@ -1210,7 +1336,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 	if (customplan)
 	{
 		/* Build a custom plan */
-		plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv);
+		plan = BuildCachedPlan(plansource, qlist, boundParams, queryEnv, false);
 		/* Accumulate total costs of custom plans, but 'ware overflow */
 		if (plansource->num_custom_plans < INT_MAX)
 		{
@@ -1906,3 +2032,144 @@ ResetPlanCache(void)
 		}
 	}
 }
+
+/*
+ * ParamFetchPrecalculated
+ *		Fetch of parameters in generic plans. Use the same fetch function as in
+ *		the custom plan, but after its call put PARAM_FLAG_PRECALCULATED instead
+ *		of PARAM_FLAG_CONST to keep the plan as generic and prevent, for
+ *		example, the creation of a Const node for this parameter.
+ */
+ParamExternData *
+ParamFetchPrecalculated(ParamListInfo params, int paramid, bool speculative,
+						ParamExternData *workspace)
+{
+	/* Fetch back the hook data */
+	ParamFetchHook paramFetch = (ParamFetchHook) params->paramFetchArg;
+	ParamExternData *prm;
+
+	Assert(paramFetch != NULL);
+
+	prm = (*paramFetch) (params, paramid, speculative, workspace);
+	Assert(prm);
+
+	if (OidIsValid(prm->ptype) && (prm->pflags & PARAM_FLAG_CONST))
+	{
+		prm->pflags &= ~PARAM_FLAG_CONST;
+		prm->pflags |= PARAM_FLAG_PRECALCULATED;
+	}
+
+	return prm;
+}
+
+/*
+ * GetParamExternData: get ParamExternData with this paramid from ParamListInfo.
+ *
+ * If the parameter is dynamic, use speculative fetching, so it should avoid
+ * erroring out if parameter is unavailable.
+ */
+static ParamExternData *
+GetParamExternData(ParamListInfo boundParams, int paramid,
+				   ParamExternData *workspace)
+{
+	if (boundParams == NULL)
+		return NULL;
+
+	/*
+	 * Give hook a chance in case parameter is dynamic.  Tell it that this fetch
+	 * is speculative, so it should avoid erroring out if parameter is
+	 * unavailable.
+	 */
+	if (boundParams->paramFetch != NULL)
+		return boundParams->paramFetch(boundParams, paramid, true, workspace);
+
+	return &boundParams->params[paramid - 1];
+}
+
+/*
+ * IsParamValid: return true if prm is not NULL and its ptype is valid.
+ */
+static bool
+IsParamValid(const ParamExternData *prm)
+{
+	return prm && OidIsValid(prm->ptype);
+}
+
+/*
+ * CheckBoundParams
+ *		Check if bound params are compatible in the generic plan:
+ *		1) Check that the parameters with the same paramid are equal in terms of
+ *		   the CachedExpr node: both are constants/precalculated so they have
+ *		   previously been precalculated and will be precalculated, or both are
+ *		   not.
+ *		2) Check that the other parameters are not constants or precalculated,
+ *		   so they have not previously been precalculated and will not be
+ *		   precalculated.
+ */
+static bool
+CheckBoundParams(ParamListInfo firstBoundParams,
+				 ParamListInfo secondBoundParams)
+{
+	int			maxNumParams = 0,
+				paramid;
+	ParamExternData *first_prm,
+					*second_prm;
+	ParamExternData first_prmdata,
+					second_prmdata;
+
+	/* Get the maximum number of parameters to check */
+	if (firstBoundParams && firstBoundParams->numParams > maxNumParams)
+		maxNumParams = firstBoundParams->numParams;
+	if (secondBoundParams && secondBoundParams->numParams > maxNumParams)
+		maxNumParams = secondBoundParams->numParams;
+
+	/*
+	 * If there're parameters with the same paramid, check that they are equal
+	 * in terms of the CachedExpr node: both are constants/precalculated so they
+	 * have previously been precalculated and will be precalculated, or both are
+	 * not.
+	 *
+	 * Check that the other parameters are not constants or precalculated, so
+	 * they have not previously been precalculated and will not be
+	 * precalculated.
+	 */
+	for (paramid = 1; paramid <= maxNumParams; ++paramid)
+	{
+		first_prm = GetParamExternData(firstBoundParams, paramid,
+									   &first_prmdata);
+		second_prm = GetParamExternData(secondBoundParams, paramid,
+										&second_prmdata);
+
+		if (IsParamValid(first_prm) && IsParamValid(second_prm))
+		{
+			/*
+			 * Check that both are constants/precalculated or both are not.
+			 */
+			if ((first_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED) !=
+				(second_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED))
+				return false;
+		}
+		else if (IsParamValid(first_prm))
+		{
+			/*
+			 * The second parameter with this paramid is not
+			 * constant/precalculated, so check that the first one is also not
+			 * constant/precalculated.
+			 */
+			if (first_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED)
+				return false;
+		}
+		else if (IsParamValid(second_prm))
+		{
+			/*
+			 * The first parameter with this paramid is not
+			 * constant/precalculated, so check that the second one is also not
+			 * constant/precalculated.
+			 */
+			if (second_prm->pflags & PARAM_FLAG_ALWAYS_PRECALCULATED)
+				return false;
+		}
+	}
+
+	return true;
+}
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 0622a2b..bf3770c 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -37,10 +37,15 @@ struct ParseState;
  *	  Although parameter numbers are normally consecutive, we allow
  *	  ptype == InvalidOid to signal an unused array entry.
  *
- *	  pflags is a flags field.  Currently the only used bit is:
+ *	  pflags is a flags field.  Currently used bits are:
+ *
  *	  PARAM_FLAG_CONST signals the planner that it may treat this parameter
- *	  as a constant (i.e., generate a plan that works only for this value
- *	  of the parameter).
+ *			as a constant (i.e., generate a plan that works only for this value
+ *			of the parameter).
+ *
+ *	  PARAM_FLAG_PRECALCULATED signals the planner that it cannot be treated as
+ *			a pure constant, such as PARAM_FLAG_CONST. But it can be used as an
+ *			argument to the CachedExpr node.
  *
  *	  In the dynamic approach, all access to parameter values is done through
  *	  hook functions found in the ParamListInfo struct.  In this case,
@@ -85,7 +90,9 @@ struct ParseState;
  *	  and paramCompileArg is rather arbitrary.
  */
 
-#define PARAM_FLAG_CONST	0x0001	/* parameter is constant */
+#define PARAM_FLAG_CONST			0x0001	/* parameter is constant */
+#define PARAM_FLAG_PRECALCULATED	0x0002	/* parameter is precalculated: it is
+											 * cached in the generic plan */
 
 typedef struct ParamExternData
 {
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index ab20aa0..859461d 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -138,6 +138,13 @@ typedef struct CachedPlan
 	bool		dependsOnRole;	/* is plan specific to that role? */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
+
+	/*
+	 * Used to check whether the generic plan is valid for the new boundParams;
+	 * NULL for the custom plans.
+	 */
+	ParamListInfo boundParams;
+
 	int			generation;		/* parent's generation number for this plan */
 	int			refcount;		/* count of live references to this struct */
 	MemoryContext context;		/* context containing this CachedPlan */
@@ -179,7 +186,11 @@ extern List *CachedPlanGetTargetList(CachedPlanSource *plansource,
 extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
 			  ParamListInfo boundParams,
 			  bool useResOwner,
-			  QueryEnvironment *queryEnv);
+			  QueryEnvironment *queryEnv,
+			  bool genericPlanPrecalculateConstBoundParams);
 extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
+extern ParamExternData *ParamFetchPrecalculated(ParamListInfo params,
+						int paramid, bool speculative,
+						ParamExternData *workspace);
 
 #endif							/* PLANCACHE_H */
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
index 6a81993..2beee2e 100644
--- a/src/test/regress/expected/precalculate_stable_functions.out
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -6113,6 +6113,78 @@ SELECT simple();
 (1 row)
 
 ROLLBACK;
+-- Prepared statements testing
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
 -- Drop tables for testing
 DROP TABLE x;
 DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
diff --git a/src/test/regress/expected/precalculate_stable_functions_1.out b/src/test/regress/expected/precalculate_stable_functions_1.out
index 10f66d4..9ff2530 100644
--- a/src/test/regress/expected/precalculate_stable_functions_1.out
+++ b/src/test/regress/expected/precalculate_stable_functions_1.out
@@ -5759,6 +5759,78 @@ SELECT simple();
 (1 row)
 
 ROLLBACK;
+-- Prepared statements testing
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+NOTICE:  i2
+NOTICE:  i2
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+  QUERY PLAN   
+---------------
+ Seq Scan on x
+(1 row)
+
 -- Drop tables for testing
 DROP TABLE x;
 DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
index 23b1f04..44fcacb 100644
--- a/src/test/regress/sql/precalculate_stable_functions.sql
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -1920,6 +1920,20 @@ INSERT INTO x VALUES (5);
 SELECT simple();
 ROLLBACK;
 
+-- Prepared statements testing
+
+PREPARE test_x_imm2 (integer) AS SELECT x_imm2(x_imm2($1)) FROM x;
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+EXPLAIN (COSTS OFF) EXECUTE test_x_imm2(2);
+
 -- Drop tables for testing
 
 DROP TABLE x;
-- 
2.7.4

v9-0001-Move-the-FreeExecutorState-call-in-the-StoreAttrD.patchtext/x-diff; name=v9-0001-Move-the-FreeExecutorState-call-in-the-StoreAttrD.patchDownload
From d9d9e91b8d72217434474ef137133b14ffabf754 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Thu, 24 May 2018 11:33:38 +0300
Subject: [PATCH v9] Move the FreeExecutorState() call in the StoreAttrDefault

In the function StoreAttrDefault move the call of the function FreeExecutorState
until we get the full result so that the memory context of the latter is not
freed too early. This is used for cached expressions the results of which are
copied into the ecxt_per_query_memory context.
---
 src/backend/catalog/heap.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 39813de..ffdf6f8 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2110,8 +2110,6 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 			missingval = ExecEvalExpr(exprState, econtext,
 									  &missingIsNull);
 
-			FreeExecutorState(estate);
-
 			defAttStruct = TupleDescAttr(rel->rd_att, attnum - 1);
 
 			if (missingIsNull)
@@ -2131,6 +2129,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 															 defAttStruct->attalign));
 			}
 
+			FreeExecutorState(estate);
+
 			valuesAtt[Anum_pg_attribute_atthasmissing - 1] = !missingIsNull;
 			replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true;
 			valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval;
-- 
2.7.4

v9-0002-Compile-check-constraints-for-domains-into-separa.patchtext/x-diff; name=v9-0002-Compile-check-constraints-for-domains-into-separa.patchDownload
From 88facc56669ad3b5af84a94d442ab6ada67201a3 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Thu, 24 May 2018 11:58:33 +0300
Subject: [PATCH v9] Compile check constraints for domains into separate
 ExprState nodes

Because the plans for check constraints of the CoerceToDomain nodes are handled
dynamically they are compiled into separate ExprState nodes so they can be used
with lists of their own cached expressions that are compiled as PARAM_EXEC.
---
 src/backend/executor/execExpr.c       | 58 +++++++++++++++++------------------
 src/backend/executor/execExprInterp.c | 22 ++++++++++---
 src/include/executor/execExpr.h       |  9 ++++--
 3 files changed, 53 insertions(+), 36 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e284fd7..29c21c3 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2673,14 +2673,9 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 	DomainConstraintRef *constraint_ref;
 	Datum	   *domainval = NULL;
 	bool	   *domainnull = NULL;
-	Datum	   *save_innermost_domainval;
-	bool	   *save_innermost_domainnull;
 	ListCell   *l;
 
 	scratch->d.domaincheck.resulttype = ctest->resulttype;
-	/* we'll allocate workspace only if needed */
-	scratch->d.domaincheck.checkvalue = NULL;
-	scratch->d.domaincheck.checknull = NULL;
 
 	/*
 	 * Evaluate argument - it's fine to directly store it into resv/resnull,
@@ -2721,6 +2716,8 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 	foreach(l, constraint_ref->constraints)
 	{
 		DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
+		Expr	   *check_expr = con->check_expr;
+		ExprState  *check_exprstate;
 
 		scratch->d.domaincheck.constraintname = con->name;
 
@@ -2728,18 +2725,10 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 		{
 			case DOM_CONSTRAINT_NOTNULL:
 				scratch->opcode = EEOP_DOMAIN_NOTNULL;
+				scratch->d.domaincheck.check_exprstate = NULL;
 				ExprEvalPushStep(state, scratch);
 				break;
 			case DOM_CONSTRAINT_CHECK:
-				/* Allocate workspace for CHECK output if we didn't yet */
-				if (scratch->d.domaincheck.checkvalue == NULL)
-				{
-					scratch->d.domaincheck.checkvalue =
-						(Datum *) palloc(sizeof(Datum));
-					scratch->d.domaincheck.checknull =
-						(bool *) palloc(sizeof(bool));
-				}
-
 				/*
 				 * If first time through, determine where CoerceToDomainValue
 				 * nodes should read from.
@@ -2772,27 +2761,38 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 					}
 				}
 
-				/*
-				 * Set up value to be returned by CoerceToDomainValue nodes.
-				 * We must save and restore innermost_domainval/null fields,
-				 * in case this node is itself within a check expression for
-				 * another domain.
-				 */
-				save_innermost_domainval = state->innermost_domainval;
-				save_innermost_domainnull = state->innermost_domainnull;
-				state->innermost_domainval = domainval;
-				state->innermost_domainnull = domainnull;
+				check_exprstate = makeNode(ExprState);
+				check_exprstate->expr = check_expr;
+				check_exprstate->parent = state->parent;
+				check_exprstate->ext_params = state->ext_params;
+
+				/* Set up value to be returned by CoerceToDomainValue nodes */
+				check_exprstate->innermost_domainval = domainval;
+				check_exprstate->innermost_domainnull = domainnull;
 
 				/* evaluate check expression value */
-				ExecInitExprRec(con->check_expr, state,
-								scratch->d.domaincheck.checkvalue,
-								scratch->d.domaincheck.checknull);
+				ExecInitExprRec(check_expr, check_exprstate,
+								&check_exprstate->resvalue,
+								&check_exprstate->resnull);
 
-				state->innermost_domainval = save_innermost_domainval;
-				state->innermost_domainnull = save_innermost_domainnull;
+				if (check_exprstate->steps_len == 1 &&
+					check_exprstate->steps[0].opcode == EEOP_DOMAIN_TESTVAL)
+				{
+					/* Trivial, so we need no check work at runtime */
+					check_exprstate = NULL;
+				}
+				else
+				{
+					/* Not trivial, so append a DONE step */
+					scratch->opcode = EEOP_DONE;
+					ExprEvalPushStep(check_exprstate, scratch);
+					/* and ready the subexpression */
+					ExecReadyExpr(check_exprstate);
+				}
 
 				/* now test result */
 				scratch->opcode = EEOP_DOMAIN_CHECK;
+				scratch->d.domaincheck.check_exprstate = check_exprstate;
 				ExprEvalPushStep(state, scratch);
 
 				break;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 9d6e25a..f0246ce 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1455,7 +1455,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		EEO_CASE(EEOP_DOMAIN_CHECK)
 		{
 			/* too complex for an inline implementation */
-			ExecEvalConstraintCheck(state, op);
+			ExecEvalConstraintCheck(state, op, econtext);
 
 			EEO_NEXT();
 		}
@@ -3508,10 +3508,24 @@ ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op)
  * Evaluate a CHECK domain constraint.
  */
 void
-ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op)
+ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op,
+						ExprContext *econtext)
 {
-	if (!*op->d.domaincheck.checknull &&
-		!DatumGetBool(*op->d.domaincheck.checkvalue))
+	if (op->d.domaincheck.check_exprstate)
+	{
+		op->d.domaincheck.checkvalue = ExecEvalExpr(
+											op->d.domaincheck.check_exprstate,
+											econtext,
+											&op->d.domaincheck.checknull);
+	}
+	else
+	{
+		op->d.domaincheck.checkvalue = *(op->resvalue);
+		op->d.domaincheck.checknull = *(op->resnull);
+	}
+
+	if (!op->d.domaincheck.checknull &&
+		!DatumGetBool(op->d.domaincheck.checkvalue))
 		ereport(ERROR,
 				(errcode(ERRCODE_CHECK_VIOLATION),
 				 errmsg("value for domain %s violates check constraint \"%s\"",
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index f7b1f77..0ec8947 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -509,10 +509,12 @@ typedef struct ExprEvalStep
 			/* name of constraint */
 			char	   *constraintname;
 			/* where the result of a CHECK constraint will be stored */
-			Datum	   *checkvalue;
-			bool	   *checknull;
+			Datum		checkvalue;
+			bool		checknull;
 			/* OID of domain type */
 			Oid			resulttype;
+			/* NULL for EEOP_DOMAIN_NOTNULL */
+			ExprState  *check_exprstate;
 		}			domaincheck;
 
 		/* for EEOP_CONVERT_ROWTYPE */
@@ -726,7 +728,8 @@ extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
 					   ExprContext *econtext);
 extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
-extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op,
+						ExprContext *econtext);
 extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op);
 extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op,
-- 
2.7.4

v9-0003-Precalculate-stable-immutable-expressions-infrast.patchtext/x-diff; name=v9-0003-Precalculate-stable-immutable-expressions-infrast.patchDownload
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

v9-0004-Precalculate-stable-immutable-expressions-executo.patchtext/plain; name=v9-0004-Precalculate-stable-immutable-expressions-executo.patchDownload
From 75de30def4aa674908775176ce03a1a2f99b1d39 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Thu, 24 May 2018 12:49:25 +0300
Subject: [PATCH v9] Precalculate stable/immutable expressions: executor

Cached expressions are always executed as PARAM_EXEC. 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). In this case all information about them is stored in
ExprState, not in EState. Usually in this case the new node PlannedExpr is used.
It is returned by a function expression_planner, and contains an expression and
the list of its cached expressions.

Note: the cached ids (= the PARAM_EXEC ids for cached expressions) of all
non-internal cached expressions must be set by the function
set_non_internal_cachedexprs_walker after all mutations. Otherwise all the
created cached expressions are considered internal and will not be cached by
themselves.
---
 src/backend/catalog/heap.c                     |   2 +-
 src/backend/commands/copy.c                    |   6 +-
 src/backend/commands/indexcmds.c               |   2 +-
 src/backend/commands/tablecmds.c               |  14 +-
 src/backend/executor/execExpr.c                | 263 +++++++++++++++++++++++--
 src/backend/executor/execExprInterp.c          |  56 +++++-
 src/backend/executor/execMain.c                |   5 +
 src/backend/executor/execParallel.c            |   1 +
 src/backend/executor/execPartition.c           |   6 +-
 src/backend/executor/execSRF.c                 |   2 +-
 src/backend/executor/nodeAgg.c                 |   2 +-
 src/backend/executor/nodeBitmapHeapscan.c      |   4 +-
 src/backend/executor/nodeCtescan.c             |   2 +-
 src/backend/executor/nodeCustom.c              |   2 +-
 src/backend/executor/nodeForeignscan.c         |   4 +-
 src/backend/executor/nodeFunctionscan.c        |   2 +-
 src/backend/executor/nodeGroup.c               |   2 +-
 src/backend/executor/nodeHash.c                |   2 +-
 src/backend/executor/nodeHashjoin.c            |  10 +-
 src/backend/executor/nodeIndexonlyscan.c       |   4 +-
 src/backend/executor/nodeIndexscan.c           |  12 +-
 src/backend/executor/nodeLimit.c               |   4 +-
 src/backend/executor/nodeMergejoin.c           |  10 +-
 src/backend/executor/nodeModifyTable.c         |   4 +-
 src/backend/executor/nodeNamedtuplestorescan.c |   2 +-
 src/backend/executor/nodeNestloop.c            |   4 +-
 src/backend/executor/nodeProjectSet.c          |   2 +-
 src/backend/executor/nodeResult.c              |   5 +-
 src/backend/executor/nodeSamplescan.c          |   4 +-
 src/backend/executor/nodeSeqscan.c             |   2 +-
 src/backend/executor/nodeSubplan.c             |   2 +-
 src/backend/executor/nodeSubqueryscan.c        |   2 +-
 src/backend/executor/nodeTableFuncscan.c       |   6 +-
 src/backend/executor/nodeTidscan.c             |   8 +-
 src/backend/executor/nodeValuesscan.c          |   2 +-
 src/backend/executor/nodeWindowAgg.c           |   4 +-
 src/backend/executor/nodeWorktablescan.c       |   2 +-
 src/backend/optimizer/plan/planner.c           |  13 +-
 src/backend/optimizer/util/clauses.c           |   2 +-
 src/backend/optimizer/util/predtest.c          |   2 +-
 src/backend/parser/parse_utilcmd.c             |   2 +-
 src/backend/partitioning/partbounds.c          |   2 +-
 src/backend/partitioning/partprune.c           |   5 +-
 src/backend/replication/logical/worker.c       |   7 +-
 src/backend/utils/cache/typcache.c             |  18 +-
 src/include/executor/execExpr.h                |  11 ++
 src/include/executor/executor.h                |  13 +-
 src/include/nodes/execnodes.h                  |  35 +++-
 src/include/nodes/params.h                     |  13 +-
 src/include/optimizer/planner.h                |   2 +-
 src/pl/plpgsql/src/pl_exec.c                   |  34 +++-
 src/pl/plpgsql/src/plpgsql.h                   |   2 +
 52 files changed, 508 insertions(+), 119 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index ffdf6f8..113cc43 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2102,7 +2102,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 
 		if (add_column_mode)
 		{
-			expr2 = expression_planner(expr2);
+			expr2 = expression_planner(expr2)->expr;
 			estate = CreateExecutorState();
 			exprState = ExecPrepareExpr(expr2, estate);
 			econtext = GetPerTupleExprContext(estate);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0a1706c..b77635b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3075,10 +3075,12 @@ BeginCopyFrom(ParseState *pstate,
 			if (defexpr != NULL)
 			{
 				/* Run the expression through planner */
-				defexpr = expression_planner(defexpr);
+				PlannedExpr *planned_defexpr = expression_planner(defexpr);
+				defexpr = planned_defexpr->expr;
 
 				/* Initialize executable expression in copycontext */
-				defexprs[num_defaults] = ExecInitExpr(defexpr, NULL);
+				defexprs[num_defaults] =
+					ExecInitExpr(defexpr, NULL, planned_defexpr->cachedExprs);
 				defmap[num_defaults] = attnum - 1;
 				num_defaults++;
 
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3a3223b..7550913 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1335,7 +1335,7 @@ CheckMutability(Expr *expr)
 	 *
 	 * We assume here that expression_planner() won't scribble on its input.
 	 */
-	expr = expression_planner(expr);
+	expr = expression_planner(expr)->expr;
 
 	/* Now we can search for non-immutable functions */
 	return contain_mutable_functions((Node *) expr);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0e95037..5cc9cf2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -200,7 +200,7 @@ typedef struct NewConstraint
 typedef struct NewColumnValue
 {
 	AttrNumber	attnum;			/* which column */
-	Expr	   *expr;			/* expression to compute */
+	PlannedExpr *expr;			/* expression to compute */
 	ExprState  *exprstate;		/* execution state */
 } NewColumnValue;
 
@@ -4621,9 +4621,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	foreach(l, tab->newvals)
 	{
 		NewColumnValue *ex = lfirst(l);
+		PlannedExpr *planned_expr = ex->expr;
 
 		/* expr already planned */
-		ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
+		ex->exprstate = ExecInitExpr((Expr *) planned_expr->expr, NULL,
+									 planned_expr->cachedExprs);
 	}
 
 	notnull_attrs = NIL;
@@ -9052,6 +9054,7 @@ ATPrepAlterColumnType(List **wqueue,
 	ParseState *pstate = make_parsestate(NULL);
 	AclResult	aclresult;
 	bool		is_expr;
+	PlannedExpr *planned_transform;
 
 	if (rel->rd_rel->reloftype && !recursing)
 		ereport(ERROR,
@@ -9164,7 +9167,8 @@ ATPrepAlterColumnType(List **wqueue,
 		assign_expr_collations(pstate, transform);
 
 		/* Plan the expr now so we can accurately assess the need to rewrite. */
-		transform = (Node *) expression_planner((Expr *) transform);
+		planned_transform = expression_planner((Expr *) transform);
+		transform = (Node *) planned_transform->expr;
 
 		/*
 		 * Add a work queue item to make ATRewriteTable update the column
@@ -9172,7 +9176,7 @@ ATPrepAlterColumnType(List **wqueue,
 		 */
 		newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
 		newval->attnum = attnum;
-		newval->expr = (Expr *) transform;
+		newval->expr = planned_transform;
 
 		tab->newvals = lappend(tab->newvals, newval);
 		if (ATColumnChangeRequiresRewrite(transform, attnum))
@@ -13733,7 +13737,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
 				 * expression destructively and we have already saved the
 				 * expression to be stored into the catalog above.
 				 */
-				expr = (Node *) expression_planner((Expr *) expr);
+				expr = (Node *) expression_planner((Expr *) expr)->expr;
 
 				/*
 				 * Partition expression cannot contain mutable functions,
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 29c21c3..55996ff 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -78,6 +78,8 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
 					  ExprEvalStep *scratch,
 					  FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
 					  int transno, int setno, int setoff, bool ishash);
+static void ExecInitDynamicallyPlannedCachedExprs(ExprState *state,
+									  List *cachedexprs);
 
 
 /*
@@ -114,9 +116,12 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
  * callers that may or may not have an expression that needs to be compiled.
  * Note that a NULL ExprState pointer *cannot* be handed to ExecEvalExpr,
  * although ExecQual and ExecCheck will accept one (and treat it as "true").
+ *
+ * Initialize cached expressions as dynamically planned if cachedexprs is not
+ * NULL.
  */
 ExprState *
-ExecInitExpr(Expr *node, PlanState *parent)
+ExecInitExpr(Expr *node, PlanState *parent, List *cachedexprs)
 {
 	ExprState  *state;
 	ExprEvalStep scratch = {0};
@@ -134,6 +139,10 @@ ExecInitExpr(Expr *node, PlanState *parent)
 	/* Insert EEOP_*_FETCHSOME steps as needed */
 	ExecInitExprSlots(state, (Node *) node);
 
+	/* Initialize dynamically planned cached expressions */
+	if (cachedexprs)
+		ExecInitDynamicallyPlannedCachedExprs(state, cachedexprs);
+
 	/* Compile the expression proper */
 	ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
 
@@ -153,7 +162,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
  * and instead we may have a ParamListInfo describing PARAM_EXTERN Params.
  */
 ExprState *
-ExecInitExprWithParams(Expr *node, ParamListInfo ext_params)
+ExecInitExprWithParams(Expr *node, ParamListInfo ext_params, List *cachedexprs)
 {
 	ExprState  *state;
 	ExprEvalStep scratch = {0};
@@ -171,6 +180,10 @@ ExecInitExprWithParams(Expr *node, ParamListInfo ext_params)
 	/* Insert EEOP_*_FETCHSOME steps as needed */
 	ExecInitExprSlots(state, (Node *) node);
 
+	/* Initialize dynamically planned cached expressions */
+	if (cachedexprs)
+		ExecInitDynamicallyPlannedCachedExprs(state, cachedexprs);
+
 	/* Compile the expression proper */
 	ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
 
@@ -200,9 +213,12 @@ ExecInitExprWithParams(Expr *node, ParamListInfo ext_params)
  * is false.  This makes ExecQual primarily useful for evaluating WHERE
  * clauses, since SQL specifies that tuples with null WHERE results do not
  * get selected.
+ *
+ * As in ExecInitExpr, initialize cached expressions as dynamically planned if
+ * cachedexprs is not NULL.
  */
 ExprState *
-ExecInitQual(List *qual, PlanState *parent)
+ExecInitQual(List *qual, PlanState *parent, List *cachedexprs)
 {
 	ExprState  *state;
 	ExprEvalStep scratch = {0};
@@ -226,6 +242,10 @@ ExecInitQual(List *qual, PlanState *parent)
 	/* Insert EEOP_*_FETCHSOME steps as needed */
 	ExecInitExprSlots(state, (Node *) qual);
 
+	/* Initialize dynamically planned cached expressions */
+	if (cachedexprs)
+		ExecInitDynamicallyPlannedCachedExprs(state, cachedexprs);
+
 	/*
 	 * ExecQual() needs to return false for an expression returning NULL. That
 	 * allows us to short-circuit the evaluation the first time a NULL is
@@ -291,7 +311,7 @@ ExecInitQual(List *qual, PlanState *parent)
  * can just apply ExecInitExpr to produce suitable input for ExecCheck.
  */
 ExprState *
-ExecInitCheck(List *qual, PlanState *parent)
+ExecInitCheck(List *qual, PlanState *parent, List *cachedexprs)
 {
 	/* short-circuit (here and in ExecCheck) for empty restriction list */
 	if (qual == NIL)
@@ -304,7 +324,7 @@ ExecInitCheck(List *qual, PlanState *parent)
 	 * than one entry), and compile normally.  Unlike ExecQual, we can't
 	 * short-circuit on NULL results, so the regular AND behavior is needed.
 	 */
-	return ExecInitExpr(make_ands_explicit(qual), parent);
+	return ExecInitExpr(make_ands_explicit(qual), parent, cachedexprs);
 }
 
 /*
@@ -320,7 +340,7 @@ ExecInitExprList(List *nodes, PlanState *parent)
 	{
 		Expr	   *e = lfirst(lc);
 
-		result = lappend(result, ExecInitExpr(e, parent));
+		result = lappend(result, ExecInitExpr(e, parent, NULL));
 	}
 
 	return result;
@@ -489,12 +509,13 @@ ExecPrepareExpr(Expr *node, EState *estate)
 {
 	ExprState  *result;
 	MemoryContext oldcontext;
+	PlannedExpr *planned_expr;
 
 	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-	node = expression_planner(node);
+	planned_expr = expression_planner(node);
 
-	result = ExecInitExpr(node, NULL);
+	result = ExecInitExpr(planned_expr->expr, NULL, planned_expr->cachedExprs);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -517,12 +538,14 @@ ExecPrepareQual(List *qual, EState *estate)
 {
 	ExprState  *result;
 	MemoryContext oldcontext;
+	PlannedExpr *planned_qual;
 
 	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-	qual = (List *) expression_planner((Expr *) qual);
+	planned_qual = expression_planner((Expr *) qual);
 
-	result = ExecInitQual(qual, NULL);
+	result = ExecInitQual((List *) planned_qual->expr, NULL,
+						  planned_qual->cachedExprs);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -540,12 +563,14 @@ ExecPrepareCheck(List *qual, EState *estate)
 {
 	ExprState  *result;
 	MemoryContext oldcontext;
+	PlannedExpr *planned_qual;
 
 	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
 
-	qual = (List *) expression_planner((Expr *) qual);
+	planned_qual = expression_planner((Expr *) qual);
 
-	result = ExecInitCheck(qual, NULL);
+	result = ExecInitCheck((List *) planned_qual->expr, NULL,
+						   planned_qual->cachedExprs);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -734,6 +759,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 						scratch.opcode = EEOP_PARAM_EXEC;
 						scratch.d.param.paramid = param->paramid;
 						scratch.d.param.paramtype = param->paramtype;
+						scratch.d.param.dynamically_planned = false;
 						ExprEvalPushStep(state, &scratch);
 						break;
 					case PARAM_EXTERN:
@@ -761,6 +787,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 							scratch.opcode = EEOP_PARAM_EXTERN;
 							scratch.d.param.paramid = param->paramid;
 							scratch.d.param.paramtype = param->paramtype;
+							scratch.d.param.dynamically_planned = false;
 							ExprEvalPushStep(state, &scratch);
 						}
 						break;
@@ -842,7 +869,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 					wfstate->args = ExecInitExprList(wfunc->args,
 													 state->parent);
 					wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter,
-													  state->parent);
+													  state->parent, NULL);
 
 					/*
 					 * Complain if the windowfunc's arguments contain any
@@ -867,6 +894,61 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+				int			cached_id = cachedexpr->cached_id;
+
+				if (cached_id < 0)
+				{
+					/*
+					 * This is an internal cached expression. So just add its
+					 * subexpression steps.
+					 */
+					ExecInitExprRec((Expr *) cachedexpr->subexpr, state, resv,
+									resnull);
+				}
+				else
+				{
+					CachedExprsInfo *cachedexprs_info;
+					bool		dynamically_planned;
+
+					if (state->cachedexprs_vals)
+					{
+						cachedexprs_info = &(state->cachedexprs_info);
+						dynamically_planned = true;
+					}
+					else
+					{
+						if (!state->parent)
+						{
+							/* planner messed up */
+							elog(ERROR, "CachedExpr found with no parent plan");
+						}
+
+						if (!state->parent->state)
+						{
+							/* planner messed up */
+							elog(ERROR,
+								 "CachedExpr found with no parent plan's executor state");
+						}
+
+						cachedexprs_info =
+							&(state->parent->state->es_cachedexprs_info);
+						dynamically_planned = false;
+					}
+
+					scratch.opcode = EEOP_PARAM_EXEC;
+					scratch.d.param.paramid =
+						(cachedexprs_info->first_cached_paramid + cached_id);
+					scratch.d.param.paramtype = exprType((const Node *) node);
+					scratch.d.param.dynamically_planned = dynamically_planned;
+					ExprEvalPushStep(state, &scratch);
+				}
+
+				break;
+			}
+
 		case T_ArrayRef:
 			{
 				ArrayRef   *aref = (ArrayRef *) node;
@@ -2716,7 +2798,7 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 	foreach(l, constraint_ref->constraints)
 	{
 		DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
-		Expr	   *check_expr = con->check_expr;
+		PlannedExpr *check_expr = con->check_expr;
 		ExprState  *check_exprstate;
 
 		scratch->d.domaincheck.constraintname = con->name;
@@ -2762,7 +2844,7 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 				}
 
 				check_exprstate = makeNode(ExprState);
-				check_exprstate->expr = check_expr;
+				check_exprstate->expr = check_expr->expr;
 				check_exprstate->parent = state->parent;
 				check_exprstate->ext_params = state->ext_params;
 
@@ -2771,7 +2853,9 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 				check_exprstate->innermost_domainnull = domainnull;
 
 				/* evaluate check expression value */
-				ExecInitExprRec(check_expr, check_exprstate,
+				ExecInitDynamicallyPlannedCachedExprs(check_exprstate,
+													  check_expr->cachedExprs);
+				ExecInitExprRec(check_expr->expr, check_exprstate,
 								&check_exprstate->resvalue,
 								&check_exprstate->resnull);
 
@@ -3345,3 +3429,150 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc,
 
 	return state;
 }
+
+void
+ExecInitCachedExprs(QueryDesc *queryDesc)
+{
+	List	   *cachedexprs = queryDesc->plannedstmt->cachedExprs;
+	EState	   *estate = queryDesc->estate;
+	PlanState  *planstate = queryDesc->planstate;
+	CachedExprsInfo *cachedexprs_info = &(estate->es_cachedexprs_info);
+	MemoryContext oldcontext;
+	int			num_cachedexprs;
+	ListCell   *cell;
+
+	if (cachedexprs == NIL)
+	{
+		/* nothing to do here */
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+	num_cachedexprs = list_length(cachedexprs);
+	cachedexprs_info->subexpr_states = (ExprState *)
+		palloc0(num_cachedexprs * sizeof(ExprState));
+	cachedexprs_info->num_cachedexprs = num_cachedexprs;
+
+	/*
+	 * Set the assigned PARAM_EXEC slot number of the first cached expression
+	 * since the PARAM_EXEC nodes must be the first.
+	 */
+	cachedexprs_info->first_cached_paramid =
+		(list_length(queryDesc->plannedstmt->paramExecTypes) - num_cachedexprs);
+
+	foreach(cell, cachedexprs)
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) lfirst(cell);
+		int			cached_id = cachedexpr->cached_id;
+		int			paramid;
+		Expr	   *subexpr = (Expr *) cachedexpr->subexpr;
+		ExprState  *subexpr_state;
+		ExprEvalStep scratch = {0};
+
+		if (cached_id < 0)
+		{
+			/* planner messed up */
+			elog(ERROR,
+				 "Internal cached expression found in the list of non-internal cached expressions");
+		}
+
+		paramid = cachedexprs_info->first_cached_paramid + cached_id;
+		subexpr_state = &(cachedexprs_info->subexpr_states[cached_id]);
+
+		/* make ExprState for subexpression evaluation */
+		subexpr_state->tag.type = T_ExprState;
+		subexpr_state->expr = subexpr;
+		subexpr_state->parent = planstate;
+		subexpr_state->ext_params = NULL;
+
+		/* Compile the subexpression proper */
+		ExecInitExprRec(subexpr, subexpr_state,
+						&subexpr_state->resvalue,
+						&subexpr_state->resnull);
+
+		/* Finally, append a DONE step */
+		scratch.opcode = EEOP_DONE;
+		ExprEvalPushStep(subexpr_state, &scratch);
+
+		ExecReadyExpr(subexpr_state);
+
+		estate->es_param_exec_vals[paramid].execPlan = subexpr_state;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+}
+
+static void
+ExecInitDynamicallyPlannedCachedExprs(ExprState *state, List *cachedexprs)
+{
+	CachedExprsInfo *cachedexprs_info = &(state->cachedexprs_info);
+	int			num_cachedexprs;
+	ListCell   *cell;
+
+	if (cachedexprs == NIL)
+	{
+		/* nothing to do here */
+		return;
+	}
+
+	num_cachedexprs = list_length(cachedexprs);
+	state->cachedexprs_vals = (ParamExecData *)
+		palloc0(num_cachedexprs * sizeof(ParamExecData));
+	cachedexprs_info->subexpr_states = (ExprState *)
+		palloc0(num_cachedexprs * sizeof(ExprState));
+	cachedexprs_info->num_cachedexprs = num_cachedexprs;
+	cachedexprs_info->first_cached_paramid = 0;
+
+	foreach(cell, cachedexprs)
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) lfirst(cell);
+		int			cached_id = cachedexpr->cached_id;
+		Expr	   *subexpr = (Expr *) cachedexpr->subexpr;
+		ExprState  *subexpr_state;
+		ExprEvalStep scratch = {0};
+
+		if (cached_id < 0)
+		{
+			/* planner messed up */
+			elog(ERROR,
+				 "Internal cached expression found in the list of non-internal cached expressions");
+		}
+
+		subexpr_state = &(cachedexprs_info->subexpr_states[cached_id]);
+
+		/* make ExprState for subexpression evaluation */
+		subexpr_state->tag.type = T_ExprState;
+		subexpr_state->expr = subexpr;
+		subexpr_state->parent = state->parent;
+		subexpr_state->ext_params = state->ext_params;
+
+		/* Compile the subexpression proper */
+		ExecInitExprRec(subexpr, subexpr_state,
+						&subexpr_state->resvalue,
+						&subexpr_state->resnull);
+
+		/* Finally, append a DONE step */
+		scratch.opcode = EEOP_DONE;
+		ExprEvalPushStep(subexpr_state, &scratch);
+
+		ExecReadyExpr(subexpr_state);
+
+		state->cachedexprs_vals[cached_id].execPlan = subexpr_state;
+	}
+}
+
+void
+ExecSetDynamicallyPlannedCachedExprs(ExprState *state)
+{
+	CachedExprsInfo *cachedexprs_info = &(state->cachedexprs_info);
+	int			cached_id;
+
+	for (cached_id = 0;
+		 cached_id < cachedexprs_info->num_cachedexprs;
+		 ++cached_id)
+	{
+		state->cachedexprs_vals[cached_id].execPlan =
+			&(cachedexprs_info->subexpr_states[cached_id]);
+	}
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index f0246ce..d18faf8 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2239,13 +2239,24 @@ void
 ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 {
 	ParamExecData *prm;
+	int			paramid = op->d.param.paramid;
+
+	if (op->d.param.dynamically_planned)
+		prm = &(state->cachedexprs_vals[paramid]);
+	else
+		prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
-	prm = &(econtext->ecxt_param_exec_vals[op->d.param.paramid]);
 	if (unlikely(prm->execPlan != NULL))
 	{
 		/* Parameter not evaluated yet, so go do it */
-		ExecSetParamPlan(prm->execPlan, econtext);
-		/* ExecSetParamPlan should have processed this param... */
+		if (IsA(prm->execPlan, ExprState))
+			ExecEvalCachedExpr(state, op, econtext);
+		else
+			ExecSetParamPlan(prm->execPlan, econtext);
+		/*
+		 * ExecSetParamPlan/ExecEvalCachedExpr should have processed this
+		 * param...
+		 */
 		Assert(prm->execPlan == NULL);
 	}
 	*op->resvalue = prm->value;
@@ -4139,3 +4150,42 @@ ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op,
 	ExecStoreVirtualTuple(pertrans->sortslot);
 	tuplesort_puttupleslot(pertrans->sortstates[setno], pertrans->sortslot);
 }
+
+void
+ExecEvalCachedExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ParamExecData *prm;
+	int			paramid = op->d.param.paramid;
+	MemoryContext oldContext;
+	int16		restyplen;
+	bool		restypbyval;
+
+	if (op->d.param.dynamically_planned)
+		prm = &(state->cachedexprs_vals[paramid]);
+	else
+		prm = &(econtext->ecxt_param_exec_vals[paramid]);
+
+	prm->value = ExecEvalExpr((ExprState *) prm->execPlan, econtext,
+							  &(prm->isnull));
+
+	/* Save result */
+	if (!(prm->isnull))
+	{
+		get_typlenbyval(op->d.param.paramtype, &restyplen, &restypbyval);
+
+		/*
+		 * Switch per-query memory context. It is necessary to save the
+		 * subexpression result between all tuples if its value datum is a
+		 * pointer.
+		 */
+		oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+		prm->value = datumCopy(prm->value, restypbyval, restyplen);
+
+		/* Switch memory context back */
+		MemoryContextSwitchTo(oldContext);
+	}
+
+	/* Mark this cached expression as done */
+	prm->execPlan = NULL;
+}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 3d12f9c..88c83f1 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -259,6 +259,11 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 		AfterTriggerBeginQuery();
 
 	/*
+	 * Initialize cached expressions used in the plan
+	 */
+	ExecInitCachedExprs(queryDesc);
+
+	/*
 	 * Initialize the plan state tree
 	 */
 	InitPlan(queryDesc, eflags);
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 52f1a96..5aa050e 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -208,6 +208,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->utilityStmt = NULL;
 	pstmt->stmt_location = -1;
 	pstmt->stmt_len = -1;
+	pstmt->cachedExprs = estate->es_plannedstmt->cachedExprs;
 
 	/* Return serialized copy of our dummy PlannedStmt. */
 	return nodeToString(pstmt);
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c83991c..f127b03 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -420,7 +420,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
 		{
 			WithCheckOption *wco = castNode(WithCheckOption, lfirst(ll));
 			ExprState  *wcoExpr = ExecInitQual(castNode(List, wco->qual),
-											   &mtstate->ps);
+											   &mtstate->ps, NULL);
 
 			wcoExprs = lappend(wcoExprs, wcoExpr);
 		}
@@ -646,7 +646,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
 											&found_whole_row);
 					/* We ignore the value of found_whole_row. */
 					leaf_part_rri->ri_onConflict->oc_WhereClause =
-						ExecInitQual((List *) clause, &mtstate->ps);
+						ExecInitQual((List *) clause, &mtstate->ps, NULL);
 				}
 			}
 		}
@@ -1504,7 +1504,7 @@ ExecSetupPartitionPruneState(PlanState *planstate, List *partitionpruneinfo)
 					stateidx = PruneCxtStateIdx(partnatts,
 												step->step.step_id, keyno);
 					context->exprstates[stateidx] =
-						ExecInitExpr(expr, context->planstate);
+						ExecInitExpr(expr, context->planstate, NULL);
 				}
 				keyno++;
 			}
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index b97b8d7..98598c4 100644
--- a/src/backend/executor/execSRF.c
+++ b/src/backend/executor/execSRF.c
@@ -83,7 +83,7 @@ ExecInitTableFunctionResult(Expr *expr,
 	}
 	else
 	{
-		state->elidedFuncState = ExecInitExpr(expr, parent);
+		state->elidedFuncState = ExecInitExpr(expr, parent, NULL);
 	}
 
 	return state;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 0fe0c22..7781e87 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -2237,7 +2237,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	 * in the targetlist are found during ExecAssignProjectionInfo, below.
 	 */
 	aggstate->ss.ps.qual =
-		ExecInitQual(node->plan.qual, (PlanState *) aggstate);
+		ExecInitQual(node->plan.qual, (PlanState *) aggstate, NULL);
 
 	/*
 	 * We should now have found all Aggrefs in the targetlist and quals.
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 3e1c9e0..b06e7c9 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -937,9 +937,9 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	scanstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate, NULL);
 	scanstate->bitmapqualorig =
-		ExecInitQual(node->bitmapqualorig, (PlanState *) scanstate);
+		ExecInitQual(node->bitmapqualorig, (PlanState *) scanstate, NULL);
 
 	/*
 	 * Determine the maximum for prefetch_target.  If the tablespace has a
diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c
index 24700dd..0af8b82 100644
--- a/src/backend/executor/nodeCtescan.c
+++ b/src/backend/executor/nodeCtescan.c
@@ -272,7 +272,7 @@ ExecInitCteScan(CteScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	scanstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate, NULL);
 
 	return scanstate;
 }
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
index b816e0b..77369bf 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -92,7 +92,7 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
 
 	/* initialize child expressions */
 	css->ss.ps.qual =
-		ExecInitQual(cscan->scan.plan.qual, (PlanState *) css);
+		ExecInitQual(cscan->scan.plan.qual, (PlanState *) css, NULL);
 
 	/*
 	 * The callback of custom-scan provider applies the final initialization
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index a2a28b7..18faf88 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -205,9 +205,9 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	scanstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate, NULL);
 	scanstate->fdw_recheck_quals =
-		ExecInitQual(node->fdw_recheck_quals, (PlanState *) scanstate);
+		ExecInitQual(node->fdw_recheck_quals, (PlanState *) scanstate, NULL);
 
 	/*
 	 * Initialize FDW-related state.
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index fb7c9f6..9492012 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -494,7 +494,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	scanstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate, NULL);
 
 	/*
 	 * Create a memory context that ExecMakeTableFunctionResult can use to
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 2ea80e8..41c3b77 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -200,7 +200,7 @@ ExecInitGroup(Group *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	grpstate->ss.ps.qual =
-		ExecInitQual(node->plan.qual, (PlanState *) grpstate);
+		ExecInitQual(node->plan.qual, (PlanState *) grpstate, NULL);
 
 	/*
 	 * Precompute fmgr lookup data for inner loop
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 4f069d1..d139f5a 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -389,7 +389,7 @@ ExecInitHash(Hash *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	hashstate->ps.qual =
-		ExecInitQual(node->plan.qual, (PlanState *) hashstate);
+		ExecInitQual(node->plan.qual, (PlanState *) hashstate, NULL);
 
 	return hashstate;
 }
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index dd94cff..23215a9 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -701,11 +701,11 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	hjstate->js.ps.qual =
-		ExecInitQual(node->join.plan.qual, (PlanState *) hjstate);
+		ExecInitQual(node->join.plan.qual, (PlanState *) hjstate, NULL);
 	hjstate->js.joinqual =
-		ExecInitQual(node->join.joinqual, (PlanState *) hjstate);
+		ExecInitQual(node->join.joinqual, (PlanState *) hjstate, NULL);
 	hjstate->hashclauses =
-		ExecInitQual(node->hashclauses, (PlanState *) hjstate);
+		ExecInitQual(node->hashclauses, (PlanState *) hjstate, NULL);
 
 	/*
 	 * initialize hash-specific info
@@ -732,9 +732,9 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 		OpExpr	   *hclause = lfirst_node(OpExpr, l);
 
 		lclauses = lappend(lclauses, ExecInitExpr(linitial(hclause->args),
-												  (PlanState *) hjstate));
+												  (PlanState *) hjstate, NULL));
 		rclauses = lappend(rclauses, ExecInitExpr(lsecond(hclause->args),
-												  (PlanState *) hjstate));
+												  (PlanState *) hjstate, NULL));
 		hoperators = lappend_oid(hoperators, hclause->opno);
 	}
 	hjstate->hj_OuterHashKeys = lclauses;
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 3a02a99..790d6d9 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -550,9 +550,9 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
 	 * sub-parts corresponding to runtime keys (see below).
 	 */
 	indexstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate, NULL);
 	indexstate->indexqual =
-		ExecInitQual(node->indexqual, (PlanState *) indexstate);
+		ExecInitQual(node->indexqual, (PlanState *) indexstate, NULL);
 
 	/*
 	 * If we are just doing EXPLAIN (ie, aren't going to run the plan), stop
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index d601219..404c61e 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -971,9 +971,9 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
 	 * in the expression must be found now...)
 	 */
 	indexstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate, NULL);
 	indexstate->indexqualorig =
-		ExecInitQual(node->indexqualorig, (PlanState *) indexstate);
+		ExecInitQual(node->indexqualorig, (PlanState *) indexstate, NULL);
 	indexstate->indexorderbyorig =
 		ExecInitExprList(node->indexorderbyorig, (PlanState *) indexstate);
 
@@ -1308,7 +1308,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 				}
 				runtime_keys[n_runtime_keys].scan_key = this_scan_key;
 				runtime_keys[n_runtime_keys].key_expr =
-					ExecInitExpr(rightop, planstate);
+					ExecInitExpr(rightop, planstate, NULL);
 				runtime_keys[n_runtime_keys].key_toastable =
 					TypeIsToastable(op_righttype);
 				n_runtime_keys++;
@@ -1438,7 +1438,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 					}
 					runtime_keys[n_runtime_keys].scan_key = this_sub_key;
 					runtime_keys[n_runtime_keys].key_expr =
-						ExecInitExpr(rightop, planstate);
+						ExecInitExpr(rightop, planstate, NULL);
 					runtime_keys[n_runtime_keys].key_toastable =
 						TypeIsToastable(op_righttype);
 					n_runtime_keys++;
@@ -1556,7 +1556,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 					}
 					runtime_keys[n_runtime_keys].scan_key = this_scan_key;
 					runtime_keys[n_runtime_keys].key_expr =
-						ExecInitExpr(rightop, planstate);
+						ExecInitExpr(rightop, planstate, NULL);
 
 					/*
 					 * Careful here: the runtime expression is not of
@@ -1574,7 +1574,7 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 				/* Executor has to expand the array value */
 				array_keys[n_array_keys].scan_key = this_scan_key;
 				array_keys[n_array_keys].array_expr =
-					ExecInitExpr(rightop, planstate);
+					ExecInitExpr(rightop, planstate, NULL);
 				/* the remaining fields were zeroed by palloc0 */
 				n_array_keys++;
 				scanvalue = (Datum) 0;
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index 56d98b4..3d12b79 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -363,9 +363,9 @@ ExecInitLimit(Limit *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	limitstate->limitOffset = ExecInitExpr((Expr *) node->limitOffset,
-										   (PlanState *) limitstate);
+										   (PlanState *) limitstate, NULL);
 	limitstate->limitCount = ExecInitExpr((Expr *) node->limitCount,
-										  (PlanState *) limitstate);
+										  (PlanState *) limitstate, NULL);
 
 	/*
 	 * Initialize result slot and type. (XXX not actually used, but upper
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 5e52b90..93d91ff 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -207,8 +207,10 @@ MJExamineQuals(List *mergeclauses,
 		/*
 		 * Prepare the input expressions for execution.
 		 */
-		clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent);
-		clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent);
+		clause->lexpr = ExecInitExpr((Expr *) linitial(qual->args), parent,
+									 NULL);
+		clause->rexpr = ExecInitExpr((Expr *) lsecond(qual->args), parent,
+									 NULL);
 
 		/* Set up sort support data */
 		clause->ssup.ssup_cxt = CurrentMemoryContext;
@@ -1524,9 +1526,9 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	mergestate->js.ps.qual =
-		ExecInitQual(node->join.plan.qual, (PlanState *) mergestate);
+		ExecInitQual(node->join.plan.qual, (PlanState *) mergestate, NULL);
 	mergestate->js.joinqual =
-		ExecInitQual(node->join.joinqual, (PlanState *) mergestate);
+		ExecInitQual(node->join.joinqual, (PlanState *) mergestate, NULL);
 	/* mergeclauses are handled below */
 
 	/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c4c841c..845b206 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2351,7 +2351,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		{
 			WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
 			ExprState  *wcoExpr = ExecInitQual((List *) wco->qual,
-											   mtstate->mt_plans[i]);
+											   mtstate->mt_plans[i], NULL);
 
 			wcoExprs = lappend(wcoExprs, wcoExpr);
 		}
@@ -2483,7 +2483,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			ExprState  *qualexpr;
 
 			qualexpr = ExecInitQual((List *) node->onConflictWhere,
-									&mtstate->ps);
+									&mtstate->ps, NULL);
 			resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
 		}
 	}
diff --git a/src/backend/executor/nodeNamedtuplestorescan.c b/src/backend/executor/nodeNamedtuplestorescan.c
index b260ad2..ca5a70c 100644
--- a/src/backend/executor/nodeNamedtuplestorescan.c
+++ b/src/backend/executor/nodeNamedtuplestorescan.c
@@ -145,7 +145,7 @@ ExecInitNamedTuplestoreScan(NamedTuplestoreScan *node, EState *estate, int eflag
 	 * initialize child expressions
 	 */
 	scanstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate, NULL);
 
 	/*
 	 * Initialize projection.
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 9ae9863..caa4049 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -311,10 +311,10 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	nlstate->js.ps.qual =
-		ExecInitQual(node->join.plan.qual, (PlanState *) nlstate);
+		ExecInitQual(node->join.plan.qual, (PlanState *) nlstate, NULL);
 	nlstate->js.jointype = node->join.jointype;
 	nlstate->js.joinqual =
-		ExecInitQual(node->join.joinqual, (PlanState *) nlstate);
+		ExecInitQual(node->join.joinqual, (PlanState *) nlstate, NULL);
 
 	/*
 	 * detect whether we need only consider the first matching inner tuple
diff --git a/src/backend/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c
index 6d6ed38..c8d4226 100644
--- a/src/backend/executor/nodeProjectSet.c
+++ b/src/backend/executor/nodeProjectSet.c
@@ -287,7 +287,7 @@ ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags)
 		else
 		{
 			Assert(!expression_returns_set((Node *) expr));
-			state->elems[off] = (Node *) ExecInitExpr(expr, &state->ps);
+			state->elems[off] = (Node *) ExecInitExpr(expr, &state->ps, NULL);
 		}
 
 		off++;
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
index e4418a2..e99b1c1 100644
--- a/src/backend/executor/nodeResult.c
+++ b/src/backend/executor/nodeResult.c
@@ -224,9 +224,10 @@ ExecInitResult(Result *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	resstate->ps.qual =
-		ExecInitQual(node->plan.qual, (PlanState *) resstate);
+		ExecInitQual(node->plan.qual, (PlanState *) resstate, NULL);
 	resstate->resconstantqual =
-		ExecInitQual((List *) node->resconstantqual, (PlanState *) resstate);
+		ExecInitQual((List *) node->resconstantqual, (PlanState *) resstate,
+					 NULL);
 
 	return resstate;
 }
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 15177db..900839d 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -163,11 +163,11 @@ ExecInitSampleScan(SampleScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	scanstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate, NULL);
 
 	scanstate->args = ExecInitExprList(tsc->args, (PlanState *) scanstate);
 	scanstate->repeatable =
-		ExecInitExpr(tsc->repeatable, (PlanState *) scanstate);
+		ExecInitExpr(tsc->repeatable, (PlanState *) scanstate, NULL);
 
 	/*
 	 * If we don't have a REPEATABLE clause, select a random seed.  We want to
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 9db3689..ad50b2c 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -188,7 +188,7 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	scanstate->ss.ps.qual =
-		ExecInitQual(node->plan.qual, (PlanState *) scanstate);
+		ExecInitQual(node->plan.qual, (PlanState *) scanstate, NULL);
 
 	return scanstate;
 }
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 44f551b..2c0addf 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -778,7 +778,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 	sstate->parent = parent;
 
 	/* Initialize subexpressions */
-	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
+	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent, NULL);
 	sstate->args = ExecInitExprList(subplan->args, parent);
 
 	/*
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
index fa61884..ebb1de2 100644
--- a/src/backend/executor/nodeSubqueryscan.c
+++ b/src/backend/executor/nodeSubqueryscan.c
@@ -141,7 +141,7 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	subquerystate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) subquerystate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) subquerystate, NULL);
 
 	return subquerystate;
 }
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
index fed6f2b..a7a04d1 100644
--- a/src/backend/executor/nodeTableFuncscan.c
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -159,7 +159,7 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	scanstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps);
+		ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps, NULL);
 
 	/* Only XMLTABLE is supported currently */
 	scanstate->routine = &XmlTableRoutine;
@@ -175,9 +175,9 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
 	scanstate->ns_uris =
 		ExecInitExprList(tf->ns_uris, (PlanState *) scanstate);
 	scanstate->docexpr =
-		ExecInitExpr((Expr *) tf->docexpr, (PlanState *) scanstate);
+		ExecInitExpr((Expr *) tf->docexpr, (PlanState *) scanstate, NULL);
 	scanstate->rowexpr =
-		ExecInitExpr((Expr *) tf->rowexpr, (PlanState *) scanstate);
+		ExecInitExpr((Expr *) tf->rowexpr, (PlanState *) scanstate, NULL);
 	scanstate->colexprs =
 		ExecInitExprList(tf->colexprs, (PlanState *) scanstate);
 	scanstate->coldefexprs =
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index e207b1f..8c8c53d 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -83,10 +83,10 @@ TidExprListCreate(TidScanState *tidstate)
 			arg2 = get_rightop(expr);
 			if (IsCTIDVar(arg1))
 				tidexpr->exprstate = ExecInitExpr((Expr *) arg2,
-												  &tidstate->ss.ps);
+												  &tidstate->ss.ps, NULL);
 			else if (IsCTIDVar(arg2))
 				tidexpr->exprstate = ExecInitExpr((Expr *) arg1,
-												  &tidstate->ss.ps);
+												  &tidstate->ss.ps, NULL);
 			else
 				elog(ERROR, "could not identify CTID variable");
 			tidexpr->isarray = false;
@@ -97,7 +97,7 @@ TidExprListCreate(TidScanState *tidstate)
 
 			Assert(IsCTIDVar(linitial(saex->args)));
 			tidexpr->exprstate = ExecInitExpr(lsecond(saex->args),
-											  &tidstate->ss.ps);
+											  &tidstate->ss.ps, NULL);
 			tidexpr->isarray = true;
 		}
 		else if (expr && IsA(expr, CurrentOfExpr))
@@ -561,7 +561,7 @@ ExecInitTidScan(TidScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	tidstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) tidstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) tidstate, NULL);
 
 	TidExprListCreate(tidstate);
 
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index f76999d..5681f8b 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -273,7 +273,7 @@ ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	scanstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate, NULL);
 
 	/*
 	 * Other node-specific setup
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index fe5369a..0aaac10 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2476,9 +2476,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 
 	/* initialize frame bound offset expressions */
 	winstate->startOffset = ExecInitExpr((Expr *) node->startOffset,
-										 (PlanState *) winstate);
+										 (PlanState *) winstate, NULL);
 	winstate->endOffset = ExecInitExpr((Expr *) node->endOffset,
-									   (PlanState *) winstate);
+									   (PlanState *) winstate, NULL);
 
 	/* Lookup in_range support functions if needed */
 	if (OidIsValid(node->startInRangeFunc))
diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c
index 2ff9a21..b2ea099 100644
--- a/src/backend/executor/nodeWorktablescan.c
+++ b/src/backend/executor/nodeWorktablescan.c
@@ -166,7 +166,7 @@ ExecInitWorkTableScan(WorkTableScan *node, EState *estate, int eflags)
 	 * initialize child expressions
 	 */
 	scanstate->ss.ps.qual =
-		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate, NULL);
 
 	/*
 	 * Do not yet initialize projection info, see ExecWorkTableScan() for
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 67a2c7a..882a752 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5883,22 +5883,25 @@ adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
  * tree.  (It would actually be okay to apply fix_opfuncids to it, but since
  * we first do an expression_tree_mutator-based walk, what is returned will
  * be a new node tree.)
+ *
+ * Note: all cached expressions must be added to the corresponding EState or
+ * ExprState of this expression.
  */
-Expr *
+PlannedExpr *
 expression_planner(Expr *expr)
 {
-	Node	   *result;
+	PlannedExpr *result = makeNode(PlannedExpr);
 
 	/*
 	 * Convert named-argument function calls, insert default arguments and
 	 * simplify constant subexprs
 	 */
-	result = eval_const_expressions(NULL, (Node *) expr);
+	result->expr = (Expr *) eval_const_expressions(NULL, (Node *) expr);
 
 	/* Fill in opfuncid values if missing */
-	fix_opfuncids(result);
+	fix_opfuncids((Node *) result->expr);
 
-	return (Expr *) result;
+	return result;
 }
 
 
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 505ae0a..17bcdba 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4867,7 +4867,7 @@ evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
 	 * Prepare expr for execution.  (Note: we can't use ExecPrepareExpr
 	 * because it'd result in recursively invoking eval_const_expressions.)
 	 */
-	exprstate = ExecInitExpr(expr, NULL);
+	exprstate = ExecInitExpr(expr, NULL, NULL);
 
 	/*
 	 * And evaluate it.
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index 446207d..02ce883 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -1749,7 +1749,7 @@ operator_predicate_proof(Expr *predicate, Node *clause,
 	fix_opfuncids((Node *) test_expr);
 
 	/* Prepare it for execution */
-	test_exprstate = ExecInitExpr(test_expr, NULL);
+	test_exprstate = ExecInitExpr(test_expr, NULL, NULL);
 
 	/* And execute it. */
 	test_result = ExecEvalExprSwitchContext(test_exprstate,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 17b54b2..1691229 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3871,7 +3871,7 @@ transformPartitionBoundValue(ParseState *pstate, A_Const *con,
 
 	/* Simplify the expression, in case we had a coercion */
 	if (!IsA(value, Const))
-		value = (Node *) expression_planner((Expr *) value);
+		value = (Node *) expression_planner((Expr *) value)->expr;
 
 	/* Fail if we don't have a constant (i.e., non-immutable coercion) */
 	if (!IsA(value, Const))
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 09c7c3e..001aaef 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -1767,7 +1767,7 @@ get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
 										   (Expr *) lower_val,
 										   (Expr *) upper_val);
 		fix_opfuncids((Node *) test_expr);
-		test_exprstate = ExecInitExpr(test_expr, NULL);
+		test_exprstate = ExecInitExpr(test_expr, NULL, NULL);
 		test_result = ExecEvalExprSwitchContext(test_exprstate,
 												GetPerTupleExprContext(estate),
 												&isNull);
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 58ec2a6..d407a89 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -386,7 +386,7 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory)
 	{
 		List	   *partqual = rel->partition_qual;
 
-		partqual = (List *) expression_planner((Expr *) partqual);
+		partqual = (List *) expression_planner((Expr *) partqual)->expr;
 
 		/* Fix Vars to have the desired varno */
 		if (rel->relid != 1)
@@ -689,7 +689,8 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
 						if (partconstr)
 						{
 							partconstr = (List *)
-								expression_planner((Expr *) partconstr);
+								expression_planner((Expr *) partconstr)->expr;
+
 							if (rel->relid != 1)
 								ChangeVarNodes((Node *) partconstr, 1,
 											   rel->relid, 0);
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 2ed8144..5d2ea90 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -252,6 +252,7 @@ slot_fill_defaults(LogicalRepRelMapEntry *rel, EState *estate,
 	for (attnum = 0; attnum < num_phys_attrs; attnum++)
 	{
 		Expr	   *defexpr;
+		PlannedExpr *planned_defexpr;
 
 		if (TupleDescAttr(desc, attnum)->attisdropped)
 			continue;
@@ -264,10 +265,12 @@ slot_fill_defaults(LogicalRepRelMapEntry *rel, EState *estate,
 		if (defexpr != NULL)
 		{
 			/* Run the expression through planner */
-			defexpr = expression_planner(defexpr);
+			planned_defexpr = expression_planner(defexpr);
+			defexpr = planned_defexpr->expr;
 
 			/* Initialize executable expression in copycontext */
-			defexprs[num_defaults] = ExecInitExpr(defexpr, NULL);
+			defexprs[num_defaults] = ExecInitExpr(defexpr, NULL,
+												  planned_defexpr->cachedExprs);
 			defmap[num_defaults] = attnum;
 			num_defaults++;
 		}
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index e38fd16..2079506 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -957,6 +957,7 @@ load_domaintype_info(TypeCacheEntry *typentry)
 			char	   *constring;
 			Expr	   *check_expr;
 			DomainConstraintState *r;
+			PlannedExpr *planned_check_expr;
 
 			/* Ignore non-CHECK constraints (presently, shouldn't be any) */
 			if (c->contype != CONSTRAINT_CHECK)
@@ -993,12 +994,13 @@ load_domaintype_info(TypeCacheEntry *typentry)
 			check_expr = (Expr *) stringToNode(constring);
 
 			/* ExecInitExpr will assume we've planned the expression */
-			check_expr = expression_planner(check_expr);
+			planned_check_expr = expression_planner(check_expr);
+			check_expr = planned_check_expr->expr;
 
 			r = makeNode(DomainConstraintState);
 			r->constrainttype = DOM_CONSTRAINT_CHECK;
 			r->name = pstrdup(NameStr(c->conname));
-			r->check_expr = check_expr;
+			r->check_expr = planned_check_expr;
 			r->check_exprstate = NULL;
 
 			MemoryContextSwitchTo(oldcxt);
@@ -1167,7 +1169,17 @@ prep_domain_constraints(List *constraints, MemoryContext execctx)
 		newr->constrainttype = r->constrainttype;
 		newr->name = r->name;
 		newr->check_expr = r->check_expr;
-		newr->check_exprstate = ExecInitExpr(r->check_expr, NULL);
+
+		/* check_expr is NULL if this is NOT NULL Constraint */
+		if (r->check_expr)
+		{
+			newr->check_exprstate = ExecInitExpr(r->check_expr->expr, NULL,
+												 r->check_expr->cachedExprs);
+		}
+		else
+		{
+			newr->check_exprstate = ExecInitExpr(NULL, NULL, NULL);
+		}
 
 		result = lappend(result, newr);
 	}
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 0ec8947..5521ae2 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -349,6 +349,15 @@ typedef struct ExprEvalStep
 		{
 			int			paramid;	/* numeric ID for parameter */
 			Oid			paramtype;	/* OID of parameter's datatype */
+
+			/*
+			 * 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). In this case all information about them is stored in
+			 * ExprState, not in EState.
+			 */
+			bool		dynamically_planned;
 		}			param;
 
 		/* for EEOP_PARAM_CALLBACK */
@@ -747,5 +756,7 @@ extern void ExecEvalAggOrderedTransDatum(ExprState *state, ExprEvalStep *op,
 							 ExprContext *econtext);
 extern void ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op,
 							 ExprContext *econtext);
+extern void ExecEvalCachedExpr(ExprState *state, ExprEvalStep *op,
+				   ExprContext *econtext);
 
 #endif							/* EXEC_EXPR_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index a7ea3c7..3c4708f 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -242,10 +242,13 @@ ExecProcNode(PlanState *node)
 /*
  * prototypes from functions in execExpr.c
  */
-extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
-extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
-extern ExprState *ExecInitQual(List *qual, PlanState *parent);
-extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
+extern ExprState *ExecInitExpr(Expr *node, PlanState *parent,
+			 List *cachedexprs);
+extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params,
+					   List *cachedexprs);
+extern ExprState *ExecInitQual(List *qual, PlanState *parent, List *cachedexpr);
+extern ExprState *ExecInitCheck(List *qual, PlanState *parent,
+			  List *cachedexpr);
 extern List *ExecInitExprList(List *nodes, PlanState *parent);
 extern ExprState *ExecBuildAggTrans(AggState *aggstate, struct AggStatePerPhaseData *phase,
 				  bool doSort, bool doHash);
@@ -263,6 +266,8 @@ extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
 extern ExprState *ExecPrepareQual(List *qual, EState *estate);
 extern ExprState *ExecPrepareCheck(List *qual, EState *estate);
 extern List *ExecPrepareExprList(List *nodes, EState *estate);
+extern void ExecInitCachedExprs(QueryDesc *queryDesc);
+extern void ExecSetDynamicallyPlannedCachedExprs(ExprState *state);
 
 /*
  * ExecEvalExpr
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index da7f52c..127a17b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -54,6 +54,25 @@ typedef Datum (*ExprStateEvalFunc) (struct ExprState *expression,
 /* expression is for use with ExecQual() */
 #define EEO_FLAG_IS_QUAL					(1 << 0)
 
+/*
+ * CachedExprsInfo
+ *
+ * This struct is used to store the ExprStates of subexpressions of cached
+ * expressions. It also stores the assigned PARAM_EXEC slot number of the first
+ * cached expression since the PARAM_EXEC nodes must be the first.
+ */
+typedef struct CachedExprsInfo
+{
+	struct ExprState *subexpr_states;	/* array of states of subexpressions */
+	int			num_cachedexprs;	/* length of array */
+
+	/*
+	 * The paramid of the first cached expression in es_param_exec_vals or
+	 * cachedexprs_vals (it is always 0 in the second case).
+	 */
+	int			first_cached_paramid;
+} CachedExprsInfo;
+
 typedef struct ExprState
 {
 	Node		tag;
@@ -93,6 +112,17 @@ typedef struct ExprState
 	void	   *evalfunc_private;
 
 	/*
+	 * 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). In this
+	 * case all information about them is stored in the ExprState of this plan,
+	 * and not in EState.
+	 */
+	ParamExecData *cachedexprs_vals;	/* values of cached expressions */
+	struct CachedExprsInfo cachedexprs_info;	/* to evaluate subexpressions of
+												 * cached expressions */
+
+	/*
 	 * XXX: following fields only needed during "compilation" (ExecInitExpr);
 	 * could be thrown away afterwards.
 	 */
@@ -571,6 +601,9 @@ typedef struct EState
 	 */
 	int			es_jit_flags;
 	struct JitContext *es_jit;
+
+	/* To evaluate subexpressions of cached expressions. */
+	struct CachedExprsInfo es_cachedexprs_info;
 } EState;
 
 
@@ -875,7 +908,7 @@ 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 */
+	PlannedExpr	*check_expr;		/* for CHECK, a boolean expression */
 	ExprState  *check_exprstate;	/* check_expr's eval state, or NULL */
 } DomainConstraintState;
 
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 04b03c7..0622a2b 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -129,14 +129,15 @@ typedef struct ParamListInfoData
  *	  ParamExecData
  *
  *	  ParamExecData entries are used for executor internal parameters
- *	  (that is, values being passed into or out of a sub-query).  The
- *	  paramid of a PARAM_EXEC Param is a (zero-based) index into an
- *	  array of ParamExecData records, which is referenced through
+ *	  (that is, values being passed into or out of a sub-query) or executor
+ *	  cached expressions (that is, the expression is executed only once for all
+ *	  output rows).  The paramid of a PARAM_EXEC Param is a (zero-based) index
+ *	  into an array of ParamExecData records, which is referenced through
  *	  es_param_exec_vals or ecxt_param_exec_vals.
  *
- *	  If execPlan is not NULL, it points to a SubPlanState node that needs
- *	  to be executed to produce the value.  (This is done so that we can have
- *	  lazy evaluation of InitPlans: they aren't executed until/unless a
+ *	  If execPlan is not NULL, it points to a SubPlanState or ExprState node
+ *	  that needs to be executed to produce the value.  (This is done so that we
+ *	  can have lazy evaluation of InitPlans: they aren't executed until/unless a
  *	  result value is needed.)	Otherwise the value is assumed to be valid
  *	  when needed.
  * ----------------
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index c090396..81b6a84 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -52,7 +52,7 @@ extern void mark_partial_aggref(Aggref *agg, AggSplit aggsplit);
 extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
 							 double tuple_fraction);
 
-extern Expr *expression_planner(Expr *expr);
+extern PlannedExpr *expression_planner(Expr *expr);
 
 extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
 
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 1eb421b..f963e0b 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -150,7 +150,7 @@ typedef struct					/* lookup key for cast info */
 typedef struct					/* cast_hash table entry */
 {
 	plpgsql_CastHashKey key;	/* hash key --- MUST BE FIRST */
-	Expr	   *cast_expr;		/* cast expression, or NULL if no-op cast */
+	PlannedExpr *cast_expr;		/* cast expression, or NULL if no-op cast */
 	/* ExprState is valid only when cast_lxid matches current LXID */
 	ExprState  *cast_exprstate; /* expression's eval tree */
 	bool		cast_in_use;	/* true while we're executing eval tree */
@@ -6055,9 +6055,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 	if (expr->expr_simple_lxid != curlxid)
 	{
 		oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
+
+		/*
+		 * Initialize cached expressions as dynamically planned since a simple
+		 * expression can be replanned for the same executor state.
+		 */
 		expr->expr_simple_state =
 			ExecInitExprWithParams(expr->expr_simple_expr,
-								   econtext->ecxt_param_list_info);
+								   econtext->ecxt_param_list_info,
+								   expr->expr_simple_cachedexprs);
 		expr->expr_simple_in_use = false;
 		expr->expr_simple_lxid = curlxid;
 		MemoryContextSwitchTo(oldcontext);
@@ -6082,6 +6088,12 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 	expr->expr_simple_in_use = true;
 
 	/*
+	 * The executor is used (again) from the very beginning, so the cached
+	 * expressions must be (re)calculated.
+	 */
+	ExecSetDynamicallyPlannedCachedExprs(expr->expr_simple_state);
+
+	/*
 	 * Finally we can call the executor to evaluate the expression
 	 */
 	*result = ExecEvalExpr(expr->expr_simple_state,
@@ -7501,6 +7513,7 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
 		/* We've not looked up this coercion before */
 		Node	   *cast_expr;
 		CaseTestExpr *placeholder;
+		PlannedExpr *planned_cast_expr;
 
 		/*
 		 * Since we could easily fail (no such coercion), construct a
@@ -7574,12 +7587,17 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
 		if (cast_expr)
 		{
 			/* ExecInitExpr assumes we've planned the expression */
-			cast_expr = (Node *) expression_planner((Expr *) cast_expr);
+			planned_cast_expr = expression_planner((Expr *) cast_expr);
 
 			/* Now copy the tree into cast_hash_context */
 			MemoryContextSwitchTo(estate->cast_hash_context);
 
-			cast_expr = copyObject(cast_expr);
+			planned_cast_expr = copyObject(planned_cast_expr);
+			cast_expr = (Node *) planned_cast_expr->expr;
+		}
+		else
+		{
+			planned_cast_expr = NULL;
 		}
 
 		MemoryContextSwitchTo(oldcontext);
@@ -7589,7 +7607,7 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
 														   (void *) &cast_key,
 														   HASH_ENTER, &found);
 		Assert(!found);			/* wasn't there a moment ago */
-		cast_entry->cast_expr = (Expr *) cast_expr;
+		cast_entry->cast_expr = planned_cast_expr;
 		cast_entry->cast_exprstate = NULL;
 		cast_entry->cast_in_use = false;
 		cast_entry->cast_lxid = InvalidLocalTransactionId;
@@ -7614,8 +7632,11 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
 	curlxid = MyProc->lxid;
 	if (cast_entry->cast_lxid != curlxid || cast_entry->cast_in_use)
 	{
+		PlannedExpr *cast_expr = cast_entry->cast_expr;
+
 		oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
-		cast_entry->cast_exprstate = ExecInitExpr(cast_entry->cast_expr, NULL);
+		cast_entry->cast_exprstate = ExecInitExpr(cast_expr->expr, NULL,
+												  cast_expr->cachedExprs);
 		cast_entry->cast_in_use = false;
 		cast_entry->cast_lxid = curlxid;
 		MemoryContextSwitchTo(oldcontext);
@@ -7803,6 +7824,7 @@ exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan)
 	/* Also stash away the expression result type */
 	expr->expr_simple_type = exprType((Node *) tle_expr);
 	expr->expr_simple_typmod = exprTypmod((Node *) tle_expr);
+	expr->expr_simple_cachedexprs = stmt->cachedExprs;
 }
 
 /*
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index fe61779..0415953 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -230,6 +230,8 @@ typedef struct PLpgSQL_expr
 	int			expr_simple_generation; /* plancache generation we checked */
 	Oid			expr_simple_type;	/* result type Oid, if simple */
 	int32		expr_simple_typmod; /* result typmod, if simple */
+	List	   *expr_simple_cachedexprs;	/* list of non-internal cached
+											 * expressions, or NIL */
 
 	/*
 	 * if expr is simple AND prepared in current transaction,
-- 
2.7.4

v9-0005-Precalculate-stable-and-immutable-functions-plann.patchtext/plain; name=v9-0005-Precalculate-stable-and-immutable-functions-plann.patchDownload
From f26a327052b76701c408290423064b46b8660e30 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyakova@postgrespro.ru>
Date: Thu, 24 May 2018 14:07:13 +0300
Subject: [PATCH v9] Precalculate stable and immutable functions: planner

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.

In this patch the function / operator / another expression is precalculated (=
calculated once for all output rows, but as many times as the expression is
mentioned in the query) if:
1) it doesn't return a set,
2) it's not volatile itself,
3) its arguments are also constants or precalculated expressions.

Costs are changed to reflect the changed behaviour.
Tests and small notes in the documentation are included.

Note: caching of domain constraints is not currently supported, as we cannot
assume that the planner has access to the same domain constraints that will
apply at runtime.
---
 contrib/postgres_fdw/deparse.c                     |   11 +
 doc/src/sgml/ref/create_function.sgml              |   14 +
 doc/src/sgml/xfunc.sgml                            |   13 +-
 src/backend/catalog/dependency.c                   |    7 +
 src/backend/commands/explain.c                     |    2 +-
 src/backend/commands/tablecmds.c                   |    2 +
 src/backend/executor/execExpr.c                    |    5 +
 src/backend/executor/nodeAgg.c                     |    5 +
 src/backend/executor/nodeHashjoin.c                |    1 +
 src/backend/executor/nodeIndexscan.c               |   20 +-
 src/backend/executor/nodeMergejoin.c               |    1 +
 src/backend/executor/nodeSubplan.c                 |    5 +-
 src/backend/executor/nodeTidscan.c                 |    2 +-
 src/backend/nodes/makefuncs.c                      |   26 +
 src/backend/nodes/nodeFuncs.c                      |   52 +-
 src/backend/optimizer/path/allpaths.c              |    8 +
 src/backend/optimizer/path/clausesel.c             |   23 +-
 src/backend/optimizer/path/costsize.c              |   24 +
 src/backend/optimizer/path/equivclass.c            |   48 +-
 src/backend/optimizer/path/indxpath.c              |   44 +-
 src/backend/optimizer/path/joinrels.c              |   10 +-
 src/backend/optimizer/path/tidpath.c               |    8 +-
 src/backend/optimizer/plan/analyzejoins.c          |    8 +-
 src/backend/optimizer/plan/createplan.c            |   37 +-
 src/backend/optimizer/plan/initsplan.c             |   19 +-
 src/backend/optimizer/plan/planner.c               |   24 +
 src/backend/optimizer/plan/setrefs.c               |   32 +-
 src/backend/optimizer/plan/subselect.c             |   96 +-
 src/backend/optimizer/prep/prepjointree.c          |   13 +-
 src/backend/optimizer/prep/prepqual.c              |  144 +-
 src/backend/optimizer/prep/prepunion.c             |    5 +
 src/backend/optimizer/util/clauses.c               | 1364 ++++-
 src/backend/optimizer/util/orclauses.c             |   23 +-
 src/backend/optimizer/util/predtest.c              |  118 +-
 src/backend/optimizer/util/relnode.c               |    1 +
 src/backend/optimizer/util/restrictinfo.c          |   20 +-
 src/backend/optimizer/util/tlist.c                 |   12 +-
 src/backend/optimizer/util/var.c                   |   40 +
 src/backend/parser/parse_coerce.c                  |    6 +
 src/backend/partitioning/partprune.c               |   71 +-
 src/backend/rewrite/rewriteManip.c                 |    9 +
 src/backend/statistics/dependencies.c              |   10 +-
 src/backend/utils/adt/datetime.c                   |    1 +
 src/backend/utils/adt/numeric.c                    |    1 +
 src/backend/utils/adt/ruleutils.c                  |   81 +-
 src/backend/utils/adt/selfuncs.c                   |   51 +-
 src/backend/utils/adt/timestamp.c                  |    1 +
 src/backend/utils/adt/varbit.c                     |    1 +
 src/backend/utils/adt/varchar.c                    |    1 +
 src/backend/utils/fmgr/funcapi.c                   |   26 +-
 src/include/nodes/makefuncs.h                      |    2 +
 src/include/optimizer/clauses.h                    |   18 +-
 src/pl/plpgsql/src/pl_exec.c                       |   46 +-
 .../expected/precalculate_stable_functions.out     | 6122 ++++++++++++++++++++
 .../expected/precalculate_stable_functions_1.out   | 5768 ++++++++++++++++++
 src/test/regress/parallel_schedule                 |    2 +-
 src/test/regress/serial_schedule                   |    1 +
 .../regress/sql/precalculate_stable_functions.sql  | 1932 ++++++
 58 files changed, 16007 insertions(+), 430 deletions(-)
 create mode 100644 src/test/regress/expected/precalculate_stable_functions.out
 create mode 100644 src/test/regress/expected/precalculate_stable_functions_1.out
 create mode 100644 src/test/regress/sql/precalculate_stable_functions.sql

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index d272719..aae74f3 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -775,6 +775,14 @@ foreign_expr_walker(Node *node,
 					state = FDW_COLLATE_UNSAFE;
 			}
 			break;
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+
+				return foreign_expr_walker((Node *) cachedexpr->subexpr,
+										   glob_cxt, outer_cxt);
+			}
+			break;
 		default:
 
 			/*
@@ -2305,6 +2313,9 @@ deparseExpr(Expr *node, deparse_expr_cxt *context)
 		case T_Aggref:
 			deparseAggref((Aggref *) node, context);
 			break;
+		case T_CachedExpr:
+			deparseExpr((Expr *) ((CachedExpr *) node)->subexpr, context);
+			break;
 		default:
 			elog(ERROR, "unsupported expression type for deparse: %d",
 				 (int) nodeTag(node));
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index c0adb8c..21e2f5f 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -336,6 +336,20 @@ CREATE [ OR REPLACE ] FUNCTION
        <literal>setval()</literal>.
       </para>
 
+      <note>
+       <para>
+        Stable, immutable functions and other nonvolatile expressions are
+        precalculated (= calculated once for all output rows, but as many times
+        as expression is mentioned in query), if they don't return a set and
+        their arguments are constants or recursively precalculated expressions.
+       </para>
+
+       <para>
+        Now this feature is not supported for domain constraints (see <xref
+        linkend="sql-createdomain"/>).
+       </para>
+      </note>
+
       <para>
        For additional details see <xref linkend="xfunc-volatility"/>.
       </para>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index bbc3766..169513f 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1533,9 +1533,20 @@ CREATE FUNCTION test(int, int) RETURNS int
 
    <para>
     For best optimization results, you should label your functions with the
-    strictest volatility category that is valid for them.
+    strictest volatility category that is valid for them. Stable, immutable
+    functions and other nonvolatile expressions are precalculated (= calculated
+    once for all output rows, but as many times as expression is mentioned in
+    query), if they don't return a set and their arguments are constants or
+    recursively precalculated expressions.
    </para>
 
+   <note>
+    <para>
+     Now this feature is not supported for domain constraints (see <xref
+     linkend="sql-createdomain"/>).
+    </para>
+   </note>
+
    <para>
     Any function with side-effects <emphasis>must</emphasis> be labeled
     <literal>VOLATILE</literal>, so that calls to it cannot be optimized away.
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 4f1d365..cf3cc91 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2027,6 +2027,13 @@ find_expr_references_walker(Node *node,
 						   context->addrs);
 		/* fall through to examine arguments */
 	}
+	else if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		return find_expr_references_walker((Node *) cachedexpr->subexpr,
+										   context);
+	}
 
 	return expression_tree_walker(node, find_expr_references_walker,
 								  (void *) context);
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 73d94b7..bdf9a74 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1589,7 +1589,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				List	   *tidquals = ((TidScan *) plan)->tidquals;
 
 				if (list_length(tidquals) > 1)
-					tidquals = list_make1(make_orclause(tidquals));
+					tidquals = list_make1(make_orclause(tidquals, false));
 				show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);
 				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
 				if (plan->qual)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5cc9cf2..cdd3faa 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9305,6 +9305,8 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
 				return true;
 			expr = (Node *) d->arg;
 		}
+		else if (IsA(expr, CachedExpr))
+			expr = (Node *) ((CachedExpr *) expr)->subexpr;
 		else
 			return true;
 	}
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 55996ff..6bbf784 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2432,6 +2432,11 @@ get_last_attnums_walker(Node *node, LastAttnumInfo *info)
 		return false;
 	if (IsA(node, GroupingFunc))
 		return false;
+
+	/* Don't examine cached expressions since they do not contain vars */
+	if (IsA(node, CachedExpr))
+		return false;
+
 	return expression_tree_walker(node, get_last_attnums_walker,
 								  (void *) info);
 }
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 7781e87..bd56a1b 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -1240,6 +1240,11 @@ find_unaggregated_cols_walker(Node *node, Bitmapset **colnos)
 		/* do not descend into aggregate exprs */
 		return false;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* do not descend into cached exprs since they do not contain vars */
+		return false;
+	}
 	return expression_tree_walker(node, find_unaggregated_cols_walker,
 								  (void *) colnos);
 }
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 23215a9..97dfe30 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -729,6 +729,7 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 	hoperators = NIL;
 	foreach(l, node->hashclauses)
 	{
+		/* this is not used for pseudoconstants */
 		OpExpr	   *hclause = lfirst_node(OpExpr, l);
 
 		lclauses = lappend(lclauses, ExecInitExpr(linitial(hclause->args),
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 404c61e..97c5830 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -1230,6 +1230,10 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 		int			indnkeyatts;
 
 		indnkeyatts = IndexRelationGetNumberOfKeyAttributes(index);
+
+		/*
+		 * do not check for cached expressions because they do not contain vars
+		 */
 		if (IsA(clause, OpExpr))
 		{
 			/* indexkey op const or indexkey op expression */
@@ -1276,8 +1280,8 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 			 */
 			rightop = (Expr *) get_rightop(clause);
 
-			if (rightop && IsA(rightop, RelabelType))
-				rightop = ((RelabelType *) rightop)->arg;
+			if (rightop && IsAIfCached(rightop, RelabelType))
+				rightop = castNodeIfCached(RelabelType, rightop)->arg;
 
 			Assert(rightop != NULL);
 
@@ -1406,8 +1410,12 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 				rightop = (Expr *) lfirst(rargs_cell);
 				rargs_cell = lnext(rargs_cell);
 
-				if (rightop && IsA(rightop, RelabelType))
-					rightop = ((RelabelType *) rightop)->arg;
+				/*
+				 * Do not check for cached expressions because index expressions
+				 * can only contain immutable functions.
+				 */
+				if (rightop && IsAIfCached(rightop, RelabelType))
+					rightop = castNodeIfCached(RelabelType, rightop)->arg;
 
 				Assert(rightop != NULL);
 
@@ -1520,8 +1528,8 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 			 */
 			rightop = (Expr *) lsecond(saop->args);
 
-			if (rightop && IsA(rightop, RelabelType))
-				rightop = ((RelabelType *) rightop)->arg;
+			if (rightop && IsAIfCached(rightop, RelabelType))
+				rightop = castNodeIfCached(RelabelType, rightop)->arg;
 
 			Assert(rightop != NULL);
 
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 93d91ff..cd4dc65 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -201,6 +201,7 @@ MJExamineQuals(List *mergeclauses,
 		Oid			op_righttype;
 		Oid			sortfunc;
 
+		/* This is not used for pseudoconstants */
 		if (!IsA(qual, OpExpr))
 			elog(ERROR, "mergejoin clause is not an OpExpr");
 
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 2c0addf..f077796 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -871,13 +871,15 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		 *
 		 * We also extract the combining operators themselves to initialize
 		 * the equality and hashing functions for the hash tables.
+		 *
+		 * This is not used for pseudoconstants.
 		 */
 		if (IsA(subplan->testexpr, OpExpr))
 		{
 			/* single combining operator */
 			oplist = list_make1(subplan->testexpr);
 		}
-		else if (and_clause((Node *) subplan->testexpr))
+		else if (and_clause((Node *) subplan->testexpr, false))
 		{
 			/* multiple combining operators */
 			oplist = castNode(BoolExpr, subplan->testexpr)->args;
@@ -900,6 +902,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		i = 1;
 		foreach(l, oplist)
 		{
+			/* This is not used for pseudoconstants */
 			OpExpr	   *opexpr = lfirst_node(OpExpr, l);
 			Expr	   *expr;
 			TargetEntry *tle;
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 8c8c53d..689d0af 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -74,7 +74,7 @@ TidExprListCreate(TidScanState *tidstate)
 		Expr	   *expr = (Expr *) lfirst(l);
 		TidExpr    *tidexpr = (TidExpr *) palloc0(sizeof(TidExpr));
 
-		if (is_opclause(expr))
+		if (is_opclause((Node *) expr, false))
 		{
 			Node	   *arg1;
 			Node	   *arg2;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 1bd2599..f1add53 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -628,3 +628,29 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+/*
+ * makeCachedExpr -
+ *	  create a CachedExpr node.
+ *
+ * This is an auxiliary function for other functions: checking that this
+ * subexpression can be cached is performed earlier in them.
+ *
+ * Pass subexpr as NULL to get a dummy cached expression (for example, cached
+ * case_val in eval_const_expressions_mutator).
+ *
+ * NOTE: the cached ids of all non-internal cached expressions must be set by
+ * the function set_non_internal_cachedexprs_walker after all mutations.
+ * Otherwise all the created cached expressions are considered internal and will
+ * not be cached by themselves.
+ */
+CachedExpr *
+makeCachedExpr(CacheableExpr *subexpr)
+{
+	CachedExpr *cachedexpr = makeNode(CachedExpr);
+
+	cachedexpr->subexpr = subexpr;
+	cachedexpr->cached_id = -1;
+
+	return cachedexpr;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 9fdd738..54a3780 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -598,30 +598,55 @@ exprIsLengthCoercion(const Node *expr, int32 *coercedTypmod)
  * This is primarily intended to be used during planning.  Therefore, it
  * strips any existing RelabelType nodes to maintain the planner's invariant
  * that there are not adjacent RelabelTypes.
+ *
+ * NOTE: if the expression contains cached expressions, it should be processed
+ * using set_non_internal_cachedexprs_walker to store all non-internal cached
+ * expressions separatly for the executor's purposes. Otherwise all the created
+ * cached expressions are considered internal and will not be cached by
+ * themselves.
  */
 Node *
 relabel_to_typmod(Node *expr, int32 typmod)
 {
 	Oid			type = exprType(expr);
 	Oid			coll = exprCollation(expr);
+	bool		cached = false;
+	Node	   *result;
 
-	/* Strip any existing RelabelType node(s) */
-	while (expr && IsA(expr, RelabelType))
-		expr = (Node *) ((RelabelType *) expr)->arg;
+	/* Strip any existing (and possibly cached) RelabelType node(s) */
+	while (expr && IsAIfCached(expr, RelabelType))
+	{
+		cached |= IsA(expr, CachedExpr);
+		expr = (Node *) castNodeIfCached(RelabelType, expr)->arg;
+	}
 
 	/* Apply new typmod, preserving the previous exposed type and collation */
-	return (Node *) makeRelabelType((Expr *) expr, type, typmod, coll,
-									COERCE_EXPLICIT_CAST);
+	result = (Node *) makeRelabelType((Expr *) expr, type, typmod, coll,
+									  COERCE_EXPLICIT_CAST);
+
+	/* Cache it if it was cached in the original node */
+	if (cached)
+		result = (Node *) makeCachedExpr((CacheableExpr *) result);
+
+	return result;
 }
 
 /*
  * strip_implicit_coercions: remove implicit coercions at top level of tree
  *
  * This doesn't modify or copy the input expression tree, just return a
- * pointer to a suitable place within it.
+ * pointer to a suitable place within it. But if necessary it creates a new
+ * CachedExpr node since the original node can be used elsewhere as an internal
+ * cached expression.
  *
  * Note: there isn't any useful thing we can do with a RowExpr here, so
  * just return it unchanged, even if it's marked as an implicit coercion.
+ *
+ * NOTE: if the expression contains cached expressions, it should be processed
+ * using set_non_internal_cachedexprs_walker to store all non-internal cached
+ * expressions separatly for the executor's purposes. Otherwise all the created
+ * cached expressions are considered internal and will not be cached by
+ * themselves.
  */
 Node *
 strip_implicit_coercions(Node *node)
@@ -670,6 +695,21 @@ strip_implicit_coercions(Node *node)
 		if (c->coercionformat == COERCE_IMPLICIT_CAST)
 			return strip_implicit_coercions((Node *) c->arg);
 	}
+	else if (IsA(node, CachedExpr))
+	{
+		Node	   *result = strip_implicit_coercions(
+			(Node *) ((CachedExpr *) node)->subexpr);
+
+		/*
+		 * If necessary, cache the result node. Create a new CachedExpr node
+		 * since the original node can be used elsewhere as an internal cached
+		 * expression.
+		 */
+		if (IsA(result, Const) || IsA(result, CachedExpr))
+			return result;
+		else
+			return (Node *) makeCachedExpr((CacheableExpr *) result);
+	}
 	return node;
 }
 
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 477b9f7..f0b39ad 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1091,6 +1091,14 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 					/* tell createplan.c to check for gating quals */
 					root->hasPseudoConstantQuals = true;
 				}
+
+				/*
+				 * Store all non-internal cached expressions separatly for the
+				 * executor's purposes.
+				 */
+				set_non_internal_cachedexprs_walker(onecq,
+													&(root->glob->cachedExprs));
+
 				/* reconstitute RestrictInfo with appropriate properties */
 				childquals = lappend(childquals,
 									 make_restrictinfo((Expr *) onecq,
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index f471794..3b35c93 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -192,7 +192,8 @@ clauselist_selectivity(PlannerInfo *root,
 		 * the simple way we are expecting.)  Most of the tests here can be
 		 * done more efficiently with rinfo than without.
 		 */
-		if (is_opclause(clause) && list_length(((OpExpr *) clause)->args) == 2)
+		if (is_opclause(clause, false) &&
+			list_length(((OpExpr *) clause)->args) == 2)
 		{
 			OpExpr	   *expr = (OpExpr *) clause;
 			bool		varonleft = true;
@@ -688,7 +689,7 @@ clause_selectivity(PlannerInfo *root,
 			/* XXX any way to do better than default? */
 		}
 	}
-	else if (not_clause(clause))
+	else if (not_clause(clause, false))
 	{
 		/* inverse of the selectivity of the underlying clause */
 		s1 = 1.0 - clause_selectivity(root,
@@ -697,7 +698,7 @@ clause_selectivity(PlannerInfo *root,
 									  jointype,
 									  sjinfo);
 	}
-	else if (and_clause(clause))
+	else if (and_clause(clause, false))
 	{
 		/* share code with clauselist_selectivity() */
 		s1 = clauselist_selectivity(root,
@@ -706,7 +707,7 @@ clause_selectivity(PlannerInfo *root,
 									jointype,
 									sjinfo);
 	}
-	else if (or_clause(clause))
+	else if (or_clause(clause, false))
 	{
 		/*
 		 * Selectivities for an OR clause are computed as s1+s2 - s1*s2 to
@@ -728,7 +729,7 @@ clause_selectivity(PlannerInfo *root,
 			s1 = s1 + s2 - s1 * s2;
 		}
 	}
-	else if (is_opclause(clause) || IsA(clause, DistinctExpr))
+	else if (is_opclause(clause, false) || IsA(clause, DistinctExpr))
 	{
 		OpExpr	   *opclause = (OpExpr *) clause;
 		Oid			opno = opclause->opno;
@@ -827,6 +828,18 @@ clause_selectivity(PlannerInfo *root,
 								jointype,
 								sjinfo);
 	}
+	else if (IsA(clause, CachedExpr))
+	{
+		/*
+		 * Not sure this case is needed, but it can't hurt.
+		 * Calculate selectivity of subexpression.
+		 */
+		s1 = clause_selectivity(root,
+								(Node *) ((CachedExpr *) clause)->subexpr,
+								varRelid,
+								jointype,
+								sjinfo);
+	}
 	else
 	{
 		/*
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index a2a7e0c..ecd3a25 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -1205,6 +1205,9 @@ cost_tidscan(Path *path, PlannerInfo *root,
 	ntuples = 0;
 	foreach(l, tidquals)
 	{
+		/* This is not used for pseudoconstants */
+		Assert(!IsA(lfirst(l), CachedExpr));
+
 		if (IsA(lfirst(l), ScalarArrayOpExpr))
 		{
 			/* Each element of the array yields 1 tuple */
@@ -3973,6 +3976,27 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 		 */
 		return false;
 	}
+	else if (IsA(node, CachedExpr))
+	{
+		/*
+		 * Calculate subexpression cost as usual and add it to startup cost
+		 * (because subexpression will be executed only once for all tuples).
+		 */
+		cost_qual_eval_context subexpr_context;
+
+		subexpr_context.root = context->root;
+		subexpr_context.total.startup = 0;
+		subexpr_context.total.per_tuple = 0;
+
+		cost_qual_eval_walker((Node *) ((CachedExpr *) node)->subexpr,
+							  &subexpr_context);
+
+		context->total.startup +=
+			(subexpr_context.total.startup + subexpr_context.total.per_tuple);
+
+		/* do NOT recurse into children */
+		return false;
+	}
 
 	/* recurse into children */
 	return expression_tree_walker(node, cost_qual_eval_walker,
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index b22b36e..4dc44c9 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -143,7 +143,10 @@ process_equivalence(PlannerInfo *root,
 		return false;
 
 	/* Extract info from given clause */
-	Assert(is_opclause(clause));
+
+	/* This is not used for pseudoconstants */
+	Assert(is_opclause((Node *) clause, false));
+
 	opno = ((OpExpr *) clause)->opno;
 	collation = ((OpExpr *) clause)->inputcollid;
 	item1 = (Expr *) get_leftop(clause);
@@ -488,12 +491,19 @@ process_equivalence(PlannerInfo *root,
  *
  * Note this code assumes that the expression has already been through
  * eval_const_expressions, so there are no CollateExprs and no redundant
- * RelabelTypes.
+ * RelabelTypes, but there may be cached expressions.
+ *
+ * NOTE: the expression should be processed using
+ * set_non_internal_cachedexprs_walker to store all non-internal cached
+ * expressions separatly for the executor's purposes. Otherwise all the created
+ * cached expressions are considered internal and will not be cached by
+ * themselves.
  */
 Expr *
 canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation)
 {
 	Oid			expr_type = exprType((Node *) expr);
+	bool		cached = false;
 
 	/*
 	 * For a polymorphic-input-type opclass, just keep the same exposed type.
@@ -509,16 +519,20 @@ canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation)
 		exprCollation((Node *) expr) != req_collation)
 	{
 		/*
-		 * Strip any existing RelabelType, then add a new one if needed. This
-		 * is to preserve the invariant of no redundant RelabelTypes.
+		 * Strip any existing (and possibly cached) RelabelType, then add a new
+		 * one if needed. This is to preserve the invariant of no redundant
+		 * RelabelTypes.
 		 *
 		 * If we have to change the exposed type of the stripped expression,
 		 * set typmod to -1 (since the new type may not have the same typmod
 		 * interpretation).  If we only have to change collation, preserve the
 		 * exposed typmod.
 		 */
-		while (expr && IsA(expr, RelabelType))
-			expr = (Expr *) ((RelabelType *) expr)->arg;
+		while (expr && IsAIfCached(expr, RelabelType))
+		{
+			cached |= IsA(expr, CachedExpr);
+			expr = castNodeIfCached(RelabelType, expr)->arg;
+		}
 
 		if (exprType((Node *) expr) != req_type)
 			expr = (Expr *) makeRelabelType(expr,
@@ -532,6 +546,10 @@ canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation)
 											exprTypmod((Node *) expr),
 											req_collation,
 											COERCE_IMPLICIT_CAST);
+
+		/* Cache the new node if it was cached in the original expression */
+		if (cached && !IsA(expr, CachedExpr))
+			expr = (Expr *) makeCachedExpr((CacheableExpr *) expr);
 	}
 
 	return expr;
@@ -1205,7 +1223,8 @@ generate_join_implied_equalities_normal(PlannerInfo *root,
 	 * choices, we prefer simple Var members (possibly with RelabelType) since
 	 * these are (a) cheapest to compute at runtime and (b) most likely to
 	 * have useful statistics. Also, prefer operators that are also
-	 * hashjoinable.
+	 * hashjoinable. Ignore cached RelabelTypes because they do not contain
+	 * vars.
 	 */
 	if (outer_members && inner_members)
 	{
@@ -1693,7 +1712,9 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo,
 				inner_nullable_relids;
 	ListCell   *lc1;
 
-	Assert(is_opclause(rinfo->clause));
+	/* This is not used for pseudoconstants */
+	Assert(is_opclause((Node *) rinfo->clause, false));
+
 	opno = ((OpExpr *) rinfo->clause)->opno;
 	collation = ((OpExpr *) rinfo->clause)->inputcollid;
 
@@ -1823,7 +1844,10 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
 		return false;
 
 	/* Extract needed info from the clause */
-	Assert(is_opclause(rinfo->clause));
+
+	/* This is not used for pseudoconstants */
+	Assert(is_opclause((Node *) rinfo->clause, false));
+
 	opno = ((OpExpr *) rinfo->clause)->opno;
 	collation = ((OpExpr *) rinfo->clause)->inputcollid;
 	op_input_types(opno, &left_type, &right_type);
@@ -1875,9 +1899,10 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
 		{
 			coal_em = (EquivalenceMember *) lfirst(lc2);
 			Assert(!coal_em->em_is_child);	/* no children yet */
-			if (IsA(coal_em->em_expr, CoalesceExpr))
+			if (IsAIfCached(coal_em->em_expr, CoalesceExpr))
 			{
-				CoalesceExpr *cexpr = (CoalesceExpr *) coal_em->em_expr;
+				CoalesceExpr *cexpr = castNodeIfCached(CoalesceExpr,
+													   coal_em->em_expr);
 				Node	   *cfirst;
 				Node	   *csecond;
 
@@ -2061,6 +2086,7 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root,
 				continue;		/* ignore children here */
 
 			/* EM must be a Var, possibly with RelabelType */
+			/* Ignore cached RelabelTypes because they do not contain vars */
 			var = (Var *) em->em_expr;
 			while (var && IsA(var, RelabelType))
 				var = (Var *) ((RelabelType *) var)->arg;
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index f295558..d72542a 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -928,6 +928,9 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 		{
 			RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
 
+			/* This is not used for pseudoconstants */
+			Assert(!IsA(rinfo->clause, CachedExpr));
+
 			if (IsA(rinfo->clause, ScalarArrayOpExpr))
 			{
 				if (!index->amsearcharray)
@@ -1284,6 +1287,9 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 		if (!restriction_is_or_clause(rinfo))
 			continue;
 
+		/* Its leaves are RestrictInfos and they are not cacheable */
+		Assert(!IsA(rinfo->orclause, CachedExpr));
+
 		/*
 		 * We must be able to match at least one index to each of the arms of
 		 * the OR, else we can't use it.
@@ -1294,8 +1300,8 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 			Node	   *orarg = (Node *) lfirst(j);
 			List	   *indlist;
 
-			/* OR arguments should be ANDs or sub-RestrictInfos */
-			if (and_clause(orarg))
+			/* OR arguments should be non-cached ANDs or sub-RestrictInfos */
+			if (and_clause(orarg, false))
 			{
 				List	   *andargs = ((BoolExpr *) orarg)->args;
 
@@ -2344,6 +2350,9 @@ match_clause_to_indexcol(IndexOptInfo *index,
 	opfamily = index->opfamily[indexcol];
 	idxcollation = index->indexcollations[indexcol];
 
+	/* This is not used for pseudoconstants. */
+	Assert(!IsA(clause, CachedExpr));
+
 	/* First check for boolean-index cases. */
 	if (IsBooleanOpfamily(opfamily))
 	{
@@ -2357,7 +2366,7 @@ match_clause_to_indexcol(IndexOptInfo *index,
 	 * RowCompareExpr, which we pass off to match_rowcompare_to_indexcol().
 	 * Or, if the index supports it, we can handle IS NULL/NOT NULL clauses.
 	 */
-	if (is_opclause(clause))
+	if (is_opclause((Node *) clause, false))
 	{
 		leftop = get_leftop(clause);
 		rightop = get_rightop(clause);
@@ -2698,9 +2707,9 @@ match_clause_to_ordering_op(IndexOptInfo *index,
 	idxcollation = index->indexcollations[indexcol];
 
 	/*
-	 * Clause must be a binary opclause.
+	 * Clause must be a binary non-cached opclause.
 	 */
-	if (!is_opclause(clause))
+	if (!is_opclause((Node *) clause, false))
 		return NULL;
 	leftop = get_leftop(clause);
 	rightop = get_rightop(clause);
@@ -3215,8 +3224,8 @@ match_index_to_operand(Node *operand,
 	 * we can assume there is at most one RelabelType node;
 	 * eval_const_expressions() will have simplified if more than one.
 	 */
-	if (operand && IsA(operand, RelabelType))
-		operand = (Node *) ((RelabelType *) operand)->arg;
+	if (operand && IsAIfCached(operand, RelabelType))
+		operand = (Node *) castNodeIfCached(RelabelType, operand)->arg;
 
 	indkey = index->indexkeys[indexcol];
 	if (indkey != 0)
@@ -3255,6 +3264,11 @@ match_index_to_operand(Node *operand,
 		indexkey = (Node *) lfirst(indexpr_item);
 
 		/*
+		 * Index expressions can only contain immutable functions.
+		 */
+		Assert(!IsA(indexkey, CachedExpr));
+
+		/*
 		 * Does it match the operand?  Again, strip any relabeling.
 		 */
 		if (indexkey && IsA(indexkey, RelabelType))
@@ -3333,11 +3347,14 @@ match_boolean_index_clause(Node *clause,
 						   int indexcol,
 						   IndexOptInfo *index)
 {
+	/* This is not used for pseudoconstants. */
+	Assert(!IsA(clause, CachedExpr));
+
 	/* Direct match? */
 	if (match_index_to_operand(clause, indexcol, index))
 		return true;
 	/* NOT clause? */
-	if (not_clause(clause))
+	if (not_clause(clause, false))
 	{
 		if (match_index_to_operand((Node *) get_notclausearg((Expr *) clause),
 								   indexcol, index))
@@ -3589,10 +3606,10 @@ expand_indexqual_conditions(IndexOptInfo *index,
 		}
 
 		/*
-		 * Else it must be an opclause (usual case), ScalarArrayOp,
-		 * RowCompare, or NullTest
+		 * Else it must be an non-cached clause: opclause (usual case),
+		 * ScalarArrayOp, RowCompare, or NullTest
 		 */
-		if (is_opclause(clause))
+		if (is_opclause((Node *) clause, false))
 		{
 			indexquals = list_concat(indexquals,
 									 expand_indexqual_opclause(rinfo,
@@ -3643,6 +3660,9 @@ expand_boolean_index_clause(Node *clause,
 							int indexcol,
 							IndexOptInfo *index)
 {
+	/* This is not used for pseudoconstants. */
+	Assert(!IsA(clause, CachedExpr));
+
 	/* Direct match? */
 	if (match_index_to_operand(clause, indexcol, index))
 	{
@@ -3653,7 +3673,7 @@ expand_boolean_index_clause(Node *clause,
 							 InvalidOid, InvalidOid);
 	}
 	/* NOT clause? */
-	if (not_clause(clause))
+	if (not_clause(clause, false))
 	{
 		Node	   *arg = (Node *) get_notclausearg((Expr *) clause);
 
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 7008e13..58328c9 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -1447,7 +1447,10 @@ have_partkey_equi_join(RelOptInfo *joinrel,
 			RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids))
 			continue;
 
-		/* Skip clauses which can not be used for a join. */
+		/*
+		 * Skip clauses which can not be used for a join (including cached
+		 * expressions).
+		 */
 		if (!rinfo->can_join)
 			continue;
 
@@ -1456,7 +1459,7 @@ have_partkey_equi_join(RelOptInfo *joinrel,
 			continue;
 
 		opexpr = (OpExpr *) rinfo->clause;
-		Assert(is_opclause(opexpr));
+		Assert(is_opclause((Node *) opexpr, false));
 
 		/*
 		 * The equi-join between partition keys is strict if equi-join between
@@ -1544,6 +1547,9 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op)
 	while (IsA(expr, RelabelType))
 		expr = (Expr *) (castNode(RelabelType, expr))->arg;
 
+	/* This is not used for pseudoconstants. */
+	Assert(!IsA(expr, CachedExpr));
+
 	for (cnt = 0; cnt < rel->part_scheme->partnatts; cnt++)
 	{
 		ListCell   *lc;
diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c
index 3bb5b8d..a8ef71c 100644
--- a/src/backend/optimizer/path/tidpath.c
+++ b/src/backend/optimizer/path/tidpath.c
@@ -162,6 +162,8 @@ IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno)
  *	sub-clauses, in which case we could try to pick the most efficient one.
  *	In practice, such usage seems very unlikely, so we don't bother; we
  *	just exit as soon as we find the first candidate.
+ *
+ *	Do not check for cached expressions because they do not contain vars.
  */
 static List *
 TidQualFromExpr(Node *expr, int varno)
@@ -169,7 +171,7 @@ TidQualFromExpr(Node *expr, int varno)
 	List	   *rlst = NIL;
 	ListCell   *l;
 
-	if (is_opclause(expr))
+	if (is_opclause(expr, false))
 	{
 		/* base case: check for tideq opclause */
 		if (IsTidEqualClause((OpExpr *) expr, varno))
@@ -187,7 +189,7 @@ TidQualFromExpr(Node *expr, int varno)
 		if (((CurrentOfExpr *) expr)->cvarno == varno)
 			rlst = list_make1(expr);
 	}
-	else if (and_clause(expr))
+	else if (and_clause(expr, false))
 	{
 		foreach(l, ((BoolExpr *) expr)->args)
 		{
@@ -196,7 +198,7 @@ TidQualFromExpr(Node *expr, int varno)
 				break;
 		}
 	}
-	else if (or_clause(expr))
+	else if (or_clause(expr, false))
 	{
 		foreach(l, ((BoolExpr *) expr)->args)
 		{
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 0e73f9c..385b1cc 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -693,7 +693,7 @@ rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list)
 			 * same operator the subquery would consider; that's all right
 			 * since query_is_distinct_for can resolve such cases.)  The
 			 * caller's mergejoinability test should have selected only
-			 * OpExprs.
+			 * non-cached OpExprs.
 			 */
 			op = castNode(OpExpr, rinfo->clause)->opno;
 
@@ -712,6 +712,12 @@ rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list)
 				var = (Var *) ((RelabelType *) var)->arg;
 
 			/*
+			 * Pseudoconstant operands do not pass the caller's mergejoinability
+			 * test.
+			 */
+			Assert(!IsA(var, CachedExpr));
+
+			/*
 			 * If inner side isn't a Var referencing a subquery output column,
 			 * this clause doesn't help us.
 			 */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index ca2e052..e711ae9 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3050,13 +3050,13 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
 		else if (list_length(subquals) <= 1)
 			*qual = subquals;
 		else
-			*qual = list_make1(make_orclause(subquals));
+			*qual = list_make1(make_orclause(subquals, false));
 		if (const_true_subindexqual)
 			*indexqual = NIL;
 		else if (list_length(subindexquals) <= 1)
 			*indexqual = subindexquals;
 		else
-			*indexqual = list_make1(make_orclause(subindexquals));
+			*indexqual = list_make1(make_orclause(subindexquals, false));
 		*indexECs = NIL;
 	}
 	else if (IsA(bitmapqual, IndexPath))
@@ -3160,7 +3160,7 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
 	 */
 	ortidquals = tidquals;
 	if (list_length(ortidquals) > 1)
-		ortidquals = list_make1(make_orclause(ortidquals));
+		ortidquals = list_make1(make_orclause(ortidquals, false));
 	scan_clauses = list_difference(scan_clauses, ortidquals);
 
 	scan_plan = make_tidscan(tlist,
@@ -4266,7 +4266,9 @@ create_hashjoin_plan(PlannerInfo *root,
 		OpExpr	   *clause = (OpExpr *) linitial(hashclauses);
 		Node	   *node;
 
-		Assert(is_opclause(clause));
+		/* This is not used for pseudoconstants */
+		Assert(is_opclause((Node *) clause, false));
+
 		node = (Node *) linitial(clause->args);
 		if (IsA(node, RelabelType))
 			node = (Node *) ((RelabelType *) node)->arg;
@@ -4449,6 +4451,11 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 		/* And return the replacement Param */
 		return (Node *) param;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* Don't examine cached expressions since they do not contain vars */
+		return copyObject(node);
+	}
 	return expression_tree_mutator(node,
 								   replace_nestloop_params_mutator,
 								   (void *) root);
@@ -4757,8 +4764,8 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
 	/*
 	 * Remove any binary-compatible relabeling of the indexkey
 	 */
-	if (IsA(node, RelabelType))
-		node = (Node *) ((RelabelType *) node)->arg;
+	if (IsAIfCached(node, RelabelType))
+		node = (Node *) castNodeIfCached(RelabelType, node)->arg;
 
 	Assert(indexcol >= 0 && indexcol < index->ncolumns);
 
@@ -4791,8 +4798,13 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
 				Node	   *indexkey;
 
 				indexkey = (Node *) lfirst(indexpr_item);
+
+				/* Index expressions can only contain immutable functions */
+				Assert(!IsA(indexkey, CachedExpr));
+
 				if (indexkey && IsA(indexkey, RelabelType))
 					indexkey = (Node *) ((RelabelType *) indexkey)->arg;
+
 				if (equal(node, indexkey))
 				{
 					result = makeVar(INDEX_VAR, indexcol + 1,
@@ -4833,7 +4845,9 @@ get_switched_clauses(List *clauses, Relids outerrelids)
 		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
 		OpExpr	   *clause = (OpExpr *) restrictinfo->clause;
 
-		Assert(is_opclause(clause));
+		/* This is not used for pseudoconstants */
+		Assert(is_opclause((Node *) clause, false));
+
 		if (bms_is_subset(restrictinfo->right_relids, outerrelids))
 		{
 			/*
@@ -5924,7 +5938,11 @@ find_ec_member_for_tle(EquivalenceClass *ec,
 	Expr	   *tlexpr;
 	ListCell   *lc;
 
-	/* We ignore binary-compatible relabeling on both ends */
+	/*
+	 * We ignore binary-compatible relabeling on both ends.
+	 * Do not worry about cached expressions because in any case they cannot be
+	 * equal to the non-pseudoconstant member of EquivalenceClass (see below).
+	 */
 	tlexpr = tle->expr;
 	while (tlexpr && IsA(tlexpr, RelabelType))
 		tlexpr = ((RelabelType *) tlexpr)->arg;
@@ -5936,7 +5954,8 @@ find_ec_member_for_tle(EquivalenceClass *ec,
 
 		/*
 		 * We shouldn't be trying to sort by an equivalence class that
-		 * contains a constant, so no need to consider such cases any further.
+		 * contains a constant (including cached expressions), so no need to
+		 * consider such cases any further.
 		 */
 		if (em->em_is_const)
 			continue;
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 01335db..498ef41 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -1481,7 +1481,7 @@ compute_semijoin_info(SpecialJoinInfo *sjinfo, List *clause)
 		Relids		all_varnos;
 		Oid			opinputtype;
 
-		/* Is it a binary opclause? */
+		/* Is it a binary non-cached opclause? */
 		if (!IsA(op, OpExpr) ||
 			list_length(op->args) != 2)
 		{
@@ -1503,7 +1503,7 @@ compute_semijoin_info(SpecialJoinInfo *sjinfo, List *clause)
 			return;
 		}
 
-		/* Extract data from binary opclause */
+		/* Extract data from binary non-cached opclause */
 		opno = op->opno;
 		left_expr = linitial(op->args);
 		right_expr = lsecond(op->args);
@@ -2343,6 +2343,13 @@ process_implied_equality(PlannerInfo *root,
 	{
 		clause = (Expr *) eval_const_expressions(root, (Node *) clause);
 
+		/*
+		 * Store all non-internal cached expressions separatly for the
+		 * executor's purposes.
+		 */
+		set_non_internal_cachedexprs_walker((Node *) clause,
+											&(root->glob->cachedExprs));
+
 		/* If we produced const TRUE, just drop the clause */
 		if (clause && IsA(clause, Const))
 		{
@@ -2517,7 +2524,9 @@ match_foreign_keys_to_quals(PlannerInfo *root)
 				if (rinfo->outerjoin_delayed)
 					continue;
 
-				/* Only binary OpExprs are useful for consideration */
+				/*
+				 * Only binary non-cached OpExprs are useful for consideration.
+				 */
 				if (!IsA(clause, OpExpr) ||
 					list_length(clause->args) != 2)
 					continue;
@@ -2613,7 +2622,7 @@ check_mergejoinable(RestrictInfo *restrictinfo)
 
 	if (restrictinfo->pseudoconstant)
 		return;
-	if (!is_opclause(clause))
+	if (!is_opclause((Node *) clause, false))
 		return;
 	if (list_length(((OpExpr *) clause)->args) != 2)
 		return;
@@ -2650,7 +2659,7 @@ check_hashjoinable(RestrictInfo *restrictinfo)
 
 	if (restrictinfo->pseudoconstant)
 		return;
-	if (!is_opclause(clause))
+	if (!is_opclause((Node *) clause, false))
 		return;
 	if (list_length(((OpExpr *) clause)->args) != 2)
 		return;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 882a752..1268a67 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -276,6 +276,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	Plan	   *top_plan;
 	ListCell   *lp,
 			   *lr;
+	ListCell   *cell;
 
 	/*
 	 * Set up global state for this planner invocation.  This data is needed
@@ -506,6 +507,15 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 		lfirst(lp) = set_plan_references(subroot, subplan);
 	}
 
+	/* Add all non-internal cached expressions after all the PARAM_EXEC */
+	foreach(cell, glob->cachedExprs)
+	{
+		CachedExpr *cachedexpr = lfirst_node(CachedExpr, cell);
+
+		glob->paramExecTypes = lappend_oid(glob->paramExecTypes,
+										   exprType((const Node *) cachedexpr));
+	}
+
 	/* build the PlannedStmt result */
 	result = makeNode(PlannedStmt);
 
@@ -532,6 +542,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->utilityStmt = parse->utilityStmt;
 	result->stmt_location = parse->stmt_location;
 	result->stmt_len = parse->stmt_len;
+	result->cachedExprs = glob->cachedExprs;
 
 	result->jitFlags = PGJIT_NONE;
 	if (jit_enabled && jit_above_cost >= 0 &&
@@ -1075,6 +1086,12 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
 	if (kind == EXPRKIND_QUAL)
 		expr = (Node *) make_ands_implicit((Expr *) expr);
 
+	/*
+	 * Store all non-internal cached expressions separatly for the executor's
+	 * purposes.
+	 */
+	set_non_internal_cachedexprs_walker(expr, &(root->glob->cachedExprs));
+
 	return expr;
 }
 
@@ -5898,6 +5915,13 @@ expression_planner(Expr *expr)
 	 */
 	result->expr = (Expr *) eval_const_expressions(NULL, (Node *) expr);
 
+	/*
+	 * Store all non-internal cached expressions separatly for the executor's
+	 * purposes.
+	 */
+	set_non_internal_cachedexprs_walker((Node *) result->expr,
+										&(result->cachedExprs));
+
 	/* Fill in opfuncid values if missing */
 	fix_opfuncids((Node *) result->expr);
 
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 69dd327..00cbc08 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1438,19 +1438,31 @@ fix_expr_common(PlannerInfo *root, Node *node)
 				g->cols = cols;
 		}
 	}
+	else if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		fix_expr_common(root, (Node *) cachedexpr->subexpr);
+	}
 }
 
 /*
  * fix_param_node
- *		Do set_plan_references processing on a Param
+ *		Do set_plan_references processing on a (possibly cached) Param
  *
  * If it's a PARAM_MULTIEXPR, replace it with the appropriate Param from
  * root->multiexpr_params; otherwise no change is needed.
  * Just for paranoia's sake, we make a copy of the node in either case.
  */
 static Node *
-fix_param_node(PlannerInfo *root, Param *p)
+fix_param_node(PlannerInfo *root, Node *node)
 {
+	Param	   *p = castNodeIfCached(Param, node);
+
+	/*
+	 * Do not worry about the cached expression because PARAM_MULTIEXPR cannot
+	 * be cached.
+	 */
 	if (p->paramkind == PARAM_MULTIEXPR)
 	{
 		int			subqueryid = p->paramid >> 16;
@@ -1465,7 +1477,8 @@ fix_param_node(PlannerInfo *root, Param *p)
 			elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid);
 		return copyObject(list_nth(params, colno - 1));
 	}
-	return (Node *) copyObject(p);
+
+	return (Node *) copyObject(node);
 }
 
 /*
@@ -1533,8 +1546,8 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
 			var->varnoold += context->rtoffset;
 		return (Node *) var;
 	}
-	if (IsA(node, Param))
-		return fix_param_node(context->root, (Param *) node);
+	if (IsAIfCached(node, Param))
+		return fix_param_node(context->root, node);
 	if (IsA(node, Aggref))
 	{
 		Aggref	   *aggref = (Aggref *) node;
@@ -2324,8 +2337,8 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
 		/* If not supplied by input plans, evaluate the contained expr */
 		return fix_join_expr_mutator((Node *) phv->phexpr, context);
 	}
-	if (IsA(node, Param))
-		return fix_param_node(context->root, (Param *) node);
+	if (IsAIfCached(node, Param))
+		return fix_param_node(context->root, node);
 
 	/* Try matching more complex expressions too, if tlists have any */
 	converted_whole_row = is_converted_whole_row_reference(node);
@@ -2436,8 +2449,8 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
 		/* If not supplied by input plan, evaluate the contained expr */
 		return fix_upper_expr_mutator((Node *) phv->phexpr, context);
 	}
-	if (IsA(node, Param))
-		return fix_param_node(context->root, (Param *) node);
+	if (IsAIfCached(node, Param))
+		return fix_param_node(context->root, node);
 	if (IsA(node, Aggref))
 	{
 		Aggref	   *aggref = (Aggref *) node;
@@ -2681,6 +2694,7 @@ is_converted_whole_row_reference(Node *node)
 {
 	ConvertRowtypeExpr *convexpr;
 
+	/* Ignore cached ConvertRowtypeExprs because they do not contain vars. */
 	if (!node || !IsA(node, ConvertRowtypeExpr))
 		return false;
 
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 83008d7..4cb1a54 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -994,6 +994,10 @@ convert_testexpr_mutator(Node *node,
 {
 	if (node == NULL)
 		return NULL;
+
+	/*
+	 * Do not worry about cached params because PARAM_SUBLINK cannot be cached.
+	 */
 	if (IsA(node, Param))
 	{
 		Param	   *param = (Param *) node;
@@ -1068,6 +1072,9 @@ subplan_is_hashable(Plan *plan)
 static bool
 testexpr_is_hashable(Node *testexpr)
 {
+	/* This is not used for pseudoconstants. */
+	Assert(!IsA(testexpr, CachedExpr));
+
 	/*
 	 * The testexpr must be a single OpExpr, or an AND-clause containing only
 	 * OpExprs.
@@ -1084,7 +1091,7 @@ testexpr_is_hashable(Node *testexpr)
 		if (hash_ok_operator((OpExpr *) testexpr))
 			return true;
 	}
-	else if (and_clause(testexpr))
+	else if (and_clause(testexpr, false))
 	{
 		ListCell   *l;
 
@@ -1092,6 +1099,9 @@ testexpr_is_hashable(Node *testexpr)
 		{
 			Node	   *andarg = (Node *) lfirst(l);
 
+			/* This is not used for pseudoconstants. */
+			Assert(!IsA(andarg, CachedExpr));
+
 			if (!IsA(andarg, OpExpr))
 				return false;
 			if (!hash_ok_operator((OpExpr *) andarg))
@@ -1628,7 +1638,16 @@ simplify_EXISTS_query(PlannerInfo *root, Query *query)
 		query->limitCount = node;
 
 		if (!IsA(node, Const))
+		{
+			/*
+			 * Store all non-internal cached expressions separatly for the
+			 * executor's purposes.
+			 */
+			set_non_internal_cachedexprs_walker(node,
+												&(root->glob->cachedExprs));
+
 			return false;
+		}
 
 		limit = (Const *) node;
 		Assert(limit->consttype == INT8OID);
@@ -1755,6 +1774,7 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
 	{
 		OpExpr	   *expr = (OpExpr *) lfirst(lc);
 
+		/* Ignore cached expressions because they do not contain vars */
 		if (IsA(expr, OpExpr) &&
 			hash_ok_operator(expr))
 		{
@@ -1842,8 +1862,17 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
 	 * Put back any child-level-only WHERE clauses.
 	 */
 	if (newWhere)
+	{
 		subselect->jointree->quals = (Node *) make_ands_explicit(newWhere);
 
+		/*
+		 * Store all non-internal cached expressions separatly for the
+		 * executor's purposes.
+		 */
+		set_non_internal_cachedexprs_walker(subselect->jointree->quals,
+											&(root->glob->cachedExprs));
+	}
+
 	/*
 	 * Build a new targetlist for the child that emits the expressions we
 	 * need.  Concurrently, build a testexpr for the parent using Params to
@@ -1884,6 +1913,15 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect,
 	*testexpr = (Node *) make_ands_explicit(testlist);
 	*paramIds = paramids;
 
+	/*
+	 * Store all non-internal cached expressions separatly for the executor's
+	 * purposes.
+	 */
+	set_non_internal_cachedexprs_walker((Node *) subselect->targetList,
+										&(root->glob->cachedExprs));
+	set_non_internal_cachedexprs_walker(*testexpr,
+										&(root->glob->cachedExprs));
+
 	return subselect;
 }
 
@@ -1956,6 +1994,12 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root)
  * The isQual argument tells whether or not this expression is a WHERE/HAVING
  * qualifier expression.  If it is, any sublinks appearing at top level need
  * not distinguish FALSE from UNKNOWN return values.
+ *
+ * NOTE: if the expression contains cached expressions, it should be processed
+ * using set_non_internal_cachedexprs_walker to store all non-internal cached
+ * expressions separatly for the executor's purposes. Otherwise all the created
+ * cached expressions are considered internal and will not be cached by
+ * themselves.
  */
 Node *
 SS_process_sublinks(PlannerInfo *root, Node *expr, bool isQual)
@@ -2039,46 +2083,72 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
 	 * propagates down in both cases.  (Note that this is unlike the meaning
 	 * of "top level qual" used in most other places in Postgres.)
 	 */
-	if (and_clause(node))
+	if (and_clause(node, true))
 	{
 		List	   *newargs = NIL;
 		ListCell   *l;
+		Node	   *result;
 
 		/* Still at qual top-level */
 		locContext.isTopQual = context->isTopQual;
 
-		foreach(l, ((BoolExpr *) node)->args)
+		foreach(l, castNodeIfCached(BoolExpr, node)->args)
 		{
 			Node	   *newarg;
 
 			newarg = process_sublinks_mutator(lfirst(l), &locContext);
-			if (and_clause(newarg))
-				newargs = list_concat(newargs, ((BoolExpr *) newarg)->args);
+			if (and_clause(newarg, true))
+			{
+				newargs = list_concat(newargs,
+									  castNodeIfCached(BoolExpr, newarg)->args);
+			}
 			else
+			{
 				newargs = lappend(newargs, newarg);
+			}
 		}
-		return (Node *) make_andclause(newargs);
+
+		result = (Node *) make_andclause(newargs, false);
+
+		/* Cache it if the original node was cached */
+		if (IsA(node, CachedExpr))
+			result = (Node *) makeCachedExpr((CacheableExpr *) result);
+
+		return result;
 	}
 
-	if (or_clause(node))
+	if (or_clause(node, true))
 	{
 		List	   *newargs = NIL;
 		ListCell   *l;
+		Node	   *result;
 
 		/* Still at qual top-level */
 		locContext.isTopQual = context->isTopQual;
 
-		foreach(l, ((BoolExpr *) node)->args)
+		foreach(l, castNodeIfCached(BoolExpr, node)->args)
 		{
 			Node	   *newarg;
 
 			newarg = process_sublinks_mutator(lfirst(l), &locContext);
-			if (or_clause(newarg))
-				newargs = list_concat(newargs, ((BoolExpr *) newarg)->args);
+			if (or_clause(newarg, true))
+			{
+				newargs = list_concat(newargs,
+									  castNodeIfCached(BoolExpr, newarg)->args);
+			}
 			else
+			{
 				newargs = lappend(newargs, newarg);
+			}
 		}
-		return (Node *) make_orclause(newargs);
+
+		result = (Node *) make_orclause(newargs, false);
+
+		/* Cache it if the original node was cached */
+		if (IsA(node, CachedExpr))
+			result = (Node *) makeCachedExpr((CacheableExpr *) result);
+
+		return result;
 	}
 
 	/*
@@ -2891,6 +2961,10 @@ finalize_primnode(Node *node, finalize_primnode_context *context)
 {
 	if (node == NULL)
 		return false;
+
+	/*
+	 * Do not worry about cached params because PARAM_EXEC cannot be cached.
+	 */
 	if (IsA(node, Param))
 	{
 		if (((Param *) node)->paramkind == PARAM_EXEC)
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index c3f46a2..79aed64 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -334,6 +334,13 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
 {
 	if (node == NULL)
 		return NULL;
+
+	/*
+	 * It is assumed that we have not yet used the function
+	 * eval_const_expressions.
+	 */
+	Assert(!IsA(node, CachedExpr));
+
 	if (IsA(node, SubLink))
 	{
 		SubLink    *sublink = (SubLink *) node;
@@ -452,7 +459,7 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
 		/* Else return it unmodified */
 		return node;
 	}
-	if (not_clause(node))
+	if (not_clause(node, false))
 	{
 		/* If the immediate argument of NOT is EXISTS, try to convert */
 		SubLink    *sublink = (SubLink *) get_notclausearg((Expr *) node);
@@ -519,7 +526,7 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
 		/* Else return it unmodified */
 		return node;
 	}
-	if (and_clause(node))
+	if (and_clause(node, false))
 	{
 		/* Recurse into AND clause */
 		List	   *newclauses = NIL;
@@ -545,7 +552,7 @@ pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
 		else if (list_length(newclauses) == 1)
 			return (Node *) linitial(newclauses);
 		else
-			return (Node *) make_andclause(newclauses);
+			return (Node *) make_andclause(newclauses, false);
 	}
 	/* Stop if not an AND */
 	return node;
diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c
index 52f8893..51e62a0 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -68,6 +68,12 @@ static Expr *process_duplicate_ors(List *orlist);
  * the motivations for doing this is to ensure that logically equivalent
  * expressions will be seen as physically equal(), so we should always apply
  * the same transformations.
+ *
+ * NOTE: the expression should be processed using
+ * set_non_internal_cachedexprs_walker to store all non-internal cached
+ * expressions separatly for the executor's purposes. Otherwise all the created
+ * cached expressions are considered internal and will not be cached by
+ * themselves.
  */
 Node *
 negate_clause(Node *node)
@@ -165,7 +171,7 @@ negate_clause(Node *node)
 								nargs = lappend(nargs,
 												negate_clause(lfirst(lc)));
 							}
-							return (Node *) make_orclause(nargs);
+							return (Node *) make_orclause(nargs, false);
 						}
 						break;
 					case OR_EXPR:
@@ -178,7 +184,7 @@ negate_clause(Node *node)
 								nargs = lappend(nargs,
 												negate_clause(lfirst(lc)));
 							}
-							return (Node *) make_andclause(nargs);
+							return (Node *) make_andclause(nargs, false);
 						}
 						break;
 					case NOT_EXPR:
@@ -252,6 +258,36 @@ negate_clause(Node *node)
 				return (Node *) newexpr;
 			}
 			break;
+		case T_CachedExpr:
+			{
+				CachedExpr *expr = (CachedExpr *) node;
+				CacheableExpr *subexpr = expr->subexpr;
+				/* Try to simplify its subexpression */
+				Node	   *newsubnode = negate_clause((Node *) subexpr);
+
+				if (IsA(newsubnode, BoolExpr) &&
+					((BoolExpr *) newsubnode)->boolop == NOT_EXPR)
+				{
+					/*
+					 * Simplifying its subexpression did not help so return the
+					 * cached negation of the original node. Make a new
+					 * CachedExpr node for the original subexpression since it
+					 * may used elsewhere else as a non-internal cached
+					 * expression.
+					 */
+					return (Node *) makeCachedExpr((CacheableExpr *)
+						make_notclause((Expr *) makeCachedExpr(subexpr)));
+				}
+				else
+				{
+					/*
+					 * A simplified subexpression may be non-cacheable, so
+					 * return it by itself.
+					 */
+					return newsubnode;
+				}
+			}
+			break;
 		default:
 			/* else fall through */
 			break;
@@ -285,6 +321,12 @@ negate_clause(Node *node)
  * its own flattening logic, but that requires a useless extra pass over the
  * tree.
  *
+ * NOTE: the expression should be processed using
+ * set_non_internal_cachedexprs_walker to store all non-internal cached
+ * expressions separatly for the executor's purposes. Otherwise all the created
+ * cached expressions are considered internal and will not be cached by
+ * themselves.
+ *
  * Returns the modified qualification.
  */
 Expr *
@@ -333,11 +375,26 @@ pull_ands(List *andlist)
 		 * built a new arglist not shared with any other expr. Otherwise we'd
 		 * need a list_copy here.
 		 */
-		if (and_clause(subexpr))
-			out_list = list_concat(out_list,
-								   pull_ands(((BoolExpr *) subexpr)->args));
+		if (and_clause(subexpr, true))
+		{
+			out_list = list_concat(
+						out_list,
+						pull_ands(castNodeIfCached(BoolExpr, subexpr)->args));
+		}
 		else
+		{
+			/*
+			 * Make a new CachedExpr node since the source node can be used
+			 * elsewhere as an internal cached expression.
+			 */
+			if (IsA(subexpr, CachedExpr))
+			{
+				subexpr =
+					(Node *) makeCachedExpr(((CachedExpr *) subexpr)->subexpr);
+			}
+
 			out_list = lappend(out_list, subexpr);
+		}
 	}
 	return out_list;
 }
@@ -365,11 +422,26 @@ pull_ors(List *orlist)
 		 * built a new arglist not shared with any other expr. Otherwise we'd
 		 * need a list_copy here.
 		 */
-		if (or_clause(subexpr))
-			out_list = list_concat(out_list,
-								   pull_ors(((BoolExpr *) subexpr)->args));
+		if (or_clause(subexpr, true))
+		{
+			out_list = list_concat(
+						out_list,
+						pull_ors(castNodeIfCached(BoolExpr, subexpr)->args));
+		}
 		else
+		{
+			/*
+			 * Make a new CachedExpr node since the source node can be used
+			 * elsewhere as an internal cached expression.
+			 */
+			if (IsA(subexpr, CachedExpr))
+			{
+				subexpr =
+					(Node* ) makeCachedExpr(((CachedExpr *) subexpr)->subexpr);
+			}
+
 			out_list = lappend(out_list, subexpr);
+		}
 	}
 	return out_list;
 }
@@ -415,13 +487,13 @@ pull_ors(List *orlist)
 static Expr *
 find_duplicate_ors(Expr *qual, bool is_check)
 {
-	if (or_clause((Node *) qual))
+	if (or_clause((Node *) qual, true))
 	{
 		List	   *orlist = NIL;
 		ListCell   *temp;
 
 		/* Recurse */
-		foreach(temp, ((BoolExpr *) qual)->args)
+		foreach(temp, castNodeIfCached(BoolExpr, qual)->args)
 		{
 			Expr	   *arg = (Expr *) lfirst(temp);
 
@@ -459,13 +531,13 @@ find_duplicate_ors(Expr *qual, bool is_check)
 		/* Now we can look for duplicate ORs */
 		return process_duplicate_ors(orlist);
 	}
-	else if (and_clause((Node *) qual))
+	else if (and_clause((Node *) qual, true))
 	{
 		List	   *andlist = NIL;
 		ListCell   *temp;
 
 		/* Recurse */
-		foreach(temp, ((BoolExpr *) qual)->args)
+		foreach(temp, castNodeIfCached(BoolExpr, qual)->args)
 		{
 			Expr	   *arg = (Expr *) lfirst(temp);
 
@@ -509,7 +581,7 @@ find_duplicate_ors(Expr *qual, bool is_check)
 			return (Expr *) linitial(andlist);
 
 		/* Else we still need an AND node */
-		return make_andclause(andlist);
+		return make_andclause(andlist, true);
 	}
 	else
 		return qual;
@@ -550,9 +622,9 @@ process_duplicate_ors(List *orlist)
 	{
 		Expr	   *clause = (Expr *) lfirst(temp);
 
-		if (and_clause((Node *) clause))
+		if (and_clause((Node *) clause, true))
 		{
-			List	   *subclauses = ((BoolExpr *) clause)->args;
+			List	   *subclauses = castNodeIfCached(BoolExpr, clause)->args;
 			int			nclauses = list_length(subclauses);
 
 			if (reference == NIL || nclauses < num_subclauses)
@@ -588,9 +660,10 @@ process_duplicate_ors(List *orlist)
 		{
 			Expr	   *clause = (Expr *) lfirst(temp2);
 
-			if (and_clause((Node *) clause))
+			if (and_clause((Node *) clause, true))
 			{
-				if (!list_member(((BoolExpr *) clause)->args, refclause))
+				if (!list_member(castNodeIfCached(BoolExpr, clause)->args,
+								 refclause))
 				{
 					win = false;
 					break;
@@ -614,7 +687,7 @@ process_duplicate_ors(List *orlist)
 	 * If no winners, we can't transform the OR
 	 */
 	if (winners == NIL)
-		return make_orclause(orlist);
+		return make_orclause(orlist, true);
 
 	/*
 	 * Generate new OR list consisting of the remaining sub-clauses.
@@ -631,17 +704,22 @@ process_duplicate_ors(List *orlist)
 	{
 		Expr	   *clause = (Expr *) lfirst(temp);
 
-		if (and_clause((Node *) clause))
+		if (and_clause((Node *) clause, true))
 		{
-			List	   *subclauses = ((BoolExpr *) clause)->args;
+			List	   *subclauses = castNodeIfCached(BoolExpr, clause)->args;
 
 			subclauses = list_difference(subclauses, winners);
 			if (subclauses != NIL)
 			{
 				if (list_length(subclauses) == 1)
+				{
 					neworlist = lappend(neworlist, linitial(subclauses));
+				}
 				else
-					neworlist = lappend(neworlist, make_andclause(subclauses));
+				{
+					neworlist = lappend(neworlist,
+										make_andclause(subclauses, true));
+				}
 			}
 			else
 			{
@@ -670,9 +748,14 @@ process_duplicate_ors(List *orlist)
 	if (neworlist != NIL)
 	{
 		if (list_length(neworlist) == 1)
+		{
 			winners = lappend(winners, linitial(neworlist));
+		}
 		else
-			winners = lappend(winners, make_orclause(pull_ors(neworlist)));
+		{
+			winners = lappend(winners,
+							  make_orclause(pull_ors(neworlist), true));
+		}
 	}
 
 	/*
@@ -680,7 +763,20 @@ process_duplicate_ors(List *orlist)
 	 * element and AND/OR flatness.
 	 */
 	if (list_length(winners) == 1)
-		return (Expr *) linitial(winners);
+	{
+		Expr	   *result = linitial(winners);
+
+		/*
+		 * Make a new CachedExpr node since the source node can be used
+		 * elsewhere as an internal cached expression.
+		 */
+		if (IsA(result, CachedExpr))
+			result = (Expr *) makeCachedExpr(((CachedExpr *) result)->subexpr);
+
+		return result;
+	}
 	else
-		return make_andclause(pull_ands(winners));
+	{
+		return make_andclause(pull_ands(winners), true);
+	}
 }
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 0ab4014..c1625e0 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -2261,6 +2261,11 @@ adjust_appendrel_attrs_mutator(Node *node,
 											  context->appinfos);
 		return (Node *) phv;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return copyObject(node);
+	}
 	/* Shouldn't need to handle planner auxiliary nodes here */
 	Assert(!IsA(node, SpecialJoinInfo));
 	Assert(!IsA(node, AppendRelInfo));
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 17bcdba..883a31a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -96,6 +96,46 @@ typedef struct
 	List	   *safe_param_ids; /* PARAM_EXEC Param IDs to treat as safe */
 } max_parallel_hazard_context;
 
+/*
+ * A detailed result of checking that the subnodes/functions of the node are
+ * safe for evaluation or caching. This is used in the functions
+ * ece_all_arguments_const (which checks the subnodes) and
+ * ece_functions_are_safe (which checks the node functions).
+ *
+ * When investigating subnodes:
+ * - SAFE_FOR_EVALUATION indicates that all the children of this node are
+ * Consts.
+ * - SAFE_FOR_CACHING_ONLY indicates that all the children of this node are
+ * Consts or CachedExprs.
+ * - SAFE_FOR_NOTHING indicates that this node has a non-Const and
+ * non-CachedExpr child node.
+ *
+ * When investigating node functions:
+ * - SAFE_FOR_EVALUATION that the node contains only immutable functions (or
+ * also stable functions in the case of estimation).
+ * - SAFE_FOR_CACHING_ONLY indicates the node contains only immutable or stable
+ * functions (so in the case of estimation there's no difference between
+ * SAFE_FOR_EVALUATION and SAFE_FOR_CACHING_ONLY, and the returned value is
+ * always SAFE_FOR_EVALUATION because it is more strict in general).
+ * - SAFE_FOR_NOTHING indicates that the node contains a volatile function.
+ */
+typedef enum ece_check_node_safety_detailed
+{
+	SAFE_FOR_EVALUATION,
+	SAFE_FOR_CACHING_ONLY,
+	SAFE_FOR_NOTHING,
+} ece_check_node_safety_detailed;
+
+#define SAFE_FOR_CACHING(detailed) \
+	((detailed) == SAFE_FOR_EVALUATION || (detailed) == SAFE_FOR_CACHING_ONLY)
+
+typedef struct
+{
+	ece_check_node_safety_detailed detailed;	/* detailed result */
+	bool		recurse;		/* we should also check the subnodes? */
+	bool		estimate;		/* are stable functions safe for evaluation? */
+} ece_functions_are_safe_context;
+
 static bool contain_agg_clause_walker(Node *node, void *context);
 static bool get_agg_clause_costs_walker(Node *node,
 							get_agg_clause_costs_context *context);
@@ -113,23 +153,31 @@ static bool contain_leaked_vars_walker(Node *node, void *context);
 static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
 static List *find_nonnullable_vars_walker(Node *node, bool top_level);
 static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
+static ece_check_node_safety_detailed ece_all_arguments_const(Node *node);
 static Node *eval_const_expressions_mutator(Node *node,
 							   eval_const_expressions_context *context);
-static bool contain_non_const_walker(Node *node, void *context);
-static bool ece_function_is_safe(Oid funcid,
-					 eval_const_expressions_context *context);
+static bool contain_non_const_walker(Node *node,
+						 ece_check_node_safety_detailed *detailed);
+static ece_check_node_safety_detailed ece_functions_are_safe(Node *node,
+					   bool recurse, bool estimate);
+static bool ece_functions_are_safe_walker(Node *node,
+							  ece_functions_are_safe_context *context);
 static List *simplify_or_arguments(List *args,
 					  eval_const_expressions_context *context,
-					  bool *haveNull, bool *forceTrue);
+					  bool *haveNull, bool *forceTrue,
+					  bool *all_consts_or_cached);
 static List *simplify_and_arguments(List *args,
 					   eval_const_expressions_context *context,
-					   bool *haveNull, bool *forceFalse);
+					   bool *haveNull, bool *forceFalse,
+					   bool *all_consts_or_cached);
 static Node *simplify_boolean_equality(Oid opno, List *args);
 static Expr *simplify_function(Oid funcid,
 				  Oid result_type, int32 result_typmod,
 				  Oid result_collid, Oid input_collid, List **args_p,
-				  bool funcvariadic, bool process_args, bool allow_non_const,
-				  eval_const_expressions_context *context);
+				  bool funcvariadic, bool process_args,
+				  bool allow_only_consts_and_simple_caching,
+				  eval_const_expressions_context *context,
+				  CoercionForm funcformat, Oid opno, int location);
 static List *reorder_function_arguments(List *args, HeapTuple func_tuple);
 static List *add_function_defaults(List *args, HeapTuple func_tuple);
 static List *fetch_function_defaults(HeapTuple func_tuple);
@@ -157,6 +205,12 @@ static Query *substitute_actual_srf_parameters(Query *expr,
 static Node *substitute_actual_srf_parameters_mutator(Node *node,
 										 substitute_actual_srf_parameters_context *context);
 static bool tlist_matches_coltypelist(List *tlist, List *coltypelist);
+static bool is_const_or_cached(const Node *node);
+static Expr *cache_function(Oid funcid, Oid result_type, Oid result_collid,
+			   Oid input_collid,
+			   List *args, bool funcvariadic, HeapTuple func_tuple,
+			   eval_const_expressions_context *context, CoercionForm funcformat,
+			   Oid opno, int location);
 
 
 /*****************************************************************************
@@ -201,6 +255,9 @@ get_leftop(const Expr *clause)
 {
 	const OpExpr *expr = (const OpExpr *) clause;
 
+	if (IsA(expr, CachedExpr))
+		expr = (const OpExpr *) ((const CachedExpr *) expr)->subexpr;
+
 	if (expr->args != NIL)
 		return linitial(expr->args);
 	else
@@ -218,6 +275,9 @@ get_rightop(const Expr *clause)
 {
 	const OpExpr *expr = (const OpExpr *) clause;
 
+	if (IsA(expr, CachedExpr))
+		expr = (const OpExpr *) ((const CachedExpr *) expr)->subexpr;
+
 	if (list_length(expr->args) >= 2)
 		return lsecond(expr->args);
 	else
@@ -232,10 +292,14 @@ get_rightop(const Expr *clause)
  * not_clause
  *
  * Returns t iff this is a 'not' clause: (NOT expr).
+ * Check the subexpression of the cached expression if check_cachedexpr is true.
  */
 bool
-not_clause(Node *clause)
+not_clause(Node *clause, bool check_cachedexpr)
 {
+	if (check_cachedexpr && IsA(clause, CachedExpr))
+		clause = (Node *) ((CachedExpr *) clause)->subexpr;
+
 	return (clause != NULL &&
 			IsA(clause, BoolExpr) &&
 			((BoolExpr *) clause)->boolop == NOT_EXPR);
@@ -265,7 +329,7 @@ make_notclause(Expr *notclause)
 Expr *
 get_notclausearg(Expr *notclause)
 {
-	return linitial(((BoolExpr *) notclause)->args);
+	return linitial(castNodeIfCached(BoolExpr, notclause)->args);
 }
 
 /*****************************************************************************
@@ -276,10 +340,14 @@ get_notclausearg(Expr *notclause)
  * or_clause
  *
  * Returns t iff the clause is an 'or' clause: (OR { expr }).
+ * Check the subexpression of the cached expression if check_cachedexpr is true.
  */
 bool
-or_clause(Node *clause)
+or_clause(Node *clause, bool check_cachedexpr)
 {
+	if (check_cachedexpr && IsA(clause, CachedExpr))
+		clause = (Node *) ((CachedExpr *) clause)->subexpr;
+
 	return (clause != NULL &&
 			IsA(clause, BoolExpr) &&
 			((BoolExpr *) clause)->boolop == OR_EXPR);
@@ -289,15 +357,34 @@ or_clause(Node *clause)
  * make_orclause
  *
  * Creates an 'or' clause given a list of its subclauses.
+ *
+ * NOTE: if try_to_cache is true and the subclauses contain cached expressions,
+ * the returned expression should be processed using
+ * set_non_internal_cachedexprs_walker to store all non-internal cached
+ * expressions separatly for the executor's purposes. Otherwise the created
+ * cached expression is considered internal and will not be cached by itself.
  */
 Expr *
-make_orclause(List *orclauses)
+make_orclause(List *orclauses, bool try_to_cache)
 {
 	BoolExpr   *expr = makeNode(BoolExpr);
 
 	expr->boolop = OR_EXPR;
 	expr->args = orclauses;
 	expr->location = -1;
+
+	if (try_to_cache)
+	{
+		bool		all_consts_or_cached = true;
+		ListCell   *cell;
+
+		foreach(cell, expr->args)
+			all_consts_or_cached &= is_const_or_cached((Node *) lfirst(cell));
+
+		if (all_consts_or_cached)
+			return (Expr *) makeCachedExpr((CacheableExpr *) expr);
+	}
+
 	return (Expr *) expr;
 }
 
@@ -310,10 +397,14 @@ make_orclause(List *orclauses)
  * and_clause
  *
  * Returns t iff its argument is an 'and' clause: (AND { expr }).
+ * Check the subexpression of the cached expression if check_cachedexpr is true.
  */
 bool
-and_clause(Node *clause)
+and_clause(Node *clause, bool check_cachedexpr)
 {
+	if (check_cachedexpr && IsA(clause, CachedExpr))
+		clause = (Node *) ((CachedExpr *) clause)->subexpr;
+
 	return (clause != NULL &&
 			IsA(clause, BoolExpr) &&
 			((BoolExpr *) clause)->boolop == AND_EXPR);
@@ -323,15 +414,34 @@ and_clause(Node *clause)
  * make_andclause
  *
  * Creates an 'and' clause given a list of its subclauses.
+ *
+ * NOTE: if try_to_cache is true and the subclauses contain cached expressions,
+ * the returned expression should be processed using
+ * set_non_internal_cachedexprs_walker to store all non-internal cached
+ * expressions separatly for the executor's purposes. Otherwise the created
+ * cached expression is considered internal and will not be cached by itself.
  */
 Expr *
-make_andclause(List *andclauses)
+make_andclause(List *andclauses, bool try_to_cache)
 {
 	BoolExpr   *expr = makeNode(BoolExpr);
 
 	expr->boolop = AND_EXPR;
 	expr->args = andclauses;
 	expr->location = -1;
+
+	if (try_to_cache)
+	{
+		bool		all_consts_or_cached = true;
+		ListCell   *cell;
+
+		foreach(cell, expr->args)
+			all_consts_or_cached &= is_const_or_cached((Node *) lfirst(cell));
+
+		if (all_consts_or_cached)
+			return (Expr *) makeCachedExpr((CacheableExpr *) expr);
+	}
+
 	return (Expr *) expr;
 }
 
@@ -352,7 +462,7 @@ make_and_qual(Node *qual1, Node *qual2)
 		return qual2;
 	if (qual2 == NULL)
 		return qual1;
-	return (Node *) make_andclause(list_make2(qual1, qual2));
+	return (Node *) make_andclause(list_make2(qual1, qual2), false);
 }
 
 /*
@@ -372,9 +482,16 @@ make_ands_explicit(List *andclauses)
 	else if (list_length(andclauses) == 1)
 		return (Expr *) linitial(andclauses);
 	else
-		return make_andclause(andclauses);
+		return make_andclause(andclauses, false);
 }
 
+/*
+ * NOTE: if the input expression contains cached expressions, the returned
+ * expression should be processed using set_non_internal_cachedexprs_walker to
+ * store all non-internal cached expressions separatly for the executor's
+ * purposes. Otherwise all the created cached expressions are considered
+ * internal and will not be cached by themselves.
+ */
 List *
 make_ands_implicit(Expr *clause)
 {
@@ -386,8 +503,41 @@ make_ands_implicit(Expr *clause)
 	 */
 	if (clause == NULL)
 		return NIL;				/* NULL -> NIL list == TRUE */
-	else if (and_clause((Node *) clause))
-		return ((BoolExpr *) clause)->args;
+	else if (and_clause((Node *) clause, true))
+	{
+		List	   *args_list = castNodeIfCached(BoolExpr, clause)->args;
+
+		if (IsA(clause, CachedExpr))
+		{
+			/*
+			 * Make new CachedExpr nodes for the cached elements of the list
+			 * since the source nodes can be used elsewhere as internal cached
+			 * expressions.
+			 */
+			List	   *result = NIL;
+			ListCell   *cell;
+
+			foreach(cell, args_list)
+			{
+				Node	   *node = lfirst(cell);
+
+				if (IsA(node, CachedExpr))
+				{
+					result = lappend(
+								result,
+								makeCachedExpr(((CachedExpr *) node)->subexpr));
+				}
+				else
+				{
+					result = lappend(result, node);
+				}
+			}
+
+			return result;
+		}
+
+		return args_list;
+	}
 	else if (IsA(clause, Const) &&
 			 !((Const *) clause)->constisnull &&
 			 DatumGetBool(((Const *) clause)->constvalue))
@@ -435,6 +585,11 @@ contain_agg_clause_walker(Node *node, void *context)
 		Assert(((GroupingFunc *) node)->agglevelsup == 0);
 		return true;			/* abort the tree traversal and return true */
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* aggregation clauses are not cacheable */
+		return false;
+	}
 	Assert(!IsA(node, SubLink));
 	return expression_tree_walker(node, contain_agg_clause_walker, context);
 }
@@ -706,6 +861,11 @@ get_agg_clause_costs_walker(Node *node, get_agg_clause_costs_context *context)
 		 */
 		return false;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* aggregation clauses are not cacheable */
+		return false;
+	}
 	Assert(!IsA(node, SubLink));
 	return expression_tree_walker(node, get_agg_clause_costs_walker,
 								  (void *) context);
@@ -778,6 +938,11 @@ find_window_functions_walker(Node *node, WindowFuncLists *lists)
 		 */
 		return false;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* window functions are not cacheable */
+		return false;
+	}
 	Assert(!IsA(node, SubLink));
 	return expression_tree_walker(node, find_window_functions_walker,
 								  (void *) lists);
@@ -821,6 +986,12 @@ expression_returns_set_rows(Node *clause)
 			return clamp_row_est(get_func_rows(expr->opfuncid));
 		}
 	}
+	if (IsA(clause, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) clause;
+
+		return expression_returns_set_rows((Node *) cachedexpr->subexpr);
+	}
 	return 1.0;
 }
 
@@ -855,6 +1026,8 @@ contain_subplans_walker(Node *node, void *context)
 		IsA(node, AlternativeSubPlan) ||
 		IsA(node, SubLink))
 		return true;			/* abort the tree traversal and return true */
+	if (IsA(node, CachedExpr))
+		return false;			/* subplans are not cacheable */
 	return expression_tree_walker(node, contain_subplans_walker, context);
 }
 
@@ -981,6 +1154,11 @@ contain_volatile_functions_walker(Node *node, void *context)
 		/* NextValueExpr is volatile */
 		return true;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* CachedExpr is not volatile */
+		return false;
+	}
 
 	/*
 	 * See notes in contain_mutable_functions_walker about why we treat
@@ -1028,6 +1206,12 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
 								context))
 		return true;
 
+	if (IsA(node, CachedExpr))
+	{
+		/* CachedExpr is not volatile */
+		return false;
+	}
+
 	/*
 	 * See notes in contain_mutable_functions_walker about why we treat
 	 * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable, while
@@ -1288,6 +1472,14 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
 								 context, 0);
 	}
 
+	else if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		return max_parallel_hazard_walker((Node *) cachedexpr->subexpr,
+										  context);
+	}
+
 	/* Recurse to check arguments */
 	return expression_tree_walker(node,
 								  max_parallel_hazard_walker,
@@ -1418,6 +1610,14 @@ contain_nonstrict_functions_walker(Node *node, void *context)
 	if (IsA(node, BooleanTest))
 		return true;
 
+	if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		return contain_nonstrict_functions_walker((Node *) cachedexpr->subexpr,
+												  context);
+	}
+
 	/* Check other function-containing nodes */
 	if (check_functions_in_node(node, contain_nonstrict_functions_checker,
 								context))
@@ -1493,6 +1693,14 @@ contain_context_dependent_node_walker(Node *node, int *flags)
 			return res;
 		}
 	}
+	if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		return contain_context_dependent_node_walker(
+												(Node *) cachedexpr->subexpr,
+												flags);
+	}
 	return expression_tree_walker(node, contain_context_dependent_node_walker,
 								  (void *) flags);
 }
@@ -1613,6 +1821,12 @@ contain_leaked_vars_walker(Node *node, void *context)
 			 */
 			return false;
 
+		case T_CachedExpr:
+
+			return contain_leaked_vars_walker(
+										(Node *) ((CachedExpr *) node)->subexpr,
+										context);
+
 		default:
 
 			/*
@@ -2195,9 +2409,9 @@ is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK)
 		if (nitems > 0)
 			return true;
 	}
-	else if (rightop && IsA(rightop, ArrayExpr))
+	else if (rightop && IsAIfCached(rightop, ArrayExpr))
 	{
-		ArrayExpr  *arrayexpr = (ArrayExpr *) rightop;
+		ArrayExpr  *arrayexpr = castNodeIfCached(ArrayExpr, rightop);
 
 		if (arrayexpr->elements != NIL && !arrayexpr->multidims)
 			return true;
@@ -2291,7 +2505,7 @@ CommuteOpExpr(OpExpr *clause)
 	Node	   *temp;
 
 	/* Sanity checks: caller is at fault if these fail */
-	if (!is_opclause(clause) ||
+	if (!is_opclause((Node *) clause, false) ||
 		list_length(clause->args) != 2)
 		elog(ERROR, "cannot commute non-binary-operator clause");
 
@@ -2454,6 +2668,12 @@ rowtype_field_matches(Oid rowtypeid, int fieldnum,
  * NOTE: another critical effect is that any function calls that require
  * default arguments will be expanded, and named-argument calls will be
  * converted to positional notation.  The executor won't handle either.
+ *
+ * NOTE: the expression should be processed using
+ * set_non_internal_cachedexprs_walker to store all non-internal cached
+ * expressions separatly for the executor's purposes. Otherwise all the created
+ * cached expressions are considered internal and will not be cached by
+ * themselves.
  *--------------------
  */
 Node *
@@ -2487,6 +2707,9 @@ eval_const_expressions(PlannerInfo *root, Node *node)
  *	  value of the Param.
  * 2. Fold stable, as well as immutable, functions to constants.
  * 3. Reduce PlaceHolderVar nodes to their contained expressions.
+ *
+ * Unlike eval_const_expressions, the cached expressions are never used for
+ * estimation.
  *--------------------
  */
 Node *
@@ -2506,11 +2729,13 @@ estimate_expression_value(PlannerInfo *root, Node *node)
 /*
  * The generic case in eval_const_expressions_mutator is to recurse using
  * expression_tree_mutator, which will copy the given node unchanged but
- * const-simplify its arguments (if any) as far as possible.  If the node
+ * simplify its arguments (if any) as far as possible.  If the node
  * itself does immutable processing, and each of its arguments were reduced
  * to a Const, we can then reduce it to a Const using evaluate_expr.  (Some
  * node types need more complicated logic; for example, a CASE expression
- * might be reducible to a constant even if not all its subtrees are.)
+ * might be reducible to a constant even if not all its subtrees are.) Also if
+ * the note itself does not volatile processing, and each of its arguments were
+ * reduced to Const or CachedExpr nodes, we can then reduce it to a CachedExpr.
  */
 #define ece_generic_processing(node) \
 	expression_tree_mutator((Node *) (node), eval_const_expressions_mutator, \
@@ -2521,8 +2746,13 @@ estimate_expression_value(PlannerInfo *root, Node *node)
  * By going directly to expression_tree_walker, contain_non_const_walker
  * is not applied to the node itself, only to its children.
  */
-#define ece_all_arguments_const(node) \
-	(!expression_tree_walker((Node *) (node), contain_non_const_walker, NULL))
+static ece_check_node_safety_detailed
+ece_all_arguments_const(Node *node)
+{
+	ece_check_node_safety_detailed detailed = SAFE_FOR_EVALUATION;
+	expression_tree_walker(node, contain_non_const_walker, (void *) &detailed);
+	return detailed;
+}
 
 /* Generic macro for applying evaluate_expr */
 #define ece_evaluate_expr(node) \
@@ -2679,8 +2909,11 @@ eval_const_expressions_mutator(Node *node,
 										   &args,
 										   expr->funcvariadic,
 										   true,
-										   true,
-										   context);
+										   false,
+										   context,
+										   expr->funcformat,
+										   InvalidOid,
+										   expr->location);
 				if (simple)		/* successfully simplified it */
 					return (Node *) simple;
 
@@ -2726,26 +2959,15 @@ eval_const_expressions_mutator(Node *node,
 										   &args,
 										   false,
 										   true,
-										   true,
-										   context);
+										   false,
+										   context,
+										   COERCE_EXPLICIT_CALL,
+										   expr->opno,
+										   expr->location);
 				if (simple)		/* successfully simplified it */
 					return (Node *) simple;
 
 				/*
-				 * If the operator is boolean equality or inequality, we know
-				 * how to simplify cases involving one constant and one
-				 * non-constant argument.
-				 */
-				if (expr->opno == BooleanEqualOperator ||
-					expr->opno == BooleanNotEqualOperator)
-				{
-					simple = (Expr *) simplify_boolean_equality(expr->opno,
-																args);
-					if (simple) /* successfully simplified it */
-						return (Node *) simple;
-				}
-
-				/*
 				 * The expression cannot be simplified any further, so build
 				 * and return a replacement OpExpr node using the
 				 * possibly-simplified arguments.
@@ -2808,31 +3030,38 @@ eval_const_expressions_mutator(Node *node,
 					/* one null? then distinct */
 					if (has_null_input)
 						return makeBoolConst(true, false);
+				}
 
-					/* otherwise try to evaluate the '=' operator */
-					/* (NOT okay to try to inline it, though!) */
+				/* otherwise try to evaluate the '=' operator */
+				/* (NOT okay to try to inline it, though!) */
 
-					/*
-					 * Need to get OID of underlying function.  Okay to
-					 * scribble on input to this extent.
-					 */
-					set_opfuncid((OpExpr *) expr);	/* rely on struct
-													 * equivalence */
+				/*
+				 * Need to get OID of underlying function.  Okay to
+				 * scribble on input to this extent.
+				 */
+				set_opfuncid((OpExpr *) expr);	/* rely on struct
+												 * equivalence */
 
-					/*
-					 * Code for op/func reduction is pretty bulky, so split it
-					 * out as a separate function.
-					 */
-					simple = simplify_function(expr->opfuncid,
-											   expr->opresulttype, -1,
-											   expr->opcollid,
-											   expr->inputcollid,
-											   &args,
-											   false,
-											   false,
-											   false,
-											   context);
-					if (simple) /* successfully simplified it */
+				/*
+				 * Code for op/func reduction is pretty bulky, so split it
+				 * out as a separate function.
+				 */
+				simple = simplify_function(expr->opfuncid,
+										   expr->opresulttype, -1,
+										   expr->opcollid,
+										   expr->inputcollid,
+										   &args,
+										   false,
+										   false,
+										   true,
+										   context,
+										   COERCE_EXPLICIT_CALL,
+										   expr->opno,
+										   expr->location);
+
+				if (simple) /* successfully simplified it */
+				{
+					if (IsA(simple, Const))
 					{
 						/*
 						 * Since the underlying operator is "=", must negate
@@ -2844,6 +3073,29 @@ eval_const_expressions_mutator(Node *node,
 							BoolGetDatum(!DatumGetBool(csimple->constvalue));
 						return (Node *) csimple;
 					}
+					else if (IsA(simple, CachedExpr))
+					{
+						/*
+						 * Cache DistinctExpr using information from its cached
+						 * operator.
+						 */
+						CachedExpr *csimple = castNode(CachedExpr, simple);
+						OpExpr	   *subexpr = castNode(OpExpr,
+													   csimple->subexpr);
+						DistinctExpr *newsubexpr = makeNode(DistinctExpr);
+
+						newsubexpr->opno = subexpr->opno;
+						newsubexpr->opfuncid = subexpr->opfuncid;
+						newsubexpr->opresulttype = subexpr->opresulttype;
+						newsubexpr->opretset = subexpr->opretset;
+						newsubexpr->opcollid = subexpr->opcollid;
+						newsubexpr->inputcollid = subexpr->inputcollid;
+						newsubexpr->args = subexpr->args;
+						newsubexpr->location = subexpr->location;
+
+						return (Node *) makeCachedExpr(
+												(CacheableExpr *) newsubexpr);
+					}
 				}
 
 				/*
@@ -2862,24 +3114,175 @@ eval_const_expressions_mutator(Node *node,
 				newexpr->location = expr->location;
 				return (Node *) newexpr;
 			}
+		case T_NullIfExpr:
+			{
+				NullIfExpr *expr = (NullIfExpr *) node;
+				List	   *args = expr->args;
+				ListCell   *arg;
+				bool		has_null_input = false;
+				bool		has_nonconst_input = false;
+				Expr	   *simple;
+				NullIfExpr *newexpr;
+
+				/*
+				 * Reduce constants in the NullIfExpr's arguments.  We know
+				 * args is either NIL or a List node, so we can call
+				 * expression_tree_mutator directly rather than recursing to
+				 * self.
+				 */
+				args = (List *) expression_tree_mutator((Node *) expr->args,
+														eval_const_expressions_mutator,
+														(void *) context);
+
+				/*
+				 * We must do our own check for NULLs because NullIfExpr has
+				 * different results for NULL input than the underlying
+				 * operator does.
+				 */
+				foreach(arg, args)
+				{
+					if (IsA(lfirst(arg), Const))
+						has_null_input |= ((Const *) lfirst(arg))->constisnull;
+					else
+						has_nonconst_input = true;
+				}
+
+				/*
+				 * All constants and has null? Then const arguments aren't
+				 * equal, so return the first one.
+				 */
+				if (!has_nonconst_input && has_null_input)
+					return linitial(args);
+
+				/* Try to evaluate the '=' operator */
+				/* (NOT okay to try to inline it, though!) */
+
+				/*
+				 * Need to get OID of underlying function.  Okay to scribble
+				 * on input to this extent.
+				 */
+				set_opfuncid((OpExpr *) expr);	/* rely on struct
+												 * equivalence */
+
+				/*
+				 * Code for op/func reduction is pretty bulky, so split it
+				 * out as a separate function.
+				 */
+				simple = simplify_function(expr->opfuncid,
+										   BOOLOID, -1,
+										   InvalidOid,
+										   expr->inputcollid,
+										   &args,
+										   false,
+										   false,
+										   true,
+										   context,
+										   COERCE_EXPLICIT_CALL,
+										   expr->opno,
+										   expr->location);
+
+				if (simple) /* successfully simplified it */
+				{
+					if (IsA(simple, Const))
+					{
+						/*
+						 * Since the underlying operator is "=", return the
+						 * first argument if the arguments are not equal and
+						 * NULL otherwise.
+						 */
+						Const	   *csimple = castNode(Const, simple);
+
+						if (DatumGetBool(csimple->constvalue))
+						{
+							return (Node *) makeNullConst(
+													expr->opresulttype,
+													exprTypmod(linitial(args)),
+													expr->opcollid);
+						}
+						else
+						{
+							return linitial(args);
+						}
+					}
+					else if (IsA(simple, CachedExpr))
+					{
+						/*
+						 * Cache NullIfExpr using information from its cached
+						 * operator.
+						 */
+						CachedExpr *csimple = castNode(CachedExpr, simple);
+						OpExpr	   *subexpr = castNode(OpExpr,
+													   csimple->subexpr);
+						NullIfExpr *newsubexpr = makeNode(NullIfExpr);
+
+						newsubexpr->opno = subexpr->opno;
+						newsubexpr->opfuncid = subexpr->opfuncid;
+						newsubexpr->opresulttype = expr->opresulttype;
+						newsubexpr->opretset = subexpr->opretset;
+						newsubexpr->opcollid = expr->opcollid;
+						newsubexpr->inputcollid = subexpr->inputcollid;
+						newsubexpr->args = subexpr->args;
+						newsubexpr->location = subexpr->location;
+
+						return (Node *) makeCachedExpr(
+												(CacheableExpr *) newsubexpr);
+					}
+				}
+
+				/*
+				 * The expression cannot be simplified any further, so build
+				 * and return a replacement NullIfExpr node using the
+				 * possibly-simplified arguments.
+				 */
+				newexpr = makeNode(NullIfExpr);
+				newexpr->opno = expr->opno;
+				newexpr->opfuncid = expr->opfuncid;
+				newexpr->opresulttype = expr->opresulttype;
+				newexpr->opretset = expr->opretset;
+				newexpr->opcollid = expr->opcollid;
+				newexpr->inputcollid = expr->inputcollid;
+				newexpr->args = args;
+				newexpr->location = expr->location;
+				return (Node *) newexpr;
+			}
 		case T_ScalarArrayOpExpr:
+		case T_RowCompareExpr:
 			{
-				ScalarArrayOpExpr *saop;
+				/*
+				 * Generic handling for node types whose own processing checks
+				 * that their functions and arguments are safe for evaluation or
+				 * caching.
+				 */
+				ece_check_node_safety_detailed args_detailed,
+											   func_detailed;
+
+				/* Copy the node and simplify its arguments */
+				node = ece_generic_processing(node);
 
-				/* Copy the node and const-simplify its arguments */
-				saop = (ScalarArrayOpExpr *) ece_generic_processing(node);
+				/* Check node args and functions */
+				args_detailed = ece_all_arguments_const(node);
+				func_detailed = ece_functions_are_safe(node, false,
+													   context->estimate);
 
-				/* Make sure we know underlying function */
-				set_sa_opfuncid(saop);
+				/*
+				 * If all arguments are Consts, and node functions are safe for
+				 * evaluation, we can fold to a constant
+				 */
+				if (args_detailed == SAFE_FOR_EVALUATION &&
+					func_detailed == SAFE_FOR_EVALUATION)
+					return ece_evaluate_expr(node);
 
 				/*
-				 * If all arguments are Consts, and it's a safe function, we
-				 * can fold to a constant
+				 * If we do not perform estimation, all arguments are Consts or
+				 * CachedExprs, and node functions are safe for caching, we can
+				 * fold to a CachedExpr
 				 */
-				if (ece_all_arguments_const(saop) &&
-					ece_function_is_safe(saop->opfuncid, context))
-					return ece_evaluate_expr(saop);
-				return (Node *) saop;
+				if (!context->estimate &&
+					SAFE_FOR_CACHING(args_detailed) &&
+					SAFE_FOR_CACHING(func_detailed))
+					return (Node *) makeCachedExpr((CacheableExpr *) node);
+
+				return node;
 			}
 		case T_BoolExpr:
 			{
@@ -2892,11 +3295,14 @@ eval_const_expressions_mutator(Node *node,
 							List	   *newargs;
 							bool		haveNull = false;
 							bool		forceTrue = false;
-
-							newargs = simplify_or_arguments(expr->args,
-															context,
-															&haveNull,
-															&forceTrue);
+							bool		all_consts_or_cached = true;
+
+							newargs = simplify_or_arguments(
+														expr->args,
+														context,
+														&haveNull,
+														&forceTrue,
+														&all_consts_or_cached);
 							if (forceTrue)
 								return makeBoolConst(true, false);
 							if (haveNull)
@@ -2911,20 +3317,37 @@ eval_const_expressions_mutator(Node *node,
 							 * result
 							 */
 							if (list_length(newargs) == 1)
+							{
 								return (Node *) linitial(newargs);
-							/* Else we still need an OR node */
-							return (Node *) make_orclause(newargs);
+							}
+							else
+							{
+								/* We still need an OR node */
+								Expr	   *newexpr = make_orclause(newargs,
+																	false);
+
+								/* Check if we can cache the new expression */
+								if (!context->estimate && all_consts_or_cached)
+								{
+									return (Node *) makeCachedExpr(
+													(CacheableExpr *) newexpr);
+								}
+								return (Node *) newexpr;
+							}
 						}
 					case AND_EXPR:
 						{
 							List	   *newargs;
 							bool		haveNull = false;
 							bool		forceFalse = false;
-
-							newargs = simplify_and_arguments(expr->args,
-															 context,
-															 &haveNull,
-															 &forceFalse);
+							bool		all_consts_or_cached = true;
+
+							newargs = simplify_and_arguments(
+														expr->args,
+														context,
+														&haveNull,
+														&forceFalse,
+														&all_consts_or_cached);
 							if (forceFalse)
 								return makeBoolConst(false, false);
 							if (haveNull)
@@ -2939,13 +3362,28 @@ eval_const_expressions_mutator(Node *node,
 							 * result
 							 */
 							if (list_length(newargs) == 1)
+							{
 								return (Node *) linitial(newargs);
-							/* Else we still need an AND node */
-							return (Node *) make_andclause(newargs);
+							}
+							else
+							{
+								/* We still need an AND node */
+								Expr	   *newexpr = make_andclause(newargs,
+																	 false);
+
+								/* Check if we can cache the new expression */
+								if (!context->estimate && all_consts_or_cached)
+								{
+									return (Node *) makeCachedExpr(
+													(CacheableExpr *) newexpr);
+								}
+								return (Node *) newexpr;
+							}
 						}
 					case NOT_EXPR:
 						{
 							Node	   *arg;
+							Expr	   *newexpr;
 
 							Assert(list_length(expr->args) == 1);
 							arg = eval_const_expressions_mutator(linitial(expr->args),
@@ -2955,7 +3393,24 @@ eval_const_expressions_mutator(Node *node,
 							 * Use negate_clause() to see if we can simplify
 							 * away the NOT.
 							 */
-							return negate_clause(arg);
+							newexpr = (Expr *) negate_clause(arg);
+
+							if ((IsA(newexpr, BoolExpr) &&
+								 ((BoolExpr *) newexpr)->boolop == NOT_EXPR) ||
+								IsA(newexpr, CachedExpr))
+							{
+								/*
+								 * Simplification did not help and we again got
+								 * the NOT node, or the expression is already
+								 * cached.
+								 */
+								return (Node *) newexpr;
+							}
+
+							/* Try to simplify the whole new expression */
+							return eval_const_expressions_mutator(
+															(Node *) newexpr,
+															context);
 						}
 					default:
 						elog(ERROR, "unrecognized boolop: %d",
@@ -2980,7 +3435,8 @@ eval_const_expressions_mutator(Node *node,
 				 * If we can simplify the input to a constant, then we don't
 				 * need the RelabelType node anymore: just change the type
 				 * field of the Const node.  Otherwise, must copy the
-				 * RelabelType node.
+				 * RelabelType node; cache it if its arg is also cached and we
+				 * do not perform estimation.
 				 */
 				RelabelType *relabel = (RelabelType *) node;
 				Node	   *arg;
@@ -2989,11 +3445,11 @@ eval_const_expressions_mutator(Node *node,
 													 context);
 
 				/*
-				 * If we find stacked RelabelTypes (eg, from foo :: int ::
-				 * oid) we can discard all but the top one.
+				 * If we find stacked (and possibly cached) RelabelTypes (eg,
+				 * from foo :: int :: oid) we can discard all but the top one.
 				 */
-				while (arg && IsA(arg, RelabelType))
-					arg = (Node *) ((RelabelType *) arg)->arg;
+				while (arg && IsAIfCached(arg, RelabelType))
+					arg = (Node *) castNodeIfCached(RelabelType, arg)->arg;
 
 				if (arg && IsA(arg, Const))
 				{
@@ -3014,6 +3470,13 @@ eval_const_expressions_mutator(Node *node,
 					newrelabel->resultcollid = relabel->resultcollid;
 					newrelabel->relabelformat = relabel->relabelformat;
 					newrelabel->location = relabel->location;
+
+					/* Check if we can cache this node */
+					if (!context->estimate && arg && IsA(arg, CachedExpr))
+					{
+						return (Node *) makeCachedExpr(
+												(CacheableExpr *) newrelabel);
+					}
 					return (Node *) newrelabel;
 				}
 			}
@@ -3027,6 +3490,7 @@ eval_const_expressions_mutator(Node *node,
 				Oid			intypioparam;
 				Expr	   *simple;
 				CoerceViaIO *newexpr;
+				bool		cache = false;
 
 				/* Make a List so we can use simplify_function */
 				args = list_make1(expr->arg);
@@ -3045,6 +3509,7 @@ eval_const_expressions_mutator(Node *node,
 				getTypeInputInfo(expr->resulttype,
 								 &infunc, &intypioparam);
 
+				/* Try to simplify the source type's output function */
 				simple = simplify_function(outfunc,
 										   CSTRINGOID, -1,
 										   InvalidOid,
@@ -3052,8 +3517,12 @@ eval_const_expressions_mutator(Node *node,
 										   &args,
 										   false,
 										   true,
-										   true,
-										   context);
+										   false,
+										   context,
+										   COERCE_EXPLICIT_CALL,
+										   InvalidOid,
+										   -1);
+
 				if (simple)		/* successfully simplified output fn */
 				{
 					/*
@@ -3084,10 +3553,23 @@ eval_const_expressions_mutator(Node *node,
 											   &args,
 											   false,
 											   false,
-											   true,
-											   context);
+											   false,
+											   context,
+											   COERCE_EXPLICIT_CALL,
+											   InvalidOid,
+											   -1);
 					if (simple) /* successfully simplified input fn */
-						return (Node *) simple;
+					{
+						if (IsA(simple, CachedExpr))
+						{
+							/* return later cached CoerceViaIO node */
+							cache = true;
+						}
+						else
+						{
+							return (Node *) simple;
+						}
+					}
 				}
 
 				/*
@@ -3101,27 +3583,49 @@ eval_const_expressions_mutator(Node *node,
 				newexpr->resultcollid = expr->resultcollid;
 				newexpr->coerceformat = expr->coerceformat;
 				newexpr->location = expr->location;
+
+				/* Check if we can cache the new expression */
+				if (cache)
+					return (Node *) makeCachedExpr((CacheableExpr *) newexpr);
+
 				return (Node *) newexpr;
 			}
 		case T_ArrayCoerceExpr:
 			{
 				ArrayCoerceExpr *ac;
+				ece_check_node_safety_detailed func_detailed;
 
-				/* Copy the node and const-simplify its arguments */
+				/* Copy the node and simplify its arguments */
 				ac = (ArrayCoerceExpr *) ece_generic_processing(node);
 
+				/* Check the functions in the per-element expression */
+				func_detailed = ece_functions_are_safe((Node *) ac->elemexpr,
+													   true, false);
+
 				/*
 				 * If constant argument and the per-element expression is
 				 * immutable, we can simplify the whole thing to a constant.
-				 * Exception: although contain_mutable_functions considers
+				 * Exception: although ece_functions_are_safe considers
 				 * CoerceToDomain immutable for historical reasons, let's not
 				 * do so here; this ensures coercion to an array-over-domain
 				 * does not apply the domain's constraints until runtime.
 				 */
 				if (ac->arg && IsA(ac->arg, Const) &&
 					ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) &&
-					!contain_mutable_functions((Node *) ac->elemexpr))
+					func_detailed == SAFE_FOR_EVALUATION)
 					return ece_evaluate_expr(ac);
+
+				/*
+				 * If not estimation mode, constant or cached argument, and the
+				 * per-element expression is immutable or stable, we can cache
+				 * the whole thing.
+				 */
+				if (!context->estimate &&
+					is_const_or_cached((Node *) ac->arg) &&
+					ac->elemexpr && !IsA(ac->elemexpr, CoerceToDomain) &&
+					SAFE_FOR_CACHING(func_detailed))
+					return (Node *) makeCachedExpr((CacheableExpr *) ac);
+
 				return (Node *) ac;
 			}
 		case T_CollateExpr:
@@ -3163,11 +3667,13 @@ eval_const_expressions_mutator(Node *node,
 					relabel->location = collate->location;
 
 					/* Don't create stacked RelabelTypes */
-					while (arg && IsA(arg, RelabelType))
-						arg = (Node *) ((RelabelType *) arg)->arg;
+					while (arg && IsAIfCached(arg, RelabelType))
+						arg = (Node *) castNodeIfCached(RelabelType, arg)->arg;
 					relabel->arg = (Expr *) arg;
 
-					return (Node *) relabel;
+					/* Try to cache the new expression */
+					return eval_const_expressions_mutator((Node *) relabel,
+														  context);
 				}
 			}
 		case T_CaseExpr:
@@ -3200,6 +3706,10 @@ eval_const_expressions_mutator(Node *node,
 				 * expression when executing the CASE, since any contained
 				 * CaseTestExprs that might have referred to it will have been
 				 * replaced by the constant.
+				 *
+				 * If we do not perform estimation and all expressions in the
+				 * CASE expression are constant or cached, the CASE expression
+				 * will also be cached.
 				 *----------
 				 */
 				CaseExpr   *caseexpr = (CaseExpr *) node;
@@ -3210,6 +3720,7 @@ eval_const_expressions_mutator(Node *node,
 				bool		const_true_cond;
 				Node	   *defresult = NULL;
 				ListCell   *arg;
+				bool		all_subexpr_const_or_cached = true;
 
 				/* Simplify the test expression, if any */
 				newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
@@ -3222,8 +3733,17 @@ eval_const_expressions_mutator(Node *node,
 					context->case_val = newarg;
 					newarg = NULL;	/* not needed anymore, see above */
 				}
+				else if (newarg && IsA(newarg, CachedExpr))
+				{
+					/* create dummy CachedExpr node */
+					context->case_val = (Node *) makeCachedExpr(NULL);
+				}
 				else
+				{
 					context->case_val = NULL;
+					if (newarg)
+						all_subexpr_const_or_cached = false;
+				}
 
 				/* Simplify the WHEN clauses */
 				newargs = NIL;
@@ -3238,26 +3758,43 @@ eval_const_expressions_mutator(Node *node,
 					casecond = eval_const_expressions_mutator((Node *) oldcasewhen->expr,
 															  context);
 
-					/*
-					 * If the test condition is constant FALSE (or NULL), then
-					 * drop this WHEN clause completely, without processing
-					 * the result.
-					 */
-					if (casecond && IsA(casecond, Const))
+					if (casecond)
 					{
-						Const	   *const_input = (Const *) casecond;
-
-						if (const_input->constisnull ||
-							!DatumGetBool(const_input->constvalue))
-							continue;	/* drop alternative with FALSE cond */
-						/* Else it's constant TRUE */
-						const_true_cond = true;
+						/*
+						 * If the test condition is constant FALSE (or NULL),
+						 * then drop this WHEN clause completely, without
+						 * processing the result.
+						 */
+						if (IsA(casecond, Const))
+						{
+							Const	   *const_input = (Const *) casecond;
+
+							if (const_input->constisnull ||
+								!DatumGetBool(const_input->constvalue))
+							{
+								/* Drop alternative with FALSE cond */
+								continue;
+							}
+							/* Else it's constant TRUE */
+							const_true_cond = true;
+						}
+						else if (IsA(casecond, CachedExpr))
+						{
+							/* OK */
+						}
+						else
+						{
+							all_subexpr_const_or_cached = false;
+						}
 					}
 
 					/* Simplify this alternative's result value */
 					caseresult = eval_const_expressions_mutator((Node *) oldcasewhen->result,
 																context);
 
+					all_subexpr_const_or_cached &=
+						is_const_or_cached(caseresult);
+
 					/* If non-constant test condition, emit a new WHEN node */
 					if (!const_true_cond)
 					{
@@ -3281,9 +3818,14 @@ eval_const_expressions_mutator(Node *node,
 
 				/* Simplify the default result, unless we replaced it above */
 				if (!const_true_cond)
+				{
 					defresult = eval_const_expressions_mutator((Node *) caseexpr->defresult,
 															   context);
 
+					all_subexpr_const_or_cached &=
+						is_const_or_cached(defresult);
+				}
+
 				context->case_val = save_case_val;
 
 				/*
@@ -3300,35 +3842,66 @@ eval_const_expressions_mutator(Node *node,
 				newcase->args = newargs;
 				newcase->defresult = (Expr *) defresult;
 				newcase->location = caseexpr->location;
+
+				/* Check if we can cache this node */
+				if (!context->estimate && all_subexpr_const_or_cached)
+					return (Node *) makeCachedExpr((CacheableExpr *) newcase);
+
 				return (Node *) newcase;
 			}
 		case T_CaseTestExpr:
 			{
+				Node	   *case_val = context->case_val;
+
 				/*
-				 * If we know a constant test value for the current CASE
+				 * If we know that the test node for the current CASE is cached,
+				 * return the cached current node.
+				 * Else if we know a constant test value for the current CASE
 				 * construct, substitute it for the placeholder.  Else just
 				 * return the placeholder as-is.
 				 */
-				if (context->case_val)
-					return copyObject(context->case_val);
-				else
-					return copyObject(node);
+				if (case_val)
+				{
+					if (IsA(case_val, CachedExpr))
+					{
+						return (Node *) makeCachedExpr(
+											(CacheableExpr *) copyObject(node));
+					}
+					return copyObject(case_val);
+				}
+				return copyObject(node);
 			}
 		case T_ArrayRef:
 		case T_ArrayExpr:
 		case T_RowExpr:
+		case T_ConvertRowtypeExpr:
+		case T_MinMaxExpr:
+		case T_XmlExpr:
 			{
 				/*
 				 * Generic handling for node types whose own processing is
 				 * known to be immutable, and for which we need no smarts
-				 * beyond "simplify if all inputs are constants".
+				 * beyond "simplify if all inputs are constants or cached
+				 * expressions".
 				 */
+				ece_check_node_safety_detailed args_detailed;
 
-				/* Copy the node and const-simplify its arguments */
+				/* Copy the node and simplify its arguments */
 				node = ece_generic_processing(node);
+
 				/* If all arguments are Consts, we can fold to a constant */
-				if (ece_all_arguments_const(node))
+				args_detailed = ece_all_arguments_const(node);
+				if (args_detailed == SAFE_FOR_EVALUATION)
 					return ece_evaluate_expr(node);
+
+				/*
+				 * If we do not perform estimation, and all arguments are Consts
+				 * or CachedExprs, we can cache the result of this node.
+				 */
+				if (!context->estimate &&
+					args_detailed == SAFE_FOR_CACHING_ONLY)
+					return (Node *) makeCachedExpr((CacheableExpr *) node);
+
 				return node;
 			}
 		case T_CoalesceExpr:
@@ -3337,6 +3910,7 @@ eval_const_expressions_mutator(Node *node,
 				CoalesceExpr *newcoalesce;
 				List	   *newargs;
 				ListCell   *arg;
+				bool		all_args_are_const_or_cached = true;
 
 				newargs = NIL;
 				foreach(arg, coalesceexpr->args)
@@ -3363,6 +3937,14 @@ eval_const_expressions_mutator(Node *node,
 						newargs = lappend(newargs, e);
 						break;
 					}
+					else if (IsA(e, CachedExpr))
+					{
+						/* OK */
+					}
+					else
+					{
+						all_args_are_const_or_cached = false;
+					}
 					newargs = lappend(newargs, e);
 				}
 
@@ -3380,6 +3962,12 @@ eval_const_expressions_mutator(Node *node,
 				newcoalesce->coalescecollid = coalesceexpr->coalescecollid;
 				newcoalesce->args = newargs;
 				newcoalesce->location = coalesceexpr->location;
+				/* Check if we can cache this node */
+				if (!context->estimate && all_args_are_const_or_cached)
+				{
+					return (Node *) makeCachedExpr(
+												(CacheableExpr *) newcoalesce);
+				}
 				return (Node *) newcoalesce;
 			}
 		case T_SQLValueFunction:
@@ -3387,7 +3975,7 @@ eval_const_expressions_mutator(Node *node,
 				/*
 				 * All variants of SQLValueFunction are stable, so if we are
 				 * estimating the expression's value, we should evaluate the
-				 * current function value.  Otherwise just copy.
+				 * current function value.  Otherwise copy and cache it.
 				 */
 				SQLValueFunction *svf = (SQLValueFunction *) node;
 
@@ -3397,7 +3985,8 @@ eval_const_expressions_mutator(Node *node,
 												  svf->typmod,
 												  InvalidOid);
 				else
-					return copyObject((Node *) svf);
+					return (Node *) makeCachedExpr(
+											(CacheableExpr *) copyObject(node));
 			}
 		case T_FieldSelect:
 			{
@@ -3447,9 +4036,9 @@ eval_const_expressions_mutator(Node *node,
 												fselect->resultcollid,
 												((Var *) arg)->varlevelsup);
 				}
-				if (arg && IsA(arg, RowExpr))
+				if (arg && IsAIfCached(arg, RowExpr))
 				{
-					RowExpr    *rowexpr = (RowExpr *) arg;
+					RowExpr    *rowexpr = castNodeIfCached(RowExpr, arg);
 
 					if (fselect->fieldnum > 0 &&
 						fselect->fieldnum <= list_length(rowexpr->args))
@@ -3485,6 +4074,12 @@ eval_const_expressions_mutator(Node *node,
 											  newfselect->resultcollid))
 						return ece_evaluate_expr(newfselect);
 				}
+				/* Check if we can cache this node */
+				if (!context->estimate && is_const_or_cached(arg))
+				{
+					return (Node *) makeCachedExpr(
+												(CacheableExpr *) newfselect);
+				}
 				return (Node *) newfselect;
 			}
 		case T_NullTest:
@@ -3495,7 +4090,8 @@ eval_const_expressions_mutator(Node *node,
 
 				arg = eval_const_expressions_mutator((Node *) ntest->arg,
 													 context);
-				if (ntest->argisrow && arg && IsA(arg, RowExpr))
+
+				if (ntest->argisrow && arg && IsAIfCached(arg, RowExpr))
 				{
 					/*
 					 * We break ROW(...) IS [NOT] NULL into separate tests on
@@ -3503,9 +4099,10 @@ eval_const_expressions_mutator(Node *node,
 					 * efficient to evaluate, as well as being more amenable
 					 * to optimization.
 					 */
-					RowExpr    *rarg = (RowExpr *) arg;
+					RowExpr    *rarg = castNodeIfCached(RowExpr, arg);
 					List	   *newargs = NIL;
 					ListCell   *l;
+					bool		rarg_cached = IsA(arg, CachedExpr);
 
 					foreach(l, rarg->args)
 					{
@@ -3544,9 +4141,28 @@ eval_const_expressions_mutator(Node *node,
 						return makeBoolConst(true, false);
 					/* If only one nonconst input, it's the result */
 					if (list_length(newargs) == 1)
+					{
 						return (Node *) linitial(newargs);
-					/* Else we need an AND node */
-					return (Node *) make_andclause(newargs);
+					}
+					else
+					{
+						/* We need an AND node */
+						Node	   *newnode = (Node *) make_andclause(newargs,
+																	  false);
+
+						/*
+						 * We can cache the result if we do not perform
+						 * estimation, and the input also was cached (since only
+						 * the const args were ommitted and they do
+						 * not change this).
+						 */
+						if (!context->estimate && rarg_cached)
+						{
+							return (Node *) makeCachedExpr(
+													(CacheableExpr *) newnode);
+						}
+						return newnode;
+					}
 				}
 				if (!ntest->argisrow && arg && IsA(arg, Const))
 				{
@@ -3576,6 +4192,11 @@ eval_const_expressions_mutator(Node *node,
 				newntest->nulltesttype = ntest->nulltesttype;
 				newntest->argisrow = ntest->argisrow;
 				newntest->location = ntest->location;
+
+				/* Check if we can cache this node */
+				if (!context->estimate && is_const_or_cached(arg))
+					return (Node *) makeCachedExpr((CacheableExpr *) newntest);
+
 				return (Node *) newntest;
 			}
 		case T_BooleanTest:
@@ -3636,6 +4257,11 @@ eval_const_expressions_mutator(Node *node,
 				newbtest->arg = (Expr *) arg;
 				newbtest->booltesttype = btest->booltesttype;
 				newbtest->location = btest->location;
+
+				/* Check if we can cache this node */
+				if (!context->estimate && is_const_or_cached(arg))
+					return (Node *) makeCachedExpr((CacheableExpr *) newbtest);
+
 				return (Node *) newbtest;
 			}
 		case T_PlaceHolderVar:
@@ -3655,13 +4281,46 @@ eval_const_expressions_mutator(Node *node,
 													  context);
 			}
 			break;
+		case T_CachedExpr:
+			{
+				/*
+				 * Process it, because maybe we can get the Const node or we
+				 * perform estimation and there should be no cached expressions.
+				 */
+				return eval_const_expressions_mutator(
+										(Node *) ((CachedExpr *) node)->subexpr,
+										context);
+			}
+		case T_RangeTblFunction:
+			{
+				RangeTblFunction *rtfunc;
+
+				/* Copy the node and simplify its arguments */
+				rtfunc = (RangeTblFunction *) ece_generic_processing(node);
+
+				/*
+				 * Although SRF expressions are not cached, expressions that
+				 * return RECORD can be cached. If such cached expression is
+				 * executed in FROM (ROWS FROM), this means that it is executed
+				 * only once so we can treat it as a non-cached expression (note
+				 * that cached expressions always are calculated as many times
+				 * as they are mentioned in the query).
+				 */
+				if (IsA(rtfunc->funcexpr, CachedExpr))
+				{
+					rtfunc->funcexpr =
+						(Node *) ((CachedExpr *) rtfunc->funcexpr)->subexpr;
+				}
+
+				return (Node *) rtfunc;
+			}
 		default:
 			break;
 	}
 
 	/*
 	 * For any node type not handled above, copy the node unchanged but
-	 * const-simplify its subexpressions.  This is the correct thing for node
+	 * simplify its subexpressions.  This is the correct thing for node
 	 * types whose behavior might change between planning and execution, such
 	 * as CoerceToDomain.  It's also a safe default for new node types not
 	 * known to this routine.
@@ -3670,33 +4329,81 @@ eval_const_expressions_mutator(Node *node,
 }
 
 /*
- * Subroutine for eval_const_expressions: check for non-Const nodes.
+ * Subroutine for eval_const_expressions: check for non-Const or non-CachedExpr
+ * nodes.
+ *
+ * We can abort recursion immediately on finding a non-Const and non-CachedExpr
+ * node.  This is critical for performance, else eval_const_expressions_mutator
+ * would take O(N^2) time on non-simplifiable trees.  However, we do need to
+ * descend into List nodes since expression_tree_walker sometimes invokes the
+ * walker function directly on List subtrees.
  *
- * We can abort recursion immediately on finding a non-Const node.  This is
- * critical for performance, else eval_const_expressions_mutator would take
- * O(N^2) time on non-simplifiable trees.  However, we do need to descend
- * into List nodes since expression_tree_walker sometimes invokes the walker
- * function directly on List subtrees.
+ * The output argument *detailed must be initialized SAFE_FOR_EVALUATION by the
+ * caller. It will be set SAFE_FOR_CACHING_ONLY if only constant and cached
+ * nodes are detected anywhere in the argument list. It will be set
+ * SAFE_FOR_NOTHING if a non-constant or non-cached node is detected anywhere in
+ * the argument list.
  */
 static bool
-contain_non_const_walker(Node *node, void *context)
+contain_non_const_walker(Node *node, ece_check_node_safety_detailed *detailed)
 {
 	if (node == NULL)
+	{
+		/* this does not affect the value of the detailed result */
 		return false;
+	}
 	if (IsA(node, Const))
+	{
+		/* this does not affect the value of the detailed result */
+		return false;
+	}
+	if (IsA(node, CachedExpr))
+	{
+		*detailed = SAFE_FOR_CACHING_ONLY;
 		return false;
+	}
 	if (IsA(node, List))
-		return expression_tree_walker(node, contain_non_const_walker, context);
+	{
+		return expression_tree_walker(node, contain_non_const_walker,
+									  (void *) detailed);
+	}
 	/* Otherwise, abort the tree traversal and return true */
+	*detailed = SAFE_FOR_NOTHING;
 	return true;
 }
 
 /*
- * Subroutine for eval_const_expressions: check if a function is OK to evaluate
+ * ece_functions_are_safe
+ *	  Search for noncacheable functions within a clause (possibly recursively).
+ *
+ *	  Returns true if any non-cacheable function found.
+ *
+ * The output argument context->detailed must be initialized SAFE_FOR_EVALUATION
+ * by the caller. It will be set SAFE_FOR_CACHING_ONLY if we do not perform
+ * estimation and only immutable and stable functions are found (in case of
+ * estimation there's no difference between SAFE_FOR_EVALUATION and
+ * SAFE_FOR_CACHING_ONLY, and the returned value is always SAFE_FOR_EVALUATION
+ * because it is more strict in general). It will be set SAFE_FOR_NOTHING if a
+ * volatile function is detected anywhere in the subexpressions.
  */
+static ece_check_node_safety_detailed
+ece_functions_are_safe(Node *node, bool recurse, bool estimate)
+{
+	ece_functions_are_safe_context context;
+
+	context.detailed = SAFE_FOR_EVALUATION;
+	context.recurse = recurse;
+	context.estimate = estimate;
+
+	ece_functions_are_safe_walker(node, &context);
+	return context.detailed;
+}
+
 static bool
-ece_function_is_safe(Oid funcid, eval_const_expressions_context *context)
+ece_functions_are_safe_checker(Oid funcid, void *context)
 {
+	ece_functions_are_safe_context *safe_context =
+		(ece_functions_are_safe_context *) context;
 	char		provolatile = func_volatile(funcid);
 
 	/*
@@ -3707,10 +4414,74 @@ ece_function_is_safe(Oid funcid, eval_const_expressions_context *context)
 	 * to estimate the value at all.
 	 */
 	if (provolatile == PROVOLATILE_IMMUTABLE)
+	{
+		/* this does not affect the value of the detailed result */
+		return false;
+	}
+	else if (provolatile == PROVOLATILE_STABLE)
+	{
+		if (safe_context->estimate)
+		{
+			/* this does not affect the value of the detailed result */
+			return false;
+		}
+		else
+		{
+			safe_context->detailed = SAFE_FOR_CACHING_ONLY;
+			return false;
+		}
+	}
+	else
+	{
+		safe_context->detailed = SAFE_FOR_NOTHING;
+		return true;
+	}
+}
+
+static bool
+ece_functions_are_safe_walker(Node *node,
+							  ece_functions_are_safe_context *context)
+{
+	if (node == NULL)
+		return false;
+	/* Check for functions in node itself */
+	if (check_functions_in_node(node, ece_functions_are_safe_checker,
+								(void *) context))
 		return true;
-	if (context->estimate && provolatile == PROVOLATILE_STABLE)
+
+	if (IsA(node, SQLValueFunction))
+	{
+		/* all variants of SQLValueFunction are stable */
+		if (!context->estimate)
+			context->detailed = SAFE_FOR_CACHING_ONLY;
+		return false;
+	}
+
+	if (IsA(node, NextValueExpr))
+	{
+		/* NextValueExpr is volatile */
+		context->detailed = SAFE_FOR_NOTHING;
 		return true;
-	return false;
+	}
+
+	/*
+	 * See notes in contain_mutable_functions_walker about why we treat
+	 * MinMaxExpr, XmlExpr, and CoerceToDomain as immutable.  Hence, immutable
+	 * functions do not affect the value of the detailed result.
+	 */
+
+	if (!context->recurse)
+		return false;
+
+	/* Recurse to check arguments */
+	if (IsA(node, Query))
+	{
+		/* Recurse into subselects */
+		return query_tree_walker((Query *) node, ece_functions_are_safe_walker,
+								 (void *) context, 0);
+	}
+	return expression_tree_walker(node, ece_functions_are_safe_walker,
+								  (void *) context);
 }
 
 /*
@@ -3730,12 +4501,16 @@ ece_function_is_safe(Oid funcid, eval_const_expressions_context *context)
  *
  * The output arguments *haveNull and *forceTrue must be initialized false
  * by the caller.  They will be set true if a NULL constant or TRUE constant,
- * respectively, is detected anywhere in the argument list.
+ * respectively, is detected anywhere in the argument list. The output argument
+ * *all_consts_or_cached must be initialized true by the caller. It will be set
+ * false if a non-constant or non-cached node is detected anywhere in the
+ * argument list.
  */
 static List *
 simplify_or_arguments(List *args,
 					  eval_const_expressions_context *context,
-					  bool *haveNull, bool *forceTrue)
+					  bool *haveNull, bool *forceTrue,
+					  bool *all_consts_or_cached)
 {
 	List	   *newargs = NIL;
 	List	   *unprocessed_args;
@@ -3759,9 +4534,10 @@ simplify_or_arguments(List *args,
 		unprocessed_args = list_delete_first(unprocessed_args);
 
 		/* flatten nested ORs as per above comment */
-		if (or_clause(arg))
+		if (or_clause(arg, true))
 		{
-			List	   *subargs = list_copy(((BoolExpr *) arg)->args);
+			List	   *subargs =
+				list_copy(castNodeIfCached(BoolExpr, arg)->args);
 
 			/* overly tense code to avoid leaking unused list header */
 			if (!unprocessed_args)
@@ -3785,9 +4561,10 @@ simplify_or_arguments(List *args,
 		 * since it's not a mainstream case. In particular we don't worry
 		 * about const-simplifying the input twice.
 		 */
-		if (or_clause(arg))
+		if (or_clause(arg, true))
 		{
-			List	   *subargs = list_copy(((BoolExpr *) arg)->args);
+			List	   *subargs =
+				list_copy(castNodeIfCached(BoolExpr, arg)->args);
 
 			unprocessed_args = list_concat(subargs, unprocessed_args);
 			continue;
@@ -3817,6 +4594,14 @@ simplify_or_arguments(List *args,
 			/* otherwise, we can drop the constant-false input */
 			continue;
 		}
+		else if (IsA(arg, CachedExpr))
+		{
+			/* OK */
+		}
+		else
+		{
+			*all_consts_or_cached = false;
+		}
 
 		/* else emit the simplified arg into the result list */
 		newargs = lappend(newargs, arg);
@@ -3842,12 +4627,16 @@ simplify_or_arguments(List *args,
  *
  * The output arguments *haveNull and *forceFalse must be initialized false
  * by the caller.  They will be set true if a null constant or false constant,
- * respectively, is detected anywhere in the argument list.
+ * respectively, is detected anywhere in the argument list. The output argument
+ * *all_consts_or_cached must be initialized true by the caller. It will be set
+ * false if a non-constant or non-cached node is detected anywhere in the
+ * argument list.
  */
 static List *
 simplify_and_arguments(List *args,
 					   eval_const_expressions_context *context,
-					   bool *haveNull, bool *forceFalse)
+					   bool *haveNull, bool *forceFalse,
+					   bool *all_consts_or_cached)
 {
 	List	   *newargs = NIL;
 	List	   *unprocessed_args;
@@ -3861,9 +4650,10 @@ simplify_and_arguments(List *args,
 		unprocessed_args = list_delete_first(unprocessed_args);
 
 		/* flatten nested ANDs as per above comment */
-		if (and_clause(arg))
+		if (and_clause(arg, true))
 		{
-			List	   *subargs = list_copy(((BoolExpr *) arg)->args);
+			List	   *subargs =
+				list_copy(castNodeIfCached(BoolExpr, arg)->args);
 
 			/* overly tense code to avoid leaking unused list header */
 			if (!unprocessed_args)
@@ -3887,9 +4677,10 @@ simplify_and_arguments(List *args,
 		 * since it's not a mainstream case. In particular we don't worry
 		 * about const-simplifying the input twice.
 		 */
-		if (and_clause(arg))
+		if (and_clause(arg, true))
 		{
-			List	   *subargs = list_copy(((BoolExpr *) arg)->args);
+			List	   *subargs =
+				list_copy(castNodeIfCached(BoolExpr, arg)->args);
 
 			unprocessed_args = list_concat(subargs, unprocessed_args);
 			continue;
@@ -3919,6 +4710,14 @@ simplify_and_arguments(List *args,
 			/* otherwise, we can drop the constant-true input */
 			continue;
 		}
+		else if (IsA(arg, CachedExpr))
+		{
+			/* OK */
+		}
+		else
+		{
+			*all_consts_or_cached = false;
+		}
 
 		/* else emit the simplified arg into the result list */
 		newargs = lappend(newargs, arg);
@@ -3941,8 +4740,9 @@ simplify_and_arguments(List *args,
  * ensures that we will recognize these forms as being equivalent in, for
  * example, partial index matching.
  *
- * We come here only if simplify_function has failed; therefore we cannot
- * see two constant inputs, nor a constant-NULL input.
+ * We come here only if the previous strategies in simplify_function have
+ * failed; therefore we cannot see two constant inputs, nor a constant-NULL
+ * input.
  */
 static Node *
 simplify_boolean_equality(Oid opno, List *args)
@@ -3999,8 +4799,10 @@ simplify_boolean_equality(Oid opno, List *args)
  * Inputs are the function OID, actual result type OID (which is needed for
  * polymorphic functions), result typmod, result collation, the input
  * collation to use for the function, the original argument list (not
- * const-simplified yet, unless process_args is false), and some flags;
- * also the context data for eval_const_expressions.
+ * simplified yet, unless process_args is false), the coercion form of the
+ * function output, the operator OID (InvalidOid if the function itself), the
+ * location (used only for simple caching), and some flags; also the context
+ * data for eval_const_expressions.
  *
  * Returns a simplified expression if successful, or NULL if cannot
  * simplify the function call.
@@ -4009,15 +4811,17 @@ simplify_boolean_equality(Oid opno, List *args)
  * lists into positional notation and/or adding any needed default argument
  * expressions; which is a bit grotty, but it avoids extra fetches of the
  * function's pg_proc tuple.  For this reason, the args list is
- * pass-by-reference.  Conversion and const-simplification of the args list
+ * pass-by-reference.  Conversion and simplification of the args list
  * will be done even if simplification of the function call itself is not
  * possible.
  */
 static Expr *
 simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 				  Oid result_collid, Oid input_collid, List **args_p,
-				  bool funcvariadic, bool process_args, bool allow_non_const,
-				  eval_const_expressions_context *context)
+				  bool funcvariadic, bool process_args,
+				  bool allow_only_consts_and_simple_caching,
+				  eval_const_expressions_context *context,
+				  CoercionForm funcformat, Oid opno, int location)
 {
 	List	   *args = *args_p;
 	HeapTuple	func_tuple;
@@ -4025,16 +4829,19 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 	Expr	   *newexpr;
 
 	/*
-	 * We have three strategies for simplification: execute the function to
+	 * We have five strategies for simplification: execute the function to
 	 * deliver a constant result, use a transform function to generate a
-	 * substitute node tree, or expand in-line the body of the function
+	 * substitute node tree, expand in-line the body of the function
 	 * definition (which only works for simple SQL-language functions, but
-	 * that is a common case).  Each case needs access to the function's
+	 * that is a common case), simplify the boolean equality/inequality, or
+	 * cache this function call.  Each case needs access to the function's
 	 * pg_proc tuple, so fetch it just once.
 	 *
-	 * Note: the allow_non_const flag suppresses both the second and third
-	 * strategies; so if !allow_non_const, simplify_function can only return a
-	 * Const or NULL.  Argument-list rewriting happens anyway, though.
+	 * Note: the allow_only_consts_and_simple_caching flag suppresses the
+	 * second, third and fourth strategies; so if
+	 * allow_only_consts_and_simple_caching, simplify_function can only return a
+	 * Const, CachedExpr or NULL.  Argument-list rewriting happens anyway,
+	 * though.
 	 */
 	func_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
 	if (!HeapTupleIsValid(func_tuple))
@@ -4064,7 +4871,9 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 								args, funcvariadic,
 								func_tuple, context);
 
-	if (!newexpr && allow_non_const && OidIsValid(func_form->protransform))
+	if (!newexpr &&
+		!allow_only_consts_and_simple_caching &&
+		OidIsValid(func_form->protransform))
 	{
 		/*
 		 * Build a dummy FuncExpr node containing the simplified arg list.  We
@@ -4087,13 +4896,47 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 		newexpr = (Expr *)
 			DatumGetPointer(OidFunctionCall1(func_form->protransform,
 											 PointerGetDatum(&fexpr)));
+
+		if (newexpr && !context->estimate && !IsA(newexpr, CachedExpr))
+		{
+			/* Try to cache the new expression. */
+			newexpr = (Expr *) eval_const_expressions_mutator((Node *) newexpr,
+															  context);
+		}
 	}
 
-	if (!newexpr && allow_non_const)
+	if (!newexpr && !allow_only_consts_and_simple_caching)
 		newexpr = inline_function(funcid, result_type, result_collid,
 								  input_collid, args, funcvariadic,
 								  func_tuple, context);
 
+	/*
+	 * If the operator is boolean equality or inequality, we know
+	 * how to simplify cases involving one constant and one
+	 * non-constant argument.
+	 */
+	if (!newexpr &&
+		!allow_only_consts_and_simple_caching &&
+		(opno == BooleanEqualOperator ||
+		 opno == BooleanNotEqualOperator))
+	{
+		newexpr = (Expr *) simplify_boolean_equality(opno, args);
+
+		if (newexpr && !context->estimate && !IsA(newexpr, CachedExpr))
+		{
+			/* Try to cache the new expression. */
+			newexpr = (Expr *) eval_const_expressions_mutator((Node *) newexpr,
+															  context);
+		}
+	}
+
+	if (!newexpr && !context->estimate)
+	{
+		newexpr = cache_function(funcid, result_type, result_collid,
+								 input_collid, args, funcvariadic, func_tuple,
+								 context, funcformat, opno, location);
+	}
+
 	ReleaseSysCache(func_tuple);
 
 	return newexpr;
@@ -4809,6 +5652,14 @@ substitute_actual_parameters_mutator(Node *node,
 		/* We don't need to copy at this time (it'll get done later) */
 		return list_nth(context->args, param->paramid - 1);
 	}
+	if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		return substitute_actual_parameters_mutator(
+												(Node *) cachedexpr->subexpr,
+												context);
+	}
 	return expression_tree_mutator(node, substitute_actual_parameters_mutator,
 								   (void *) context);
 }
@@ -4969,6 +5820,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 		return NULL;
 	rtfunc = (RangeTblFunction *) linitial(rte->functions);
 
+	/*
+	 * Do not check whether this is CachedExpr with a FuncExpr subexpression
+	 * because STF are not cached.
+	 */
 	if (!IsA(rtfunc->funcexpr, FuncExpr))
 		return NULL;
 	fexpr = (FuncExpr *) rtfunc->funcexpr;
@@ -5272,6 +6127,14 @@ substitute_actual_srf_parameters_mutator(Node *node,
 			return result;
 		}
 	}
+	if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		return substitute_actual_srf_parameters_mutator(
+												(Node *) cachedexpr->subexpr,
+												context);
+	}
 	return expression_tree_mutator(node,
 								   substitute_actual_srf_parameters_mutator,
 								   (void *) context);
@@ -5319,3 +6182,154 @@ tlist_matches_coltypelist(List *tlist, List *coltypelist)
 
 	return true;
 }
+
+/*
+ * is_opclause
+ *
+ * Returns t iff this is an operator expression.
+ * Check the subexpression of the cached expression if check_cachedexpr is true.
+ */
+bool
+is_opclause(Node *clause, bool check_cachedexpr)
+{
+	if (check_cachedexpr && IsA(clause, CachedExpr))
+		clause = (Node *) ((CachedExpr *) clause)->subexpr;
+
+	return (clause != NULL && IsA(clause, OpExpr));
+}
+
+/*
+ * is_funcclause
+ *
+ * Returns t iff this is a function expression.
+ * Check the subexpression of the cached expression if check_cachedexpr is true.
+ */
+bool
+is_funcclause(Node *clause, bool check_cachedexpr)
+{
+	if (check_cachedexpr && IsA(clause, CachedExpr))
+		clause = (Node *) ((CachedExpr *) clause)->subexpr;
+
+	return (clause != NULL && IsA(clause, FuncExpr));
+}
+
+/*
+ * Return true if node is Const or CachedExpr.
+ */
+static bool
+is_const_or_cached(const Node *node)
+{
+	if (node == NULL)
+		return false;
+
+	return (IsA(node, Const) || IsA(node, CachedExpr));
+}
+
+/*
+ * cache_function: try to cache a result of calling this function
+ *
+ * We can do this if the function does not return a set, is not volatile itself,
+ * and its arguments are constants or recursively cached expressions. Also we do
+ * not cache during estimation.
+ *
+ * Returns a cached expression if successful, or NULL if cannot cache the
+ * function. Returns an expression for the cached operator if opno is not
+ * InvalidOid.
+ */
+static Expr *
+cache_function(Oid funcid, Oid result_type, Oid result_collid, Oid input_collid,
+			   List *args, bool funcvariadic, HeapTuple func_tuple,
+			   eval_const_expressions_context *context, CoercionForm funcformat,
+			   Oid opno, int location)
+{
+	Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
+	Expr	   *newexpr;
+
+	/* Sanity checks */
+	if (context->estimate)
+		return NULL;
+
+	/* Can't cache the result of the function if it returns a set */
+	if (funcform->proretset)
+		return NULL;
+
+	/* Check for non-constant or non-cached inputs */
+	if (!SAFE_FOR_CACHING(ece_all_arguments_const((Node *) args)))
+		return NULL;
+
+	/* Can't cache volatile functions */
+	if (funcform->provolatile == PROVOLATILE_IMMUTABLE)
+		/* okay */ ;
+	else if (funcform->provolatile == PROVOLATILE_STABLE)
+		/* okay */ ;
+	else
+		return NULL;
+
+	/* Create an expression for this function/operator call */
+	if (opno == InvalidOid)
+	{
+		FuncExpr   *funcexpr = makeNode(FuncExpr);
+
+		funcexpr->funcid = funcid;
+		funcexpr->funcresulttype = result_type;
+		funcexpr->funcretset = false;
+		funcexpr->funcvariadic = funcvariadic;
+		funcexpr->funcformat = funcformat;
+		funcexpr->funccollid = result_collid;
+		funcexpr->inputcollid = input_collid;
+		funcexpr->args = args;
+		funcexpr->location = location;
+
+		newexpr = (Expr *) funcexpr;
+	}
+	else
+	{
+		OpExpr	   *opexpr = makeNode(OpExpr);
+
+		opexpr->opno = opno;
+		opexpr->opfuncid = funcid;
+		opexpr->opresulttype = result_type;
+		opexpr->opretset = false;
+		opexpr->opcollid = result_collid;
+		opexpr->inputcollid = input_collid;
+		opexpr->args = args;
+		opexpr->location = location;
+
+		newexpr = (Expr *) opexpr;
+	}
+
+	return (Expr *) makeCachedExpr((CacheableExpr *) newexpr);
+}
+
+/*
+ * set_non_internal_cachedexprs_walker
+ *
+ * Add all non-internal cached expressions to cachedexprs and set the cached_ids
+ * for them.
+ *
+ * Caller must initialize the result list to which all found non-internal cached
+ * expressions will be added.
+ */
+bool
+set_non_internal_cachedexprs_walker(Node *node, List **cachedexprs)
+{
+	Assert(cachedexprs);
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, CachedExpr))
+	{
+		CachedExpr *cachedexpr = (CachedExpr *) node;
+
+		cachedexpr->cached_id = list_length(*cachedexprs);
+		*cachedexprs = lappend(*cachedexprs, (void *) cachedexpr);
+
+		/* do NOT recurse into children */
+		return false;
+	}
+
+	/* recurse into children */
+	return expression_tree_walker(node, set_non_internal_cachedexprs_walker,
+								  (void *) cachedexprs);
+}
diff --git a/src/backend/optimizer/util/orclauses.c b/src/backend/optimizer/util/orclauses.c
index 1e78028..207cf92 100644
--- a/src/backend/optimizer/util/orclauses.c
+++ b/src/backend/optimizer/util/orclauses.c
@@ -164,8 +164,8 @@ extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel)
 	/*
 	 * Scan each arm of the input OR clause.  Notice we descend into
 	 * or_rinfo->orclause, which has RestrictInfo nodes embedded below the
-	 * toplevel OR/AND structure.  This is useful because we can use the info
-	 * in those nodes to make is_safe_restriction_clause_for()'s checks
+	 * toplevel non-cached OR/AND structure.  This is useful because we can use
+	 * the info in those nodes to make is_safe_restriction_clause_for()'s checks
 	 * cheaper.  We'll strip those nodes from the returned tree, though,
 	 * meaning that fresh ones will be built if the clause is accepted as a
 	 * restriction clause.  This might seem wasteful --- couldn't we re-use
@@ -173,15 +173,15 @@ extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel)
 	 * selectivity and other cached data is computed exactly the same way for
 	 * a restriction clause as for a join clause, which seems undesirable.
 	 */
-	Assert(or_clause((Node *) or_rinfo->orclause));
+	Assert(or_clause((Node *) or_rinfo->orclause, false));
 	foreach(lc, ((BoolExpr *) or_rinfo->orclause)->args)
 	{
 		Node	   *orarg = (Node *) lfirst(lc);
 		List	   *subclauses = NIL;
 		Node	   *subclause;
 
-		/* OR arguments should be ANDs or sub-RestrictInfos */
-		if (and_clause(orarg))
+		/* OR arguments should be non-cached ANDs or sub-RestrictInfos */
+		if (and_clause(orarg, false))
 		{
 			List	   *andargs = ((BoolExpr *) orarg)->args;
 			ListCell   *lc2;
@@ -231,9 +231,14 @@ extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel)
 		 * to preserve AND/OR flatness (ie, no OR directly underneath OR).
 		 */
 		subclause = (Node *) make_ands_explicit(subclauses);
-		if (or_clause(subclause))
-			clauselist = list_concat(clauselist,
-									 list_copy(((BoolExpr *) subclause)->args));
+
+		/* This is not used for pseudoconstants */
+		Assert(!IsA(subclause, CachedExpr));
+
+		if (or_clause(subclause, false))
+			clauselist = list_concat(
+				clauselist,
+				list_copy(((BoolExpr *) subclause)->args));
 		else
 			clauselist = lappend(clauselist, subclause);
 	}
@@ -244,7 +249,7 @@ extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel)
 	 * one arm --- but then the input OR node was also redundant.)
 	 */
 	if (clauselist != NIL)
-		return make_orclause(clauselist);
+		return make_orclause(clauselist, false);
 	return NULL;
 }
 
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index 02ce883..8760915 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -839,14 +839,14 @@ predicate_classify(Node *clause, PredIterInfo info)
 	}
 
 	/* Handle normal AND and OR boolean clauses */
-	if (and_clause(clause))
+	if (and_clause(clause, true))
 	{
 		info->startup_fn = boolexpr_startup_fn;
 		info->next_fn = list_next_fn;
 		info->cleanup_fn = list_cleanup_fn;
 		return CLASS_AND;
 	}
-	if (or_clause(clause))
+	if (or_clause(clause, true))
 	{
 		info->startup_fn = boolexpr_startup_fn;
 		info->next_fn = list_next_fn;
@@ -855,9 +855,9 @@ predicate_classify(Node *clause, PredIterInfo info)
 	}
 
 	/* Handle ScalarArrayOpExpr */
-	if (IsA(clause, ScalarArrayOpExpr))
+	if (IsAIfCached(clause, ScalarArrayOpExpr))
 	{
-		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+		ScalarArrayOpExpr *saop = castNodeIfCached(ScalarArrayOpExpr, clause);
 		Node	   *arraynode = (Node *) lsecond(saop->args);
 
 		/*
@@ -882,9 +882,10 @@ predicate_classify(Node *clause, PredIterInfo info)
 				return saop->useOr ? CLASS_OR : CLASS_AND;
 			}
 		}
-		else if (arraynode && IsA(arraynode, ArrayExpr) &&
-				 !((ArrayExpr *) arraynode)->multidims &&
-				 list_length(((ArrayExpr *) arraynode)->elements) <= MAX_SAOP_ARRAY_SIZE)
+		else if (arraynode && IsAIfCached(arraynode, ArrayExpr) &&
+				 !castNodeIfCached(ArrayExpr, arraynode)->multidims &&
+				 list_length(castNodeIfCached(ArrayExpr, arraynode)->elements) <=
+				 MAX_SAOP_ARRAY_SIZE)
 		{
 			info->startup_fn = arrayexpr_startup_fn;
 			info->next_fn = arrayexpr_next_fn;
@@ -933,7 +934,7 @@ list_cleanup_fn(PredIterInfo info)
 static void
 boolexpr_startup_fn(Node *clause, PredIterInfo info)
 {
-	info->state = (void *) list_head(((BoolExpr *) clause)->args);
+	info->state = (void *) list_head(castNodeIfCached(BoolExpr, clause)->args);
 }
 
 /*
@@ -953,7 +954,7 @@ typedef struct
 static void
 arrayconst_startup_fn(Node *clause, PredIterInfo info)
 {
-	ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+	ScalarArrayOpExpr *saop = castNodeIfCached(ScalarArrayOpExpr, clause);
 	ArrayConstIterState *state;
 	Const	   *arrayconst;
 	ArrayType  *arrayval;
@@ -1024,8 +1025,9 @@ arrayconst_cleanup_fn(PredIterInfo info)
 }
 
 /*
- * PredIterInfo routines for iterating over a ScalarArrayOpExpr with a
- * one-dimensional ArrayExpr array operand.
+ * PredIterInfo routines for iterating over a (possibly cached)
+ * ScalarArrayOpExpr with a one-dimensional (possibly cached) ArrayExpr array
+ * operand.
  */
 typedef struct
 {
@@ -1036,7 +1038,7 @@ typedef struct
 static void
 arrayexpr_startup_fn(Node *clause, PredIterInfo info)
 {
-	ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+	ScalarArrayOpExpr *saop = castNodeIfCached(ScalarArrayOpExpr, clause);
 	ArrayExprIterState *state;
 	ArrayExpr  *arrayexpr;
 
@@ -1055,7 +1057,7 @@ arrayexpr_startup_fn(Node *clause, PredIterInfo info)
 	state->opexpr.args = list_copy(saop->args);
 
 	/* Initialize iteration variable to first member of ArrayExpr */
-	arrayexpr = (ArrayExpr *) lsecond(saop->args);
+	arrayexpr = castNodeIfCached(ArrayExpr, lsecond(saop->args));
 	state->next = list_head(arrayexpr->elements);
 }
 
@@ -1115,6 +1117,12 @@ predicate_implied_by_simple_clause(Expr *predicate, Node *clause,
 	/* Allow interrupting long proof attempts */
 	CHECK_FOR_INTERRUPTS();
 
+	/*
+	 * We assume the predicate has already been checked to contain only
+	 * immutable functions and operators.
+	 */
+	Assert(!IsA(predicate, CachedExpr));
+
 	/* First try the equal() test */
 	if (equal((Node *) predicate, clause))
 		return true;
@@ -1177,6 +1185,12 @@ predicate_refuted_by_simple_clause(Expr *predicate, Node *clause,
 	/* Allow interrupting long proof attempts */
 	CHECK_FOR_INTERRUPTS();
 
+	/*
+	 * We assume the predicate has already been checked to contain only
+	 * immutable functions and operators.
+	 */
+	Assert(!IsA(predicate, CachedExpr));
+
 	/* A simple clause can't refute itself */
 	/* Worth checking because of relation_excluded_by_constraints() */
 	if ((Node *) predicate == clause)
@@ -1197,31 +1211,42 @@ predicate_refuted_by_simple_clause(Expr *predicate, Node *clause,
 			return true;
 
 		/* foo IS NOT NULL refutes foo IS NULL */
-		if (clause && IsA(clause, NullTest) &&
-			((NullTest *) clause)->nulltesttype == IS_NOT_NULL &&
-			!((NullTest *) clause)->argisrow &&
-			equal(((NullTest *) clause)->arg, isnullarg))
+		if (clause && IsAIfCached(clause, NullTest) &&
+			castNodeIfCached(NullTest, clause)->nulltesttype == IS_NOT_NULL &&
+			!castNodeIfCached(NullTest, clause)->argisrow &&
+			equal(castNodeIfCached(NullTest, clause)->arg, isnullarg))
 			return true;
 
 		return false;			/* we can't succeed below... */
 	}
 
 	/* Try the clause-IS-NULL case */
-	if (clause && IsA(clause, NullTest) &&
-		((NullTest *) clause)->nulltesttype == IS_NULL)
+	if (clause && IsAIfCached(clause, NullTest) &&
+		castNodeIfCached(NullTest, clause)->nulltesttype == IS_NULL)
 	{
-		Expr	   *isnullarg = ((NullTest *) clause)->arg;
+		Expr	   *isnullarg = castNodeIfCached(NullTest, clause)->arg;
 
 		/* row IS NULL does not act in the simple way we have in mind */
-		if (((NullTest *) clause)->argisrow)
+		if (castNodeIfCached(NullTest, clause)->argisrow)
 			return false;
 
 		/* foo IS NULL refutes foo IS NOT NULL */
-		if (predicate && IsA(predicate, NullTest) &&
-			((NullTest *) predicate)->nulltesttype == IS_NOT_NULL &&
-			!((NullTest *) predicate)->argisrow &&
-			equal(((NullTest *) predicate)->arg, isnullarg))
-			return true;
+		if (predicate && IsA(predicate, NullTest))
+		{
+			NullTest   *predicate_isnull = (NullTest *) predicate;
+			Expr	   *predicate_isnullarg = predicate_isnull->arg;
+
+			/*
+			 * We assume the predicate has already been checked to contain only
+			 * immutable functions and operators.
+			 */
+			Assert(!IsA(predicate_isnullarg, CachedExpr));
+
+			if (predicate_isnull->nulltesttype == IS_NOT_NULL &&
+				!predicate_isnull->argisrow &&
+				equal(predicate_isnullarg, isnullarg))
+				return true;
+		}
 
 		/* foo IS NULL weakly refutes any predicate that is strict for foo */
 		if (weak &&
@@ -1245,6 +1270,13 @@ extract_not_arg(Node *clause)
 {
 	if (clause == NULL)
 		return NULL;
+
+	/*
+	 * We assume the predicate has already been checked to contain only
+	 * immutable functions and operators.
+	 */
+	Assert(!IsA(clause, CachedExpr));
+
 	if (IsA(clause, BoolExpr))
 	{
 		BoolExpr   *bexpr = (BoolExpr *) clause;
@@ -1273,16 +1305,16 @@ extract_strong_not_arg(Node *clause)
 {
 	if (clause == NULL)
 		return NULL;
-	if (IsA(clause, BoolExpr))
+	if (IsAIfCached(clause, BoolExpr))
 	{
-		BoolExpr   *bexpr = (BoolExpr *) clause;
+		BoolExpr   *bexpr = castNodeIfCached(BoolExpr, clause);
 
 		if (bexpr->boolop == NOT_EXPR)
 			return (Node *) linitial(bexpr->args);
 	}
-	else if (IsA(clause, BooleanTest))
+	else if (IsAIfCached(clause, BooleanTest))
 	{
-		BooleanTest *btest = (BooleanTest *) clause;
+		BooleanTest *btest = castNodeIfCached(BooleanTest, clause);
 
 		if (btest->booltesttype == IS_FALSE)
 			return (Node *) btest->arg;
@@ -1316,10 +1348,10 @@ clause_is_strict_for(Node *clause, Node *subexpr)
 	 * through any nullness-preserving, immutable operation.)  We should not
 	 * see stacked RelabelTypes here.
 	 */
-	if (IsA(clause, RelabelType))
-		clause = (Node *) ((RelabelType *) clause)->arg;
-	if (IsA(subexpr, RelabelType))
-		subexpr = (Node *) ((RelabelType *) subexpr)->arg;
+	if (IsAIfCached(clause, RelabelType))
+		clause = (Node *) castNodeIfCached(RelabelType, clause)->arg;
+	if (IsAIfCached(subexpr, RelabelType))
+		subexpr = (Node *) castNodeIfCached(RelabelType, subexpr)->arg;
 
 	/* Base case */
 	if (equal(clause, subexpr))
@@ -1330,20 +1362,20 @@ clause_is_strict_for(Node *clause, Node *subexpr)
 	 * if any input is forced NULL by subexpr.  This is OK even if the op or
 	 * func isn't immutable, since it won't even be called on NULL input.
 	 */
-	if (is_opclause(clause) &&
-		op_strict(((OpExpr *) clause)->opno))
+	if (is_opclause(clause, true) &&
+		op_strict(castNodeIfCached(OpExpr, clause)->opno))
 	{
-		foreach(lc, ((OpExpr *) clause)->args)
+		foreach(lc, castNodeIfCached(OpExpr, clause)->args)
 		{
 			if (clause_is_strict_for((Node *) lfirst(lc), subexpr))
 				return true;
 		}
 		return false;
 	}
-	if (is_funcclause(clause) &&
-		func_strict(((FuncExpr *) clause)->funcid))
+	if (is_funcclause(clause, true) &&
+		func_strict(castNodeIfCached(FuncExpr, clause)->funcid))
 	{
-		foreach(lc, ((FuncExpr *) clause)->args)
+		foreach(lc, castNodeIfCached(FuncExpr, clause)->args)
 		{
 			if (clause_is_strict_for((Node *) lfirst(lc), subexpr))
 				return true;
@@ -1558,14 +1590,14 @@ operator_predicate_proof(Expr *predicate, Node *clause,
 	 * about DistinctExpr in general, and this probably isn't the first place
 	 * to fix if you want to improve that.
 	 */
-	if (!is_opclause(predicate))
+	if (!is_opclause((Node *) predicate, false))
 		return false;
 	pred_opexpr = (OpExpr *) predicate;
 	if (list_length(pred_opexpr->args) != 2)
 		return false;
-	if (!is_opclause(clause))
+	if (!is_opclause(clause, true))
 		return false;
-	clause_opexpr = (OpExpr *) clause;
+	clause_opexpr = castNodeIfCached(OpExpr, clause);
 	if (list_length(clause_opexpr->args) != 2)
 		return false;
 
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 82b7842..c2d63f6 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -886,6 +886,7 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
 		 * translating whole-row Var of a child to that of the parent.
 		 * Children of an inherited table or subquery child rels can not
 		 * directly participate in a join, so other kinds of nodes here.
+		 * Do not check for cached expressions because they do not contain vars.
 		 */
 		if (IsA(var, Var))
 		{
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index edf5a48..9f7ff65 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -67,7 +67,7 @@ make_restrictinfo(Expr *clause,
 	 * If it's an OR clause, build a modified copy with RestrictInfos inserted
 	 * above each subclause of the top-level AND/OR structure.
 	 */
-	if (or_clause((Node *) clause))
+	if (or_clause((Node *) clause, true))
 		return (RestrictInfo *) make_sub_restrictinfos(clause,
 													   is_pushed_down,
 													   outerjoin_delayed,
@@ -78,7 +78,7 @@ make_restrictinfo(Expr *clause,
 													   nullable_relids);
 
 	/* Shouldn't be an AND clause, else AND/OR flattening messed up */
-	Assert(!and_clause((Node *) clause));
+	Assert(!and_clause((Node *) clause, true));
 
 	return make_restrictinfo_internal(clause,
 									  NULL,
@@ -133,7 +133,8 @@ make_restrictinfo_internal(Expr *clause,
 	 * If it's a binary opclause, set up left/right relids info. In any case
 	 * set up the total clause relids info.
 	 */
-	if (is_opclause(clause) && list_length(((OpExpr *) clause)->args) == 2)
+	if (is_opclause((Node *) clause, true) &&
+		list_length(castNodeIfCached(OpExpr, clause)->args) == 2)
 	{
 		restrictinfo->left_relids = pull_varnos(get_leftop(clause));
 		restrictinfo->right_relids = pull_varnos(get_rightop(clause));
@@ -232,12 +233,12 @@ make_sub_restrictinfos(Expr *clause,
 					   Relids outer_relids,
 					   Relids nullable_relids)
 {
-	if (or_clause((Node *) clause))
+	if (or_clause((Node *) clause, true))
 	{
 		List	   *orlist = NIL;
 		ListCell   *temp;
 
-		foreach(temp, ((BoolExpr *) clause)->args)
+		foreach(temp, castNodeIfCached(BoolExpr, clause)->args)
 			orlist = lappend(orlist,
 							 make_sub_restrictinfos(lfirst(temp),
 													is_pushed_down,
@@ -248,7 +249,8 @@ make_sub_restrictinfos(Expr *clause,
 													outer_relids,
 													nullable_relids));
 		return (Expr *) make_restrictinfo_internal(clause,
-												   make_orclause(orlist),
+												   make_orclause(orlist,
+																 false),
 												   is_pushed_down,
 												   outerjoin_delayed,
 												   pseudoconstant,
@@ -257,12 +259,12 @@ make_sub_restrictinfos(Expr *clause,
 												   outer_relids,
 												   nullable_relids);
 	}
-	else if (and_clause((Node *) clause))
+	else if (and_clause((Node *) clause, true))
 	{
 		List	   *andlist = NIL;
 		ListCell   *temp;
 
-		foreach(temp, ((BoolExpr *) clause)->args)
+		foreach(temp, castNodeIfCached(BoolExpr, clause)->args)
 			andlist = lappend(andlist,
 							  make_sub_restrictinfos(lfirst(temp),
 													 is_pushed_down,
@@ -272,7 +274,7 @@ make_sub_restrictinfos(Expr *clause,
 													 required_relids,
 													 outer_relids,
 													 nullable_relids));
-		return make_andclause(andlist);
+		return make_andclause(andlist, false);
 	}
 	else
 		return (Expr *) make_restrictinfo_internal(clause,
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index 32160d5..a8874fe 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -20,7 +20,10 @@
 #include "optimizer/tlist.h"
 
 
-/* Test if an expression node represents a SRF call.  Beware multiple eval! */
+/*
+ * Test if an expression node represents a SRF call.  Beware multiple eval!
+ * Do not check for cached expressions because they do not return a set.
+ */
 #define IS_SRF_CALL(node) \
 	((IsA(node, FuncExpr) && ((FuncExpr *) (node))->funcretset) || \
 	 (IsA(node, OpExpr) && ((OpExpr *) (node))->opretset))
@@ -79,11 +82,18 @@ tlist_member_ignore_relabel(Expr *node, List *targetlist)
 	while (node && IsA(node, RelabelType))
 		node = ((RelabelType *) node)->arg;
 
+	/* This is not used for cached expressions */
+	Assert(!IsA(node, CachedExpr));
+
 	foreach(temp, targetlist)
 	{
 		TargetEntry *tlentry = (TargetEntry *) lfirst(temp);
 		Expr	   *tlexpr = tlentry->expr;
 
+		/*
+		 * Do not worry about cached expressions because in any case they cannot
+		 * be equal to a non-cached node (see below).
+		 */
 		while (tlexpr && IsA(tlexpr, RelabelType))
 			tlexpr = ((RelabelType *) tlexpr)->arg;
 
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index b16b1e4..c43eda3 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -196,6 +196,11 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
 		context->sublevels_up--;
 		return result;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node, pull_varnos_walker,
 								  (void *) context);
 }
@@ -243,6 +248,11 @@ pull_varattnos_walker(Node *node, pull_varattnos_context *context)
 							   var->varattno - FirstLowInvalidHeapAttributeNumber);
 		return false;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 
 	/* Should not find an unplanned subquery */
 	Assert(!IsA(node, Query));
@@ -312,6 +322,11 @@ pull_vars_walker(Node *node, pull_vars_context *context)
 		context->sublevels_up--;
 		return result;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node, pull_vars_walker,
 								  (void *) context);
 }
@@ -352,6 +367,11 @@ contain_var_clause_walker(Node *node, void *context)
 			return true;		/* abort the tree traversal and return true */
 		/* else fall through to check the contained expr */
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node, contain_var_clause_walker, context);
 }
 
@@ -412,6 +432,11 @@ contain_vars_of_level_walker(Node *node, int *sublevels_up)
 		(*sublevels_up)--;
 		return result;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node,
 								  contain_vars_of_level_walker,
 								  (void *) sublevels_up);
@@ -486,6 +511,11 @@ locate_var_of_level_walker(Node *node,
 		context->sublevels_up--;
 		return result;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node,
 								  locate_var_of_level_walker,
 								  (void *) context);
@@ -636,6 +666,11 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
 		else
 			elog(ERROR, "PlaceHolderVar found where not expected");
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return false;
+	}
 	return expression_tree_walker(node, pull_var_clause_walker,
 								  (void *) context);
 }
@@ -807,6 +842,11 @@ flatten_join_alias_vars_mutator(Node *node,
 		context->sublevels_up--;
 		return (Node *) newnode;
 	}
+	if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return copyObject(node);
+	}
 	/* Already-planned tree not supported */
 	Assert(!IsA(node, SubPlan));
 	/* Shouldn't need to handle these planner auxiliary nodes here */
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index c31a563..b4f28b6 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -161,6 +161,9 @@ coerce_type(ParseState *pstate, Node *node,
 	CoercionPathType pathtype;
 	Oid			funcId;
 
+	/* This is used only for parser nodes */
+	Assert(!IsA(node, CachedExpr));
+
 	if (targetTypeId == inputTypeId ||
 		node == NULL)
 	{
@@ -991,6 +994,9 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 	int			ucolno;
 	ListCell   *arg;
 
+	/* This is used only for parser nodes */
+	Assert(!IsA(node, CachedExpr));
+
 	if (node && IsA(node, RowExpr))
 	{
 		/*
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index d407a89..eb5257c 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -621,7 +621,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
 		}
 
 		/* Get the BoolExpr's out of the way. */
-		if (IsA(clause, BoolExpr))
+		if (IsAIfCached(clause, BoolExpr))
 		{
 			/*
 			 * Generate steps for arguments.
@@ -631,7 +631,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
 			 * independently, collect their step IDs to be stored in the
 			 * combine step we'll be creating.
 			 */
-			if (or_clause((Node *) clause))
+			if (or_clause((Node *) clause, true))
 			{
 				List	   *arg_stepids = NIL;
 				bool		all_args_contradictory = true;
@@ -641,7 +641,7 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
 				 * Get pruning step for each arg.  If we get contradictory for
 				 * all args, it means the OR expression is false as a whole.
 				 */
-				foreach(lc1, ((BoolExpr *) clause)->args)
+				foreach(lc1, castNodeIfCached(BoolExpr, clause)->args)
 				{
 					Expr	   *arg = lfirst(lc1);
 					bool		arg_contradictory;
@@ -722,9 +722,9 @@ gen_partprune_steps_internal(GeneratePruningStepsContext *context,
 				}
 				continue;
 			}
-			else if (and_clause((Node *) clause))
+			else if (and_clause((Node *) clause, true))
 			{
-				List	   *args = ((BoolExpr *) clause)->args;
+				List	   *args = castNodeIfCached(BoolExpr, clause)->args;
 				List	   *argsteps,
 						   *arg_stepids = NIL;
 				ListCell   *lc1;
@@ -1415,10 +1415,10 @@ match_clause_to_partition_key(RelOptInfo *rel,
 
 		return PARTCLAUSE_MATCH_CLAUSE;
 	}
-	else if (IsA(clause, OpExpr) &&
-			 list_length(((OpExpr *) clause)->args) == 2)
+	else if (IsAIfCached(clause, OpExpr) &&
+			 list_length(castNodeIfCached(OpExpr, clause)->args) == 2)
 	{
-		OpExpr	   *opclause = (OpExpr *) clause;
+		OpExpr	   *opclause = castNodeIfCached(OpExpr, clause);
 		Expr	   *leftop,
 				   *rightop;
 		Oid			op_lefttype,
@@ -1431,11 +1431,11 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		PartClauseInfo *partclause;
 
 		leftop = (Expr *) get_leftop(clause);
-		if (IsA(leftop, RelabelType))
-			leftop = ((RelabelType *) leftop)->arg;
+		if (IsAIfCached(leftop, RelabelType))
+			leftop = castNodeIfCached(RelabelType, leftop)->arg;
 		rightop = (Expr *) get_rightop(clause);
-		if (IsA(rightop, RelabelType))
-			rightop = ((RelabelType *) rightop)->arg;
+		if (IsAIfCached(rightop, RelabelType))
+			rightop = castNodeIfCached(RelabelType, rightop)->arg;
 
 		/* check if the clause matches this partition key */
 		if (equal(leftop, partkey))
@@ -1593,9 +1593,9 @@ match_clause_to_partition_key(RelOptInfo *rel,
 
 		return PARTCLAUSE_MATCH_CLAUSE;
 	}
-	else if (IsA(clause, ScalarArrayOpExpr))
+	else if (IsAIfCached(clause, ScalarArrayOpExpr))
 	{
-		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+		ScalarArrayOpExpr *saop = castNodeIfCached(ScalarArrayOpExpr, clause);
 		Oid			saop_op = saop->opno;
 		Oid			saop_coll = saop->inputcollid;
 		Expr	   *leftop = (Expr *) linitial(saop->args),
@@ -1605,8 +1605,8 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		ListCell   *lc1;
 		bool		contradictory;
 
-		if (IsA(leftop, RelabelType))
-			leftop = ((RelabelType *) leftop)->arg;
+		if (IsAIfCached(leftop, RelabelType))
+			leftop = castNodeIfCached(RelabelType, leftop)->arg;
 
 		/* Check it matches this partition key */
 		if (!equal(leftop, partkey) ||
@@ -1697,9 +1697,9 @@ match_clause_to_partition_key(RelOptInfo *rel,
 				elem_exprs = lappend(elem_exprs, elem_expr);
 			}
 		}
-		else if (IsA(rightop, ArrayExpr))
+		else if (IsAIfCached(rightop, ArrayExpr))
 		{
-			ArrayExpr  *arrexpr = castNode(ArrayExpr, rightop);
+			ArrayExpr  *arrexpr = castNodeIfCached(ArrayExpr, rightop);
 
 			/*
 			 * For a nested ArrayExpr, we don't know how to get the actual
@@ -1750,13 +1750,13 @@ match_clause_to_partition_key(RelOptInfo *rel,
 			return PARTCLAUSE_UNSUPPORTED;	/* step generation failed */
 		return PARTCLAUSE_MATCH_STEPS;
 	}
-	else if (IsA(clause, NullTest))
+	else if (IsAIfCached(clause, NullTest))
 	{
-		NullTest   *nulltest = (NullTest *) clause;
+		NullTest   *nulltest = castNodeIfCached(NullTest, clause);
 		Expr	   *arg = nulltest->arg;
 
-		if (IsA(arg, RelabelType))
-			arg = ((RelabelType *) arg)->arg;
+		if (IsAIfCached(arg, RelabelType))
+			arg = castNodeIfCached(RelabelType, arg)->arg;
 
 		/* Does arg match with this partition key column? */
 		if (!equal(arg, partkey))
@@ -2706,9 +2706,9 @@ pull_partkey_params(PartitionPruneInfo *pinfo, List *steps)
 		{
 			Expr	   *expr = lfirst(lc2);
 
-			if (IsA(expr, Param))
+			if (IsAIfCached(expr, Param))
 			{
-				Param	   *param = (Param *) expr;
+				Param	   *param = castNodeIfCached(Param, expr);
 
 				switch (param->paramkind)
 				{
@@ -2969,20 +2969,27 @@ match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey,
 {
 	Expr	   *leftop;
 
+	/* Partition expressions can only contain immutable functions */
+	Assert(!IsA(partkey, CachedExpr));
+
 	*outconst = NULL;
 
 	if (!IsBooleanOpfamily(partopfamily))
 		return false;
 
-	if (IsA(clause, BooleanTest))
+	if (IsAIfCached(clause, BooleanTest))
 	{
-		BooleanTest *btest = (BooleanTest *) clause;
+		BooleanTest *btest = castNodeIfCached(BooleanTest, clause);
 
 		/* Only IS [NOT] TRUE/FALSE are any good to us */
 		if (btest->booltesttype == IS_UNKNOWN ||
 			btest->booltesttype == IS_NOT_UNKNOWN)
 			return false;
 
+		/*
+		 * Do not worry about cached expressions because in any case they cannot
+		 * be equal to the non-cached partkey (see below).
+		 */
 		leftop = btest->arg;
 		if (IsA(leftop, RelabelType))
 			leftop = ((RelabelType *) leftop)->arg;
@@ -2998,7 +3005,11 @@ match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey,
 	}
 	else
 	{
-		bool		is_not_clause = not_clause((Node *) clause);
+		/*
+		 * Do not worry about cached expressions because in any case they cannot
+		 * be equal to the non-cached partkey (see below).
+		 */
+		bool		is_not_clause = not_clause((Node *) clause, false);
 
 		leftop = is_not_clause ? get_notclausearg(clause) : clause;
 
@@ -3061,6 +3072,12 @@ partkey_datum_from_expr(PartitionPruneContext *context,
 			}
 			break;
 
+		case T_CachedExpr:
+			return partkey_datum_from_expr(
+										context,
+										(Expr *) ((CachedExpr *) expr)->subexpr,
+										stateidx, value);
+
 		default:
 			break;
 	}
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index f1f4212..c6f277e 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -307,6 +307,10 @@ contains_multiexpr_param(Node *node, void *context)
 {
 	if (node == NULL)
 		return false;
+
+	/*
+	 * Do not check the cached params because PARAM_MULTIEXPR cannot be cached.
+	 */
 	if (IsA(node, Param))
 	{
 		if (((Param *) node)->paramkind == PARAM_MULTIEXPR)
@@ -1343,6 +1347,11 @@ map_variable_attnos_mutator(Node *node,
 		context->sublevels_up--;
 		return (Node *) newnode;
 	}
+	else if (IsA(node, CachedExpr))
+	{
+		/* no vars in cached expressions */
+		return copyObject(node);
+	}
 	return expression_tree_mutator(node, map_variable_attnos_mutator,
 								   (void *) context);
 }
diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index 3b4a09b..c7f9b5b 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -757,7 +757,10 @@ dependency_is_compatible_clause(Node *clause, Index relid, AttrNumber *attnum)
 	if (!IsA(rinfo, RestrictInfo))
 		return false;
 
-	/* Pseudoconstants are not interesting (they couldn't contain a Var) */
+	/*
+	 * Pseudoconstants (including cached expressions) are not interesting (they
+	 * couldn't contain a Var)
+	 */
 	if (rinfo->pseudoconstant)
 		return false;
 
@@ -765,7 +768,7 @@ dependency_is_compatible_clause(Node *clause, Index relid, AttrNumber *attnum)
 	if (bms_membership(rinfo->clause_relids) != BMS_SINGLETON)
 		return false;
 
-	if (is_opclause(rinfo->clause))
+	if (is_opclause((Node *) rinfo->clause, false))
 	{
 		/* If it's an opclause, check for Var = Const or Const = Var. */
 		OpExpr	   *expr = (OpExpr *) rinfo->clause;
@@ -799,7 +802,7 @@ dependency_is_compatible_clause(Node *clause, Index relid, AttrNumber *attnum)
 
 		/* OK to proceed with checking "var" */
 	}
-	else if (not_clause((Node *) rinfo->clause))
+	else if (not_clause((Node *) rinfo->clause, false))
 	{
 		/*
 		 * "NOT x" can be interpreted as "x = false", so get the argument and
@@ -819,6 +822,7 @@ dependency_is_compatible_clause(Node *clause, Index relid, AttrNumber *attnum)
 	/*
 	 * We may ignore any RelabelType node above the operand.  (There won't be
 	 * more than one, since eval_const_expressions has been applied already.)
+	 * Do not check for cached expressions because they do not contain vars.
 	 */
 	if (IsA(var, RelabelType))
 		var = (Var *) ((RelabelType *) var)->arg;
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 979f6fd..48659fd 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -4474,6 +4474,7 @@ CheckDateTokenTables(void)
 Node *
 TemporalTransform(int32 max_precis, Node *node)
 {
+	/* this is not used for cached expressions */
 	FuncExpr   *expr = castNode(FuncExpr, node);
 	Node	   *ret = NULL;
 	Node	   *typmod;
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 8dfdffc..d3c5956 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -900,6 +900,7 @@ numeric_send(PG_FUNCTION_ARGS)
 Datum
 numeric_transform(PG_FUNCTION_ARGS)
 {
+	/* this is not used for cached expressions */
 	FuncExpr   *expr = castNode(FuncExpr, PG_GETARG_POINTER(0));
 	Node	   *ret = NULL;
 	Node	   *typmod;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 065238b..95d0f7e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5722,7 +5722,7 @@ get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno,
 		 * itself. (We can't skip the parens.)
 		 */
 		bool		need_paren = (PRETTY_PAREN(context)
-								  || IsA(expr, FuncExpr)
+								  || IsAIfCached(expr, FuncExpr)
 								  ||IsA(expr, Aggref)
 								  ||IsA(expr, WindowFunc));
 
@@ -6333,7 +6333,8 @@ get_update_query_targetlist_def(Query *query, List *targetList,
 			 * those there could be an implicit type coercion.  Because we
 			 * would ignore implicit type coercions anyway, we don't need to
 			 * be as careful as processIndirection() is about descending past
-			 * implicit CoerceToDomains.
+			 * implicit CoerceToDomains. Do not check if there're cached
+			 * expressions because PARAM_MULTIEXPR cannot be cached.
 			 */
 			expr = (Node *) tle->expr;
 			while (expr)
@@ -6797,9 +6798,9 @@ get_name_for_var_field(Var *var, int fieldno,
 	 * If it's a RowExpr that was expanded from a whole-row Var, use the
 	 * column names attached to it.
 	 */
-	if (IsA(var, RowExpr))
+	if (IsAIfCached(var, RowExpr))
 	{
-		RowExpr    *r = (RowExpr *) var;
+		RowExpr    *r = castNodeIfCached(RowExpr, var);
 
 		if (fieldno > 0 && fieldno <= list_length(r->colnames))
 			return strVal(list_nth(r->colnames, fieldno - 1));
@@ -6807,6 +6808,9 @@ get_name_for_var_field(Var *var, int fieldno,
 
 	/*
 	 * If it's a Param of type RECORD, try to find what the Param refers to.
+	 *
+	 * Do not check if this is a cached param as we only check PARAM_EXEC and
+	 * they cannot be cached.
 	 */
 	if (IsA(var, Param))
 	{
@@ -7312,7 +7316,7 @@ get_parameter(Param *param, deparse_context *context)
 		 */
 		need_paren = !(IsA(expr, Var) ||
 					   IsA(expr, Aggref) ||
-					   IsA(expr, Param));
+					   IsAIfCached(expr, Param));
 		if (need_paren)
 			appendStringInfoChar(context->buf, '(');
 
@@ -7445,6 +7449,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 					bool		is_hipriop;
 					bool		is_lopriparent;
 					bool		is_hipriparent;
+					Node	   *first_parent_arg;
 
 					op = get_simple_binary_op_name((OpExpr *) node);
 					if (!op)
@@ -7473,9 +7478,18 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 
 					/*
 					 * Operators are same priority --- can skip parens only if
-					 * we have (a - b) - c, not a - (b - c).
+					 * we have (a - b) - c, not a - (b - c). Note that the
+					 * arguments can be cached.
 					 */
-					if (node == (Node *) linitial(((OpExpr *) parentNode)->args))
+					first_parent_arg =
+						(Node *) linitial(((OpExpr *) parentNode)->args);
+					if (IsA(first_parent_arg, CachedExpr))
+					{
+						first_parent_arg =
+							(Node *) ((CachedExpr *) first_parent_arg)->subexpr;
+					}
+
+					if (node == first_parent_arg)
 						return true;
 
 					return false;
@@ -7566,6 +7580,16 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 					return false;
 			}
 
+		case T_CachedExpr:
+
+			/*
+			 * Since the cached expression is created only for internal use,
+			 * forget about it and process its subexpression.
+			 */
+			return isSimpleNode((Node *) ((CachedExpr *) node)->subexpr,
+								parentNode,
+								prettyFlags);
+
 		default:
 			break;
 	}
@@ -7737,6 +7761,19 @@ get_rule_expr(Node *node, deparse_context *context,
 			get_windowfunc_expr((WindowFunc *) node, context);
 			break;
 
+		case T_CachedExpr:
+			{
+				CachedExpr *cachedexpr = (CachedExpr *) node;
+
+				/*
+				 * Since the cached expression is created only for internal use,
+				 * forget about it and deparse its subexpression.
+				 */
+				get_rule_expr((Node *) cachedexpr->subexpr, context,
+							  showimplicit);
+			}
+			break;
+
 		case T_ArrayRef:
 			{
 				ArrayRef   *aref = (ArrayRef *) node;
@@ -7748,8 +7785,9 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * within a composite column.  Since we already punted on
 				 * displaying the FieldStore's target information, just punt
 				 * here too, and display only the assignment source
-				 * expression.
+				 * expression. Such cases are not cached.
 				 */
+				Assert(!IsACached(aref->refexpr, CaseTestExpr));
 				if (IsA(aref->refexpr, CaseTestExpr))
 				{
 					Assert(aref->refassgnexpr);
@@ -7764,7 +7802,7 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * *must* parenthesize to avoid confusion.)
 				 */
 				need_parens = !IsA(aref->refexpr, Var) &&
-					!IsA(aref->refexpr, FieldSelect);
+					!IsAIfCached(aref->refexpr, FieldSelect);
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr((Node *) aref->refexpr, context, showimplicit);
@@ -8000,7 +8038,8 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * WRONG to not parenthesize a Var argument; simplicity is not
 				 * the issue here, having the right number of names is.
 				 */
-				need_parens = !IsA(arg, ArrayRef) &&!IsA(arg, FieldSelect);
+				need_parens = !IsAIfCached(arg, ArrayRef) &&
+							  !IsAIfCached(arg, FieldSelect);
 				if (need_parens)
 					appendStringInfoChar(buf, '(');
 				get_rule_expr(arg, context, true);
@@ -8178,12 +8217,14 @@ get_rule_expr(Node *node, deparse_context *context,
 						 * inline function).  If we don't recognize the form
 						 * of the WHEN clause, just punt and display it as-is.
 						 */
-						if (IsA(w, OpExpr))
+						if (IsAIfCached(w, OpExpr))
 						{
-							List	   *args = ((OpExpr *) w)->args;
+							List	   *args =
+								castNodeIfCached(OpExpr, w)->args;
 
 							if (list_length(args) == 2 &&
-								IsA(strip_implicit_coercions(linitial(args)),
+								IsAIfCached(
+									strip_implicit_coercions(linitial(args)),
 									CaseTestExpr))
 								w = (Node *) lsecond(args);
 						}
@@ -8749,6 +8790,9 @@ get_rule_expr(Node *node, deparse_context *context,
 				save_varprefix = context->varprefix;
 				context->varprefix = false;
 
+				/* Index expressions can only contain immutable functions */
+				Assert(!IsA(iexpr->expr, CachedExpr));
+
 				/*
 				 * Parenthesize the element unless it's a simple Var or a bare
 				 * function call.  Follows pg_get_indexdef_worker().
@@ -8940,6 +8984,8 @@ looks_like_function(Node *node)
 		case T_XmlExpr:
 			/* these are all accepted by func_expr_common_subexpr */
 			return true;
+		case T_CachedExpr:
+			return looks_like_function((Node *) ((CachedExpr *) node)->subexpr);
 		default:
 			break;
 	}
@@ -9575,6 +9621,7 @@ get_sublink_expr(SubLink *sublink, deparse_context *context)
 			sep = "";
 			foreach(l, ((BoolExpr *) sublink->testexpr)->args)
 			{
+				/* this is not used for pseudoconstants */
 				OpExpr	   *opexpr = lfirst_node(OpExpr, l);
 
 				appendStringInfoString(buf, sep);
@@ -10415,12 +10462,16 @@ processIndirection(Node *node, deparse_context *context)
 			 */
 			node = (Node *) linitial(fstore->newvals);
 		}
-		else if (IsA(node, ArrayRef))
+		else if (IsAIfCached(node, ArrayRef))
 		{
-			ArrayRef   *aref = (ArrayRef *) node;
+			ArrayRef   *aref = castNodeIfCached(ArrayRef, node);
 
 			if (aref->refassgnexpr == NULL)
 				break;
+
+			/* Such cases are not cached */
+			Assert(!IsA(node, CachedExpr));
+
 			printSubscripts(aref, context);
 
 			/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 4b08cdb..7c2b8c2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -1566,7 +1566,7 @@ boolvarsel(PlannerInfo *root, Node *arg, int varRelid)
 		selec = var_eq_const(&vardata, BooleanEqualOperator,
 							 BoolGetDatum(true), false, true, false);
 	}
-	else if (is_funcclause(arg))
+	else if (is_funcclause(arg, true))
 	{
 		/*
 		 * If we have no stats and it's a function call, estimate 0.3333333.
@@ -1828,24 +1828,28 @@ strip_array_coercion(Node *node)
 {
 	for (;;)
 	{
-		if (node && IsA(node, ArrayCoerceExpr))
+		if (node && IsAIfCached(node, ArrayCoerceExpr))
 		{
-			ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
+			ArrayCoerceExpr *acoerce = castNodeIfCached(ArrayCoerceExpr, node);
 
 			/*
 			 * If the per-element expression is just a RelabelType on top of
 			 * CaseTestExpr, then we know it's a binary-compatible relabeling.
+			 * Such cases are not cached.
 			 */
+			Assert(!(IsAIfCached(acoerce->elemexpr, RelabelType) &&
+					 IsACached(castNodeIfCached(RelabelType, acoerce->elemexpr)->arg,
+							   CaseTestExpr)));
 			if (IsA(acoerce->elemexpr, RelabelType) &&
 				IsA(((RelabelType *) acoerce->elemexpr)->arg, CaseTestExpr))
 				node = (Node *) acoerce->arg;
 			else
 				break;
 		}
-		else if (node && IsA(node, RelabelType))
+		else if (node && IsAIfCached(node, RelabelType))
 		{
 			/* We don't really expect this case, but may as well cope */
-			node = (Node *) ((RelabelType *) node)->arg;
+			node = (Node *) castNodeIfCached(RelabelType, node)->arg;
 		}
 		else
 			break;
@@ -1950,6 +1954,9 @@ scalararraysel(PlannerInfo *root,
 	else if (oprsel == F_NEQSEL || oprsel == F_NEQJOINSEL)
 		isInequality = true;
 
+	/* Cached expressions are not used during estimation */
+	Assert(!IsA(rightop, CachedExpr));
+
 	/*
 	 * We consider three cases:
 	 *
@@ -2192,10 +2199,10 @@ estimate_array_length(Node *arrayexpr)
 		arrayval = DatumGetArrayTypeP(arraydatum);
 		return ArrayGetNItems(ARR_NDIM(arrayval), ARR_DIMS(arrayval));
 	}
-	else if (arrayexpr && IsA(arrayexpr, ArrayExpr) &&
-			 !((ArrayExpr *) arrayexpr)->multidims)
+	else if (arrayexpr && IsAIfCached(arrayexpr, ArrayExpr) &&
+			 !castNodeIfCached(ArrayExpr, arrayexpr)->multidims)
 	{
-		return list_length(((ArrayExpr *) arrayexpr)->elements);
+		return list_length(castNodeIfCached(ArrayExpr, arrayexpr)->elements);
 	}
 	else
 	{
@@ -3026,7 +3033,7 @@ mergejoinscansel(PlannerInfo *root, Node *clause,
 	*leftend = *rightend = 1.0;
 
 	/* Deconstruct the merge clause */
-	if (!is_opclause(clause))
+	if (!is_opclause(clause, false))
 		return;					/* shouldn't happen */
 	opno = ((OpExpr *) clause)->opno;
 	left = get_leftop((Expr *) clause);
@@ -4787,7 +4794,7 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 	vardata->vartype = exprType(node);
 
 	/* Look inside any binary-compatible relabeling */
-
+	/* Do not check for cached expressions because they do not contain vars */
 	if (IsA(node, RelabelType))
 		basenode = (Node *) ((RelabelType *) node)->arg;
 	else
@@ -4893,6 +4900,10 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 					if (indexpr_item == NULL)
 						elog(ERROR, "too few entries in indexprs list");
 					indexkey = (Node *) lfirst(indexpr_item);
+
+					/* Index expressions can only contain immutable functions */
+					Assert(!IsA(indexkey, CachedExpr));
+
 					if (indexkey && IsA(indexkey, RelabelType))
 						indexkey = (Node *) ((RelabelType *) indexkey)->arg;
 					if (equal(node, indexkey))
@@ -6571,6 +6582,9 @@ deconstruct_indexquals(IndexPath *path)
 		qinfo->rinfo = rinfo;
 		qinfo->indexcol = indexcol;
 
+		/* This is not used for pseudoconstants */
+		Assert(!IsA(clause, CachedExpr));
+
 		if (IsA(clause, OpExpr))
 		{
 			qinfo->clause_op = ((OpExpr *) clause)->opno;
@@ -6738,6 +6752,9 @@ genericcostestimate(PlannerInfo *root,
 	{
 		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
 
+		/* This is not used for pseudoconstants */
+		Assert(!IsA(rinfo->clause, CachedExpr));
+
 		if (IsA(rinfo->clause, ScalarArrayOpExpr))
 		{
 			ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause;
@@ -7005,6 +7022,9 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				break;			/* no quals at all for indexcol */
 		}
 
+		/* This is not used for pseudoconstants */
+		Assert(!IsA(clause, CachedExpr));
+
 		if (IsA(clause, ScalarArrayOpExpr))
 		{
 			int			alength = estimate_array_length(qinfo->other_operand);
@@ -7543,6 +7563,9 @@ gincost_opexpr(PlannerInfo *root,
 	/* aggressively reduce to a constant, and look through relabeling */
 	operand = estimate_expression_value(root, operand);
 
+	/* cached expressions are not used during estimation */
+	Assert(!IsA(operand, CachedExpr));
+
 	if (IsA(operand, RelabelType))
 		operand = (Node *) ((RelabelType *) operand)->arg;
 
@@ -7606,6 +7629,9 @@ gincost_scalararrayopexpr(PlannerInfo *root,
 	/* aggressively reduce to a constant, and look through relabeling */
 	rightop = estimate_expression_value(root, rightop);
 
+	/* cached expressions are not used during estimation */
+	Assert(!IsA(rightop, CachedExpr));
+
 	if (IsA(rightop, RelabelType))
 		rightop = (Node *) ((RelabelType *) rightop)->arg;
 
@@ -7875,7 +7901,10 @@ gincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 		}
 		else
 		{
-			/* shouldn't be anything else for a GIN index */
+			/*
+			 * Shouldn't be anything else (including cached expressions) for a
+			 * GIN index
+			 */
 			elog(ERROR, "unsupported GIN indexqual type: %d",
 				 (int) nodeTag(clause));
 		}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 265b1db..55b7edc 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1243,6 +1243,7 @@ intervaltypmodleastfield(int32 typmod)
 Datum
 interval_transform(PG_FUNCTION_ARGS)
 {
+	/* this is not used for cached expressions */
 	FuncExpr   *expr = castNode(FuncExpr, PG_GETARG_POINTER(0));
 	Node	   *ret = NULL;
 	Node	   *typmod;
diff --git a/src/backend/utils/adt/varbit.c b/src/backend/utils/adt/varbit.c
index 6ba400b..6b04700 100644
--- a/src/backend/utils/adt/varbit.c
+++ b/src/backend/utils/adt/varbit.c
@@ -680,6 +680,7 @@ varbit_send(PG_FUNCTION_ARGS)
 Datum
 varbit_transform(PG_FUNCTION_ARGS)
 {
+	/* this is not used for cached expressions */
 	FuncExpr   *expr = castNode(FuncExpr, PG_GETARG_POINTER(0));
 	Node	   *ret = NULL;
 	Node	   *typmod;
diff --git a/src/backend/utils/adt/varchar.c b/src/backend/utils/adt/varchar.c
index 8f07b1e..6c3524f 100644
--- a/src/backend/utils/adt/varchar.c
+++ b/src/backend/utils/adt/varchar.c
@@ -554,6 +554,7 @@ varcharsend(PG_FUNCTION_ARGS)
 Datum
 varchar_transform(PG_FUNCTION_ARGS)
 {
+	/* this is not used for cached expressions */
 	FuncExpr   *expr = castNode(FuncExpr, PG_GETARG_POINTER(0));
 	Node	   *ret = NULL;
 	Node	   *typmod;
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index d5984b4..09a9810 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -230,18 +230,20 @@ get_expr_result_type(Node *expr,
 {
 	TypeFuncClass result;
 
-	if (expr && IsA(expr, FuncExpr))
-		result = internal_get_result_type(((FuncExpr *) expr)->funcid,
-										  expr,
-										  NULL,
-										  resultTypeId,
-										  resultTupleDesc);
-	else if (expr && IsA(expr, OpExpr))
-		result = internal_get_result_type(get_opcode(((OpExpr *) expr)->opno),
-										  expr,
-										  NULL,
-										  resultTypeId,
-										  resultTupleDesc);
+	if (expr && IsAIfCached(expr, FuncExpr))
+		result = internal_get_result_type(
+							castNodeIfCached(FuncExpr, expr)->funcid,
+							expr,
+							NULL,
+							resultTypeId,
+							resultTupleDesc);
+	else if (expr && IsAIfCached(expr, OpExpr))
+		result = internal_get_result_type(
+							get_opcode(castNodeIfCached(OpExpr, expr)->opno),
+							expr,
+							NULL,
+							resultTypeId,
+							resultTupleDesc);
 	else
 	{
 		/* handle as a generic expression; no chance to resolve RECORD */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 57bd52f..a34b3b9 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -88,4 +88,6 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
 
+extern CachedExpr *makeCachedExpr(CacheableExpr *subexpr);
+
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index ed854fd..a4f2600 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -17,9 +17,6 @@
 #include "access/htup.h"
 #include "nodes/relation.h"
 
-#define is_opclause(clause)		((clause) != NULL && IsA(clause, OpExpr))
-#define is_funcclause(clause)	((clause) != NULL && IsA(clause, FuncExpr))
-
 typedef struct
 {
 	int			numWindowFuncs; /* total number of WindowFuncs found */
@@ -27,21 +24,24 @@ typedef struct
 	List	  **windowFuncs;	/* lists of WindowFuncs for each winref */
 } WindowFuncLists;
 
+extern bool is_opclause(Node *clause, bool check_cachedexpr);
+extern bool is_funcclause(Node *clause, bool check_cachedexpr);
+
 extern Expr *make_opclause(Oid opno, Oid opresulttype, bool opretset,
 			  Expr *leftop, Expr *rightop,
 			  Oid opcollid, Oid inputcollid);
 extern Node *get_leftop(const Expr *clause);
 extern Node *get_rightop(const Expr *clause);
 
-extern bool not_clause(Node *clause);
+extern bool not_clause(Node *clause, bool check_cachedexpr);
 extern Expr *make_notclause(Expr *notclause);
 extern Expr *get_notclausearg(Expr *notclause);
 
-extern bool or_clause(Node *clause);
-extern Expr *make_orclause(List *orclauses);
+extern bool or_clause(Node *clause, bool check_cachedexpr);
+extern Expr *make_orclause(List *orclauses, bool try_to_cache);
 
-extern bool and_clause(Node *clause);
-extern Expr *make_andclause(List *andclauses);
+extern bool and_clause(Node *clause, bool check_cachedexpr);
+extern Expr *make_andclause(List *andclauses, bool try_to_cache);
 extern Node *make_and_qual(Node *qual1, Node *qual2);
 extern Expr *make_ands_explicit(List *andclauses);
 extern List *make_ands_implicit(Expr *clause);
@@ -88,4 +88,6 @@ extern Query *inline_set_returning_function(PlannerInfo *root,
 extern List *expand_function_arguments(List *args, Oid result_type,
 						  HeapTuple func_tuple);
 
+extern bool set_non_internal_cachedexprs_walker(Node *node, List **cachedexprs);
+
 #endif							/* CLAUSES_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index f963e0b..55b4751 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -2194,6 +2194,12 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
 
 				if (argmodes && argmodes[i] == PROARGMODE_INOUT)
 				{
+					/*
+					 * Do not worry about cached params because they are only
+					 * used in prepared statements, PL/pgSQL inline code blocks
+					 * cannot be used here and PL/pgSQL functions cannot be
+					 * inlined.
+					 */
 					if (IsA(n, Param))
 					{
 						Param	   *param = castNode(Param, n);
@@ -6064,6 +6070,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
 			ExecInitExprWithParams(expr->expr_simple_expr,
 								   econtext->ecxt_param_list_info,
 								   expr->expr_simple_cachedexprs);
+
 		expr->expr_simple_in_use = false;
 		expr->expr_simple_lxid = curlxid;
 		MemoryContextSwitchTo(oldcontext);
@@ -7579,7 +7586,12 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
 
 		/* Note: we don't bother labeling the expression tree with collation */
 
-		/* Detect whether we have a no-op (RelabelType) coercion */
+		/*
+		 * Detect whether we have a no-op (RelabelType) coercion.
+		 * Do not check for cached expressions because the parser does not use
+		 * them.
+		 */
+		Assert(!IsA(cast_expr, CachedExpr));
 		if (IsA(cast_expr, RelabelType) &&
 			((RelabelType *) cast_expr)->arg == (Expr *) placeholder)
 			cast_expr = NULL;
@@ -7801,7 +7813,13 @@ exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan)
 			/* If setrefs.c copied up a Const, no need to look further */
 			if (IsA(tle_expr, Const))
 				break;
-			/* Otherwise, it had better be a Param or an outer Var */
+			/*
+			 * Otherwise, it had better be a Param or an outer Var
+			 *
+			 * Do not worry about cached params because they are only used in
+			 * prepared statements, PL/pgSQL inline code blocks cannot be used
+			 * here and PL/pgSQL functions cannot be inlined.
+			 */
 			Assert(IsA(tle_expr, Param) ||(IsA(tle_expr, Var) &&
 										   ((Var *) tle_expr)->varno == OUTER_VAR));
 			/* Descend to the child node */
@@ -7867,6 +7885,7 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
 	/*
 	 * Top level of expression must be a simple FuncExpr or OpExpr.
 	 */
+	Assert(!IsA(expr->expr_simple_expr, CachedExpr));
 	if (IsA(expr->expr_simple_expr, FuncExpr))
 	{
 		FuncExpr   *fexpr = (FuncExpr *) expr->expr_simple_expr;
@@ -7901,9 +7920,19 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
 	{
 		Node	   *arg = (Node *) lfirst(lc);
 
-		/* A Param is OK, whether it's the target variable or not */
-		if (arg && IsA(arg, Param))
-			continue;
+		if (arg)
+		{
+			/*
+			 * Cached params are only used in prepared statements, PL/pgSQL
+			 * inline code blocks cannot be used here and PL/pgSQL functions
+			 * cannot be inlined.
+			 */
+			Assert(!IsACached(arg, Param));
+
+			/* A Param is OK, whether it's the target variable or not */
+			if (IsA(arg, Param))
+				continue;
+		}
 		/* Otherwise, argument expression must not reference target */
 		if (contains_target_param(arg, &target_dno))
 			return;
@@ -7921,6 +7950,13 @@ contains_target_param(Node *node, int *target_dno)
 {
 	if (node == NULL)
 		return false;
+
+	/*
+	 * Cached params are only used in prepared statements, PL/pgSQL inline code
+	 * blocks cannot be used here and PL/pgSQL functions cannot be inlined.
+	 */
+	Assert(!IsACached(node, Param));
+
 	if (IsA(node, Param))
 	{
 		Param	   *param = (Param *) node;
diff --git a/src/test/regress/expected/precalculate_stable_functions.out b/src/test/regress/expected/precalculate_stable_functions.out
new file mode 100644
index 0000000..6a81993
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -0,0 +1,6122 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+-- Functions testing
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Multidimensional ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+-- Array subscripting operations testing
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- FieldSelect expressions testing
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- RelabelType expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- CoerceViaIO expressions testing
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- RowCompareExpr testing
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- SQLValueFunction testing
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_name(current_role) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(current_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(session_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+(4 rows)
+
+SELECT x_stl2_name(current_catalog) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ regression
+ regression
+ regression
+ regression
+(4 rows)
+
+SELECT x_stl2_name(current_schema) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ public
+ public
+ public
+ public
+(4 rows)
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+-- Xml expressions testing
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+NOTICE:  s2 xml
+          x_stl2_xml          
+------------------------------
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+ <foo bar="bar">content</foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+NOTICE:  s2 xml
+          x_stl2_xml          
+------------------------------
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+ <foo>abc</foo><bar>123</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+NOTICE:  s2 xml
+                          x_stl2_xml                          
+--------------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+ <?xml version="1.0" standalone="yes"?><content>abc</content>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+NOTICE:  s2 text
+                       x_stl2_text                       
+---------------------------------------------------------
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+ <?xml version="1.0"?><book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+NOTICE:  s text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+NOTICE:  v text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+NOTICE:  v text xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+NOTICE:  v text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+NOTICE:  v xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+NOTICE:  v xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+NOTICE:  v xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+NOTICE:  v xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+      x_stl2_xml      
+----------------------
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+ <abc/><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+             x_stl2_xml              
+-------------------------------------
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+ <foo bar="bar"><bar>foo</bar></foo>
+(4 rows)
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+               x_stl2_xml                
+-----------------------------------------
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+ <foo>abc</foo><bar><bar>foo</bar></bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+NOTICE:  s2 xml
+             x_stl2_xml             
+------------------------------------
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+ <book><title>Manual</title></book>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 xml
+           x_stl2_xml            
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+NOTICE:  s text xml instruction content
+NOTICE:  s2 xml
+         x_stl2_xml          
+-----------------------------
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+ <?php echo "hello world";?>
+(4 rows)
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 xml
+                      x_stl2_xml                      
+------------------------------------------------------
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+ <?xml version="1.0" standalone="yes"?><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+NOTICE:  s2 text
+  x_stl2_text   
+----------------
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+ <bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 text
+           x_stl2_text           
+---------------------------------
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+ abc<foo>bar</foo><bar>foo</bar>
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- ROW() expressions with dropped columns testing
+ALTER TABLE wxyz DROP COLUMN z;
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      4
+(1 row)
+
+INSERT INTO x VALUES (5);
+SELECT simple();
+ simple 
+--------
+      5
+(1 row)
+
+ROLLBACK;
+-- Drop tables for testing
+DROP TABLE x;
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
diff --git a/src/test/regress/expected/precalculate_stable_functions_1.out b/src/test/regress/expected/precalculate_stable_functions_1.out
new file mode 100644
index 0000000..10f66d4
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions_1.out
@@ -0,0 +1,5768 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+-- Functions testing
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expression testing
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+       
+       
+       
+       
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  equal my_integer immutable
+NOTICE:  equal my_integer immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Multidimensional ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+ {{1,2},{3,4}}
+(4 rows)
+
+-- Array subscripting operations testing
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- FieldSelect expressions testing
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+NOTICE:  s2 composite_type
+ x_stl2_composite_type 
+-----------------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- RelabelType expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- CoerceViaIO expressions testing
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+NOTICE:  cast integer as my_integer volatile
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ my_integer 
+------------
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+ {(1),(2)}
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- ConvertRowtypeExpr testing
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+NOTICE:  s2 no_columns
+ x_stl2_no_columns 
+-------------------
+ ()
+ ()
+ ()
+ ()
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- RowCompareExpr testing
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- SQLValueFunction testing
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_name(current_role) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(current_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+ regress_testrol2
+(4 rows)
+
+SELECT x_stl2_name(session_user) FROM x;
+NOTICE:  s2 name
+   x_stl2_name    
+------------------
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+ regress_testrol1
+(4 rows)
+
+SELECT x_stl2_name(current_catalog) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ regression
+ regression
+ regression
+ regression
+(4 rows)
+
+SELECT x_stl2_name(current_schema) FROM x;
+NOTICE:  s2 name
+ x_stl2_name 
+-------------
+ public
+ public
+ public
+ public
+(4 rows)
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+-- Xml expressions testing
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FRO...
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   '<?xml version="1.0"?><content>abc</content>',
+          ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   DOCUMENT '<?xml version="1.0"?><book><title>Manual</title>...
+                   ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+ERROR:  unsupported XML feature
+LINE 2:   CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+                  ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS D...
+                              ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+-- NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+SELECT x_stl() FROM x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+     1
+(4 rows)
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- JOIN/ON clause testing
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+ x | y 
+---+---
+ 2 | 1
+ 3 | 1
+ 4 | 1
+ 2 | 2
+ 3 | 2
+ 4 | 2
+(6 rows)
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+NOTICE:  v2
+ x_vlt2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(1) FROM x;
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl()) FROM x;
+NOTICE:  s
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+NOTICE:  s2 boolean
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+NOTICE:  s2 boolean
+NOTICE:  equal booleans immutable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl() ==== 1 FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal booleans stable strict
+ equal_booleans_stl_strict 
+---------------------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ equal_my_integer_stl 
+----------------------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+ nullif 
+--------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+NOTICE:  equal integers volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+NOTICE:  equal my_integer volatile
+NOTICE:  equal my_integer volatile
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+NOTICE:  v array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+NOTICE:  v my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+NOTICE:  s array_integer
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+NOTICE:  s my_integer
+NOTICE:  equal my_integer stable
+NOTICE:  equal my_integer stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and boolean expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Mixed functions and ARRAY[] expressions testing
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+NOTICE:  v
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1}
+ {1}
+ {1}
+ {1}
+(4 rows)
+
+-- Mixed functions and array subscripting operations testing
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+NOTICE:  v array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+NOTICE:  v array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+NOTICE:  v wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+NOTICE:  v my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+NOTICE:  s my_integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+NOTICE:  v oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+NOTICE:  s oid
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and CoerceViaIO expressions testing
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+NOTICE:  v text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+NOTICE:  s text integer
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+NOTICE:  v text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+NOTICE:  s text my_integer
+NOTICE:  s2 my_integer
+ x_stl2_my_integer 
+-------------------
+ (1)
+ (1)
+ (1)
+ (1)
+(4 rows)
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+NOTICE:  v array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+NOTICE:  s array_integer
+NOTICE:  s2 array_oid
+ x_stl2_array_oid 
+------------------
+ {2,3}
+ {2,3}
+ {2,3}
+ {2,3}
+(4 rows)
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  cast my_integer as integer volatile
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+NOTICE:  cast my_integer as integer stable
+NOTICE:  cast my_integer as integer stable
+NOTICE:  s2 array_integer
+ x_stl2_array_integer 
+----------------------
+ {1,2}
+ {1,2}
+ {1,2}
+ {1,2}
+(4 rows)
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+NOTICE:  v array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_vlt_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+NOTICE:  s array_integer
+NOTICE:  cast integer as my_integer stable
+NOTICE:  cast integer as my_integer stable
+ x_stl_array_integer 
+---------------------
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+ {(2),(3)}
+(4 rows)
+
+-- Mixed functions and ConvertRowtypeExpr testing
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+NOTICE:  v wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+NOTICE:  s wxyz_child
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+NOTICE:  s wxyz_child2
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+ (1,{2},t,3)
+(4 rows)
+
+-- Mixed functions and CASE expressions testing
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v boolean
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and RowCompareExpr testing
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and COALESCE expressions testing
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+NOTICE:  v2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      2
+      2
+      2
+      2
+(4 rows)
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      3
+      3
+      3
+      3
+(4 rows)
+
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+NOTICE:  s
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+NOTICE:  v text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+NOTICE:  v text xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+NOTICE:  v xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  v xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_vlt_xml_content() line 4 at RETURN
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+ERROR:  unsupported XML feature
+LINE 1: SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+                                    ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+NOTICE:  s text xml
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+ERROR:  unsupported XML feature
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+NOTICE:  s xml
+ERROR:  unsupported XML feature
+LINE 1: SELECT '<bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT '<bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml() line 4 at RETURN
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+NOTICE:  s xml content
+ERROR:  unsupported XML feature
+LINE 1: SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+               ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+HINT:  You need to rebuild PostgreSQL using --with-libxml.
+QUERY:  SELECT 'abc<foo>bar</foo><bar>foo</bar>'::xml
+CONTEXT:  PL/pgSQL function x_stl_xml_content() line 4 at RETURN
+-- Mixed functions and NullTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+NOTICE:  v
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+NOTICE:  v wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+NOTICE:  s
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+NOTICE:  s wxyz
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- Mixed functions and BooleanTest expressions testing
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+NOTICE:  v boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+NOTICE:  s2 boolean
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SET track_functions TO DEFAULT;
+-- ROW() expressions with dropped columns testing
+ALTER TABLE wxyz DROP COLUMN z;
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- Mixed functions and ROW() expressions testing
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+NOTICE:  v
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+NOTICE:  s
+NOTICE:  s2 wxyz
+ x_stl2_wxyz 
+-------------
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+ (1,{2},t)
+(4 rows)
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+BEGIN;
+SELECT simple();
+ simple 
+--------
+      4
+(1 row)
+
+INSERT INTO x VALUES (5);
+SELECT simple();
+ simple 
+--------
+      5
+(1 row)
+
+ROLLBACK;
+-- Drop tables for testing
+DROP TABLE x;
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 16f979c..a5c6bf1 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -116,7 +116,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid c
 # ----------
 # Another group of parallel tests
 # ----------
-test: identity partition_join partition_prune reloptions hash_part indexing partition_aggregate
+test: identity partition_join partition_prune reloptions hash_part indexing partition_aggregate precalculate_stable_functions
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 42632be..eb47939 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -188,6 +188,7 @@ test: reloptions
 test: hash_part
 test: indexing
 test: partition_aggregate
+test: precalculate_stable_functions
 test: event_trigger
 test: fast_default
 test: stats
diff --git a/src/test/regress/sql/precalculate_stable_functions.sql b/src/test/regress/sql/precalculate_stable_functions.sql
new file mode 100644
index 0000000..23b1f04
--- /dev/null
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -0,0 +1,1932 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create types and tables for testing
+
+CREATE TYPE my_integer AS (value integer);
+CREATE TYPE composite_type AS (first integer, second integer[], third boolean);
+
+CREATE TABLE x (x integer);
+INSERT INTO x SELECT generate_series(1, 4) x;
+
+CREATE TABLE wxyz (w integer, x integer[], y boolean, z integer);
+CREATE TABLE wxyz_child () INHERITS (wxyz);
+CREATE TABLE wxyz_child2 (a integer, b integer) INHERITS (wxyz);
+
+CREATE TABLE no_columns ();
+CREATE TABLE no_columns_child () INHERITS (no_columns);
+CREATE TABLE no_columns_child2 (a integer, b integer) INHERITS (no_columns);
+
+-- Create volatile functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_my_integer (
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_array_integer (
+)
+RETURNS int[] VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_boolean (
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v boolean';
+  RETURN TRUE;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz (
+)
+RETURNS wxyz VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child (
+)
+RETURNS wxyz_child VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_wxyz_child2 (
+)
+RETURNS wxyz_child2 VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_oid (
+)
+RETURNS oid VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_my_integer (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_text_xml_instruction_content (
+)
+RETURNS text VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_xml (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt_xml_content (
+)
+RETURNS xml VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_vlt2 (
+  integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_vlt (
+  my_integer,
+  my_integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer volatile';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_vlt (
+  integer
+)
+RETURNS my_integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer volatile';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_vlt (
+  my_integer
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer volatile';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create stable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_my_integer (
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's my_integer';
+  RETURN '(1)'::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_array_integer (
+)
+RETURNS int[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's array_integer';
+  RETURN '{2, 3}'::integer[];
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz (
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child (
+)
+RETURNS wxyz_child STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child';
+  RETURN '(1, {2}, TRUE, 3)'::wxyz_child;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_wxyz_child2 (
+)
+RETURNS wxyz_child2 STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's wxyz_child2';
+  RETURN '(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_oid (
+)
+RETURNS oid STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's oid';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text integer';
+  RETURN 1::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_my_integer (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text my_integer';
+  RETURN '(1)'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml';
+  RETURN '<?xml version="1.0"?><book><title>Manual</title></book>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_text_xml_instruction_content (
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's text xml instruction content';
+  RETURN 'echo "hello world";'::text;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_xml (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml';
+  RETURN '<bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl_xml_content (
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's xml content';
+  RETURN 'abc<foo>bar</foo><bar>foo</bar>'::xml;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+  integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+  integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_integer (
+  integer[]
+)
+RETURNS integer[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_array_oid (
+  oid[]
+)
+RETURNS oid[] STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 array_oid';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_composite_type (
+  composite_type
+)
+RETURNS composite_type STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 composite_type';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_my_integer (
+  my_integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_no_columns (
+  no_columns
+)
+RETURNS no_columns STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 no_columns';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_name (
+  name
+)
+RETURNS name STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 name';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_xml (
+  xml
+)
+RETURNS xml STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 xml';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_text (
+  text
+)
+RETURNS text STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 text';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_stl (
+  my_integer,
+  my_integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer stable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_integer_as_my_integer_stl (
+  integer
+)
+RETURNS my_integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast integer as my_integer stable';
+  RETURN ROW($1)::my_integer;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.cast_my_integer_as_integer_stl (
+  my_integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'cast my_integer as integer stable';
+  RETURN $1.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.stable_max(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN (SELECT max(x) from x);
+END
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.simple(
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RETURN stable_max();
+END
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create immutable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+  integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+  integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_my_integer (
+  my_integer
+)
+RETURNS my_integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 my_integer';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_imm (
+  boolean,
+  boolean
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_my_integer_imm (
+  my_integer,
+  my_integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal my_integer immutable';
+  RETURN $1.value = $2.value;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create operators for testing
+
+CREATE OPERATOR === (
+  PROCEDURE = equal_integers_vlt,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_integers_stl,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_integers_imm,
+  LEFTARG = integer,
+  RIGHTARG = integer
+);
+
+CREATE OPERATOR ==== (
+  PROCEDURE = equal_booleans_stl_strict,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+CREATE OPERATOR ===== (
+  PROCEDURE = equal_booleans_imm,
+  LEFTARG = boolean,
+  RIGHTARG = boolean
+);
+
+-- Functions testing
+
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+SELECT x_stl() FROM x;
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM x WHERE x_stl() < x;
+
+-- JOIN/ON clause testing
+
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+SELECT x_stl2(1) FROM x;
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2(x_stl()) FROM x;
+SELECT x_imm2(x_stl()) FROM x;
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+
+-- Operators testing
+
+SELECT 1 === 2 FROM x; -- should not be precalculated
+SELECT 1 ==== 2 FROM x;
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+SELECT x_stl() ==== 1 FROM x;
+
+-- IS (NOT) DISTINCT FROM expression testing
+
+-- create operator here because we will drop and reuse it several times
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IS DISTINCT FROM '(2)'::my_integer FROM x;
+SELECT '(1)'::my_integer IS NOT DISTINCT FROM '(2)'::my_integer FROM x;
+
+-- IS (NOT) DISTINCT FROM expressions with null arguments testing
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM 1) FROM x;
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM 1) FROM x;
+
+SELECT x_stl2_boolean(x_stl2(NULL) IS DISTINCT FROM x_stl2(NULL)) FROM x;
+SELECT x_stl2_boolean(x_stl2(NULL) IS NOT DISTINCT FROM x_stl2(NULL)) FROM x;
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- NULLIF expressions testing
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT NULLIF('(1)'::my_integer, '(2)'::my_integer) FROM x;
+
+-- NULLIF expressions with null arguments testing
+SELECT x_stl2(NULLIF(1, NULL)) FROM x;
+SELECT x_stl2(NULLIF(NULL::integer, NULL)) FROM x;
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions
+-- testing
+
+SELECT 1 === ANY ('{2, 3}') FROM x; -- should not be precalculated
+SELECT 1 === ALL ('{2, 3}') FROM x; -- should not be precalculated
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT 1 ==== ANY ('{2, 3}') FROM x;
+SELECT 1 ==== ALL ('{2, 3}') FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT 1 ===== ANY ('{2, 3}') FROM x;
+SELECT 1 ===== ALL ('{2, 3}') FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_imm,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- "scalar op ANY/ALL (array)" / "scalar IN (2 or more values)" expressions with
+-- null arguments testing
+SELECT 1 ==== ANY ('{2, NULL}') FROM x;
+SELECT x_stl2_boolean(1 ==== ANY (NULL)) FROM x;
+SELECT NULL ==== ANY ('{2, 3}'::integer[]) FROM x;
+SELECT NULL ==== ANY ('{2, NULL}'::integer[]) FROM x;
+SELECT x_stl2_boolean(NULL::integer ==== ANY (NULL)) FROM x;
+
+SELECT 1 ==== ALL ('{2, NULL}') FROM x;
+SELECT x_stl2_boolean(1 ==== ALL (NULL)) FROM x;
+SELECT NULL ==== ALL ('{2, 3}'::integer[]) FROM x;
+SELECT NULL ==== ALL ('{2, NULL}'::integer[]) FROM x;
+SELECT x_stl2_boolean(NULL::integer ==== ALL (NULL)) FROM x;
+
+SELECT x_stl2_boolean(1 IN (2, NULL)) FROM x;
+SELECT x_stl2_boolean(NULL IN (2, 3)) FROM x;
+SELECT x_stl2_boolean(NULL IN (2, NULL)) FROM x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x;
+
+-- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x;
+
+-- should not be precalculated
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x;
+
+-- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x;
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- Boolean expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+
+-- ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt(), 2]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[x_stl(), 2]) FROM x;
+
+-- Multidimensional ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[[x_vlt(), 2], [3, 4]]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[[x_stl(), 2], [3, 4]]) FROM x;
+
+-- Array subscripting operations testing
+
+SELECT x_stl2(('{1, 2}'::integer[])[1]) FROM x;
+SELECT x_stl2_array_integer(('{1, 2}'::integer[])[:]) FROM x;
+
+-- Mixed functions and array subscripting operations testing
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+
+-- FieldSelect expressions testing
+
+SELECT x_stl2(('(1, {2}, TRUE, 3)'::wxyz).w) FROM x;
+SELECT x_stl2(('(1)'::my_integer).value) FROM x;
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+
+-- ROW() expressions testing
+
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)) FROM x;
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE, 3)) FROM x;
+SELECT x_stl2_wxyz((1, '{2}', TRUE, 3)::wxyz) FROM x;
+
+SELECT x_stl2_composite_type((1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_composite_type(ROW(1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_composite_type((1, '{2}', TRUE)::composite_type) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+
+-- RelabelType expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_oid()::integer) FROM x;
+
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+
+-- CoerceViaIO expressions testing
+
+SELECT x_stl2_my_integer('(1)'::text::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+
+-- Mixed functions and CoerceViaIO expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+
+-- ArrayCoerce expressions testing
+
+-- Binary-coercible types:
+
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_vlt;
+
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x; -- should not be precalculated
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+SELECT '{1, 2}'::integer[]::my_integer[] FROM x;
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Not binary-coercible types:
+-- create cast here because we will drop and reuse it several times
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+
+-- ConvertRowtypeExpr testing
+
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3)'::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz('(1, {2}, TRUE, 3, 4, 5)'::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_no_columns('()'::no_columns_child::no_columns) FROM x;
+SELECT x_stl2_no_columns('(1, 2)'::no_columns_child2::no_columns) FROM x;
+
+-- Mixed functions and ConvertRowtypeExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+-- CASE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+
+-- RowCompareExpr testing
+
+SELECT x_stl2_boolean((1, 2) < (1, 3)) FROM x;
+
+-- Mixed functions and RowCompareExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+
+-- COALESCE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+
+-- GREATEST and LEAST functions testing
+
+SELECT x_stl2(GREATEST(2, 1, 3)) FROM x;
+SELECT x_stl2(LEAST(2, 1, 3)) FROM x;
+
+-- Mixed functions and GREATEST and LEAST functions testing
+
+-- should not be precalculated
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x;
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+
+-- SQLValueFunction testing
+
+CREATE ROLE regress_testrol2 SUPERUSER;
+CREATE ROLE regress_testrol1 SUPERUSER LOGIN IN ROLE regress_testrol2;
+
+\c -
+SET SESSION AUTHORIZATION regress_testrol1;
+SET ROLE regress_testrol2;
+
+SELECT x_stl2_boolean(date(now()) = current_date) FROM x;
+
+SELECT x_stl2_boolean(now()::timetz = current_time) FROM x;
+SELECT x_stl2_boolean(now()::timetz(2) = current_time(2)) FROM x; -- precision
+
+SELECT x_stl2_boolean(now() = current_timestamp) FROM x;
+
+-- precision
+SELECT x_stl2_boolean(
+  length(current_timestamp::text) >= length(current_timestamp(0)::text)
+)
+FROM x;
+
+SELECT x_stl2_boolean(now()::time = localtime) FROM x;
+SELECT x_stl2_boolean(now()::time(2) = localtime(2)) FROM x; -- precision
+
+SELECT x_stl2_boolean(now()::timestamp = localtimestamp) FROM x;
+
+-- precision
+SELECT x_stl2_boolean(now()::timestamp(2) = localtimestamp(2)) FROM x;
+
+SELECT x_stl2_name(current_role) FROM x;
+SELECT x_stl2_name(current_user) FROM x;
+SELECT x_stl2_name(user) FROM x;
+SELECT x_stl2_name(session_user) FROM x;
+SELECT x_stl2_name(current_catalog) FROM x;
+SELECT x_stl2_name(current_schema) FROM x;
+
+\c
+DROP ROLE regress_testrol1, regress_testrol2;
+
+-- Xml expressions testing
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', '<bar>foo</bar>')) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), 'cont', 'ent')
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, 123 AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>'
+))
+FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT 'abc<foo>bar</foo><bar>foo</bar>')) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, 'echo "hello world";')) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(
+  '<?xml version="1.0"?><content>abc</content>',
+  version '1.0',
+  standalone yes
+))
+FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  DOCUMENT '<?xml version="1.0"?><book><title>Manual</title></book>' AS text
+))
+FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(
+  CONTENT 'abc<foo>bar</foo><bar>foo</bar>' AS text
+))
+FROM x;
+
+SELECT x_stl2_boolean('abc<foo>bar</foo><bar>foo</bar>' IS DOCUMENT) FROM x;
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+
+-- NullTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+
+-- BooleanTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+
+-- Tracking functions testing
+
+SET track_functions TO 'all';
+
+-- Simple functions testing
+SELECT x_vlt() FROM x; -- should not be precalculated
+SELECT x_stl() FROM x;
+
+-- WHERE clause testing
+SELECT x_vlt() FROM x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM x WHERE x_stl() < x;
+
+-- JOIN/ON clause testing
+
+-- should not be precalculated
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_vlt() < x;
+
+SELECT * FROM x JOIN generate_series(1, 2) y ON x_stl() < x;
+
+-- Functions with constant arguments testing
+SELECT x_vlt2(1) FROM x; -- should not be precalculated
+SELECT x_stl2(1) FROM x;
+
+-- Nested functions testing
+SELECT x_stl2(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2(x_stl()) FROM x;
+SELECT x_imm2(x_stl()) FROM x;
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM x;
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM x;
+
+-- Operators testing
+SELECT 1 === 2 FROM x; -- should not be precalculated
+SELECT 1 ==== 2 FROM x;
+
+-- Strict operators testing
+SELECT x_stl2_boolean(NULL) ==== TRUE FROM x;
+SELECT x_stl2_boolean(NULL) ===== TRUE FROM x;
+
+-- Mixed functions and operators testing
+SELECT x_stl2_boolean(1 === 2) FROM x; -- should not be precalculated
+SELECT x_stl2_boolean(1 ==== 2) FROM x;
+
+SELECT x_vlt() ==== 1 FROM x; -- should not be precalculated
+SELECT x_stl() ==== 1 FROM x;
+
+-- Mixed functions and IS (NOT) DISTINCT FROM expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_booleans_stl_strict(
+  ('(1)'::my_integer IS DISTINCT FROM '(1)'::my_integer),
+  ('(1)'::my_integer IS NOT DISTINCT FROM '(1)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- should not be precalculated
+SELECT (x_vlt_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+SELECT (x_stl_my_integer() IS DISTINCT FROM '(1)'::my_integer) FROM x;
+SELECT (x_stl_my_integer() IS NOT DISTINCT FROM '(1)'::my_integer) FROM x;
+
+-- Mixed functions and NULLIF expressions testing
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT equal_my_integer_stl(
+  NULLIF('(1)'::my_integer, '(2)'::my_integer),
+  NULLIF('(2)'::my_integer, '(2)'::my_integer)
+)
+FROM x;
+
+-- should not be precalculated
+SELECT NULLIF(x_vlt_my_integer(), '(2)'::my_integer) FROM x;
+
+SELECT NULLIF(x_stl_my_integer(), '(2)'::my_integer) FROM x;
+
+-- Mixed functions and "scalar op ANY/ALL (array)" / "scalar IN (2 or more
+-- values)" expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ANY ('{2, 3}')) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(1 === ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_vlt,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+-- should not be precalculated
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_stl2_boolean(1 ==== ANY ('{2, 3}')) FROM x;
+SELECT x_stl2_boolean(1 ==== ALL ('{2, 3}')) FROM x;
+
+DROP OPERATOR = (my_integer, my_integer);
+CREATE OPERATOR = (
+  PROCEDURE = equal_my_integer_stl,
+  LEFTARG = my_integer,
+  RIGHTARG = my_integer
+);
+
+SELECT x_stl2_boolean(
+  '(1)'::my_integer IN ('(2)'::my_integer, '(3)'::my_integer)
+)
+FROM x;
+
+SELECT x_vlt() ==== ANY ('{2, 3}') FROM x; -- should not be precalculated
+SELECT x_vlt() ==== ALL ('{2, 3}') FROM x; -- should not be precalculated
+
+SELECT 1 ==== ANY (x_vlt_array_integer()) FROM x; -- should not be precalculated
+SELECT 1 ==== ALL (x_vlt_array_integer()) FROM x; -- should not be precalculated
+
+-- should not be precalculated
+SELECT x_vlt_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+SELECT x_stl() ==== ANY ('{2, 3}') FROM x;
+SELECT x_stl() ==== ALL ('{2, 3}') FROM x;
+
+SELECT 1 ==== ANY (x_stl_array_integer()) FROM x;
+SELECT 1 ==== ALL (x_stl_array_integer()) FROM x;
+
+SELECT x_stl_my_integer() IN ('(2)'::my_integer, '(3)'::my_integer) FROM x;
+
+-- Mixed functions and boolean expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() AND x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() OR x_stl2_boolean(TRUE)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(NOT x_vlt_boolean()) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) AND x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) OR x_stl2_boolean(TRUE)) FROM x;
+SELECT x_stl2_boolean(NOT x_stl2_boolean(TRUE)) FROM x;
+
+-- Mixed functions and ARRAY[] expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_array_integer(ARRAY[x_vlt()]) FROM x;
+
+SELECT x_stl2_array_integer(ARRAY[x_stl()]) FROM x;
+
+-- Mixed functions and array subscripting operations testing
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[x_vlt()]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2((x_vlt_array_integer())[1]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer((x_vlt_array_integer())[:]) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(('{1, 2}'::integer[])[x_vlt()]) FROM x;
+
+SELECT x_stl2((x_stl_array_integer())[x_stl()]) FROM x;
+SELECT x_stl2((x_stl_array_integer())[1]) FROM x;
+SELECT x_stl2_array_integer((x_stl_array_integer())[:]) FROM x;
+SELECT x_stl2(('{1, 2}'::integer[])[x_stl()]) FROM x;
+
+-- Mixed functions and FieldSelect expressions testing
+SELECT x_stl2((x_vlt_wxyz()).w) FROM x; -- should not be precalculated
+SELECT x_stl2((x_vlt_my_integer()).value) FROM x; -- should not be precalculated
+
+SELECT x_stl2((x_stl_wxyz()).w) FROM x;
+SELECT x_stl2((x_stl_my_integer()).value) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE, 3)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE, 3)) FROM x;
+
+-- Mixed functions and RelabelType expressions testing
+SELECT x_stl2(x_vlt_oid()::integer) FROM x; -- should not be precalculated
+SELECT x_stl2(x_stl_oid()::integer) FROM x;
+
+-- Mixed functions and CoerceViaIO expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(x_vlt_text_integer()::integer) FROM x;
+
+SELECT x_stl2(x_stl_text_integer()::integer) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_my_integer(x_vlt_text_my_integer()::my_integer) FROM x;
+
+SELECT x_stl2_my_integer(x_stl_text_my_integer()::my_integer) FROM x;
+
+-- Mixed functions and ArrayCoerce expressions testing
+-- Binary-coercible types:
+
+-- should not be precalculated
+SELECT x_stl2_array_oid(x_vlt_array_integer()::oid[]) FROM x;
+
+SELECT x_stl2_array_oid(x_stl_array_integer()::oid[]) FROM x;
+
+-- Not binary-coercible types:
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_vlt;
+
+-- should not be precalculated
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (my_integer AS integer);
+CREATE CAST (my_integer AS integer)
+  WITH FUNCTION cast_my_integer_as_integer_stl;
+
+SELECT x_stl2_array_integer('{(1), (2)}'::my_integer[]::integer[]) FROM x;
+
+DROP CAST (integer AS my_integer);
+CREATE CAST (integer AS my_integer)
+  WITH FUNCTION cast_integer_as_my_integer_stl;
+
+-- should not be precalculated
+SELECT x_vlt_array_integer()::my_integer[] FROM x;
+
+SELECT x_stl_array_integer()::my_integer[] FROM x;
+
+-- Mixed functions and ConvertRowtypeExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child()::wxyz_child::wxyz) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_wxyz(x_vlt_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+SELECT x_stl2_wxyz(x_stl_wxyz_child()::wxyz_child::wxyz) FROM x;
+SELECT x_stl2_wxyz(x_stl_wxyz_child2()::wxyz_child2::wxyz) FROM x;
+
+-- Mixed functions and CASE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(CASE WHEN x_vlt_boolean() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2(CASE x_vlt() WHEN x_vlt() THEN x_vlt() ELSE x_vlt() END) FROM x;
+
+SELECT x_stl2(CASE WHEN x_stl2_boolean(TRUE) THEN x_stl() ELSE x_stl() END)
+FROM x;
+
+SELECT x_stl2(CASE x_stl() WHEN x_stl() THEN x_stl() ELSE x_stl() END) FROM x;
+
+-- Mixed functions and RowCompareExpr testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean((x_vlt(), 2) < (1, 3)) FROM x;
+
+SELECT x_stl2_boolean((x_stl(), 2) < (1, 3)) FROM x;
+
+-- Mixed functions and COALESCE expressions testing
+
+-- should not be precalculated
+SELECT x_stl2(COALESCE(NULL, x_vlt2(NULL), 2)) FROM x;
+
+SELECT x_stl2(COALESCE(NULL, x_stl2(NULL), 2)) FROM x;
+
+-- Mixed functions and GREATEST and LEAST functions testing
+SELECT x_stl2(GREATEST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+SELECT x_stl2(LEAST(2, x_vlt(), 3)) FROM x; -- should not be precalculated
+
+SELECT x_stl2(GREATEST(2, x_stl(), 3)) FROM x;
+SELECT x_stl2(LEAST(2, x_stl(), 3)) FROM x;
+
+-- Mixed functions and Xml expressions testing
+-- should not be precalculated
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_vlt_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_vlt_xml())
+)
+FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_vlt_xml() AS bar)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_vlt_text_xml())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_vlt_text_xml_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLPI(name php, x_vlt_text_xml_instruction_content())) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_xml(XMLROOT(x_vlt_xml(), version '1.0', standalone yes)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_vlt_xml() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_vlt_xml_content() AS text)) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_xml_content() IS DOCUMENT) FROM x;
+
+SELECT x_stl2_xml(XMLCONCAT('<abc/>', x_stl_xml())) FROM x;
+
+SELECT x_stl2_xml(
+  XMLELEMENT(name foo, xmlattributes('bar' as bar), x_stl_xml())
+)
+FROM x;
+
+SELECT x_stl2_xml(XMLFOREST('abc' AS foo, x_stl_xml() AS bar)) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(DOCUMENT x_stl_text_xml())) FROM x;
+
+SELECT x_stl2_xml(XMLPARSE(CONTENT x_stl_text_xml_content())) FROM x;
+
+SELECT x_stl2_xml(XMLPI(name php, x_stl_text_xml_instruction_content())) FROM x;
+
+SELECT x_stl2_xml(XMLROOT(x_stl_xml(), version '1.0', standalone yes)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(DOCUMENT x_stl_xml() AS text)) FROM x;
+
+SELECT x_stl2_text(XMLSERIALIZE(CONTENT x_stl_xml_content() AS text)) FROM x;
+
+SELECT x_stl2_boolean(x_stl_xml_content() IS DOCUMENT) FROM x;
+
+-- Mixed functions and NullTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt() IS NOT NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NULL) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_wxyz() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl() IS NOT NULL) FROM x;
+
+SELECT x_stl2_boolean(x_stl_wxyz() IS NULL) FROM x;
+SELECT x_stl2_boolean(x_stl_wxyz() IS NOT NULL) FROM x;
+
+-- Mixed functions and BooleanTest expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT TRUE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT FALSE) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS UNKNOWN) FROM x;
+
+-- should not be precalculated
+SELECT x_stl2_boolean(x_vlt_boolean() IS NOT UNKNOWN) FROM x;
+
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT TRUE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(TRUE) IS NOT FALSE) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS UNKNOWN) FROM x;
+SELECT x_stl2_boolean(x_stl2_boolean(NULL) IS NOT UNKNOWN) FROM x;
+
+SET track_functions TO DEFAULT;
+
+-- ROW() expressions with dropped columns testing
+
+ALTER TABLE wxyz DROP COLUMN z;
+
+-- Update some functions
+CREATE OR REPLACE FUNCTION public.x_stl2_wxyz (
+  wxyz
+)
+RETURNS wxyz STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 wxyz';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- ROW() expressions testing
+SELECT x_stl2_wxyz((1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_wxyz(ROW(1, '{2}', TRUE)) FROM x;
+SELECT x_stl2_wxyz((1, '{2}', TRUE)::wxyz) FROM x;
+
+-- Mixed functions and ROW() expressions testing
+
+-- should not be precalculated
+SELECT x_stl2_wxyz((x_vlt(), '{2}', TRUE)) FROM x;
+
+SELECT x_stl2_wxyz((x_stl(), '{2}', TRUE)) FROM x;
+
+-- PL/pgSQL Simple expressions
+-- Make sure precalculated stable functions can't be simple expressions: these
+-- expressions are only initialized once per transaction and then executed
+-- multiple times.
+
+BEGIN;
+SELECT simple();
+INSERT INTO x VALUES (5);
+SELECT simple();
+ROLLBACK;
+
+-- Drop tables for testing
+
+DROP TABLE x;
+
+DROP FUNCTION x_vlt_wxyz, x_vlt_wxyz_child, x_vlt_wxyz_child2;
+DROP FUNCTION x_stl_wxyz, x_stl_wxyz_child, x_stl_wxyz_child2, x_stl2_wxyz;
+DROP TABLE wxyz, wxyz_child, wxyz_child2;
+
+DROP FUNCTION x_stl2_no_columns;
+DROP TABLE no_columns, no_columns_child, no_columns_child2;
-- 
2.7.4

#39Michael Paquier
michael@paquier.xyz
In reply to: Marina Polyakova (#38)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hi Marina,

On Thu, May 24, 2018 at 04:00:33PM +0300, Marina Polyakova wrote:

Here there's a 9-th version of the patches for the precalculation of stable
or immutable functions, stable or immutable operators and other nonvolatile
expressions. This is a try to execute cached expressions as PARAM_EXEC,
thanks to the comments of Tom Lane and Andres Freund [1].

Please note that v9-0004 fails to apply, so a rebase is needed. This
patch is moved to next CF, waiting on author.
--
Michael

#40Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Michael Paquier (#39)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

On Tue, Oct 2, 2018 at 4:22 AM Michael Paquier <michael@paquier.xyz> wrote:

On Thu, May 24, 2018 at 04:00:33PM +0300, Marina Polyakova wrote:

Here there's a 9-th version of the patches for the precalculation of stable
or immutable functions, stable or immutable operators and other nonvolatile
expressions. This is a try to execute cached expressions as PARAM_EXEC,
thanks to the comments of Tom Lane and Andres Freund [1].

Please note that v9-0004 fails to apply, so a rebase is needed. This
patch is moved to next CF, waiting on author.

Unfortunately, patch still has some conflicts, could you please post an updated
version?

#41Andres Freund
andres@anarazel.de
In reply to: Dmitry Dolgov (#40)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

On 2018-11-29 18:00:15 +0100, Dmitry Dolgov wrote:

On Tue, Oct 2, 2018 at 4:22 AM Michael Paquier <michael@paquier.xyz> wrote:

On Thu, May 24, 2018 at 04:00:33PM +0300, Marina Polyakova wrote:

Here there's a 9-th version of the patches for the precalculation of stable
or immutable functions, stable or immutable operators and other nonvolatile
expressions. This is a try to execute cached expressions as PARAM_EXEC,
thanks to the comments of Tom Lane and Andres Freund [1].

Please note that v9-0004 fails to apply, so a rebase is needed. This
patch is moved to next CF, waiting on author.

Unfortunately, patch still has some conflicts, could you please post an updated
version?

As nothing has happened since, I'm marking this as returned with
feedback.

Greetings,

Andres Freund

#42David Geier
geidav.pg@gmail.com
In reply to: Andres Freund (#41)
Re: WIP Patch: Precalculate stable functions, infrastructure v1

Hi hackers!

I would like to revive this thread. At ServiceNow we recurringly encounter
queries that are much slower than they would have to be, because of
frequent calls to uncached stable functions with constant arguments (mostly
to_date()). We've seen e.g. queries that get more than 8x faster by
temporarily changing to_date() from stable to immutable.

I would be glad to help bringing this effort forward. Was there more work
on the patch left than rebasing on latest master?
@Marina: do you have any plans to continue with this?

For reference here are all existing mailing list discussions I could find
on this topic:

- [WIP] Caching constant stable expressions per execution (Marti, 2011),
/messages/by-id/CABRT9RC-1wGxZC_Z5mwkdk70fgY2DRX3sLXzdP4voBKuKPZDow@mail.gmail.com
- Caching for stable expressions with constant arguments v6 (Marti, 2012),
/messages/by-id/CABRT9RA-RomVS-yzQ2wUtZ=m-eV61LcbrL1P1J3jydPStTfc6Q@mail.gmail.com
- WIP Patch: Precalculate stable functions (Marina, 2017),
/messages/by-id/ba261b9fc25dea4069d8ba9a8fcadf35@postgrespro.ru
- WIP Patch: Precalculate stable functions, infrastructure v1 (Marina,
2017),
/messages/by-id/da87bb6a014e029176a04f6e50033cfb@postgrespro.ru

--
David Geier
(ServiceNow)

On Mon, 23 May 2022 at 17:06, Andres Freund <andres@anarazel.de> wrote:

Show quoted text

On 2018-11-29 18:00:15 +0100, Dmitry Dolgov wrote:

On Tue, Oct 2, 2018 at 4:22 AM Michael Paquier <michael@paquier.xyz>

wrote:

On Thu, May 24, 2018 at 04:00:33PM +0300, Marina Polyakova wrote:

Here there's a 9-th version of the patches for the precalculation of

stable

or immutable functions, stable or immutable operators and other

nonvolatile

expressions. This is a try to execute cached expressions as

PARAM_EXEC,

thanks to the comments of Tom Lane and Andres Freund [1].

Please note that v9-0004 fails to apply, so a rebase is needed. This
patch is moved to next CF, waiting on author.

Unfortunately, patch still has some conflicts, could you please post an

updated

version?

As nothing has happened since, I'm marking this as returned with
feedback.

Greetings,

Andres Freund