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

