Sorry, attached patch.

-------- Исходное сообщение --------
Тема: WIP Patch: Precalculate stable functions
Дата: 20-04-2017 19:56
От: Marina Polyakova <m.polyak...@postgrespro.ru>
Кому: pgsql-hackers@postgresql.org

Hello everyone!

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.

There's a proposal to precalculate stable and immutable functions (= calculate once for all output rows, but as many times as function is mentioned in query), if they don't return a set and their arguments are constants or recursively precalculated functions. The same for operators' functions, strict functions, tracking functions. It can be very effective, for example, there's a comparison for full text search in messages (Intel® Core™ i5-6500 CPU @ 3.20GHz × 4, RAM 8Gb):

Without precalculation:

EXPLAIN (ANALYZE TRUE, BUFFERS TRUE) SELECT COUNT(*) FROM messages WHERE body_tsvector @@ to_tsquery('postgres'); QUERY PLAN

------------------------------------------------------------------------------------------------------
------------------------------------
Aggregate (cost=18714.82..18714.83 rows=1 width=8) (actual time=2275.334..2275.334 rows=1 loops=1)
   Buffers: shared hit=309234 read=184261
-> Bitmap Heap Scan on messages (cost=66.93..18702.34 rows=4991 width=0) (actual time=70.661..224
7.462 rows=151967 loops=1)
         Recheck Cond: (body_tsvector @@ to_tsquery('postgres'::text))
         Rows Removed by Index Recheck: 118531
         Heap Blocks: exact=56726 lossy=33286
         Buffers: shared hit=309234 read=184261
-> Bitmap Index Scan on message_body_idx (cost=0.00..65.68 rows=4991 width=0) (actual time=
54.599..54.599 rows=151967 loops=1)
Index Cond: (body_tsvector @@ to_tsquery('postgres'::text))
               Buffers: shared hit=1 read=37
 Planning time: 0.493 ms
 Execution time: 2276.412 ms
(12 rows)

With precalculation:

EXPLAIN (ANALYZE TRUE, BUFFERS TRUE) SELECT COUNT(*) FROM messages WHERE body_tsvector @@ to_tsquery('postgres'); QUERY PLAN

------------------------------------------------------------------------------------------------------
----------------------------------------
Aggregate (cost=192269.70..192269.71 rows=1 width=8) (actual time=1458.679..1458.680 rows=1 loops=1)
   Buffers: shared hit=309234 read=184261
-> Bitmap Heap Scan on messages (cost=1445.68..191883.51 rows=154474 width=0) (actual time=70.069
..1433.999 rows=151967 loops=1)
         Recheck Cond: (body_tsvector @@ to_tsquery('postgres'::text))
         Rows Removed by Index Recheck: 118531
         Heap Blocks: exact=56726 lossy=33286
         Buffers: shared hit=309234 read=184261
-> Bitmap Index Scan on message_body_idx (cost=0.00..1406.81 rows=154474 width=0) (actual t
ime=56.149..56.149 rows=151967 loops=1)
Index Cond: (body_tsvector @@ to_tsquery('postgres'::text))
               Buffers: shared hit=1 read=37
 Planning time: 1.644 ms
 Execution time: 1459.836 ms
(12 rows)

Patch is attached. It isn't done yet:
- changing documentation (partly because of next lines);
- precalculation of expressions IS DISTINCT FROM and NULLIF which use nonvolatile equality operators; - precalculation of expressions "scalar op ANY/ALL (array)" which use nonvolatile operators; - precalculation of row compare expressions which use nonvolatile operators.

--
Marina Polyakova
Postgres Professional: http://www.postgrespro.com
+7 926 92 00 265
From 46d590281129083d524751805797ef0a3c386df0 Mon Sep 17 00:00:00 2001
From: Marina Polyakova <m.polyak...@postgrespro.ru>
Date: Thu, 20 Apr 2017 19:23:05 +0300
Subject: [PATCH 2/2] Precalculate stable functions

Now in Postgresql only immutable functions are precalculated; stable functions
are calculated for every row so in fact they don't differ from volatile
functions.

In this patch function / operator is precalculated if:
1) it doesn't return set,
2) it's not volatile itself,
3) its arguments are constants or nonvolatile too (functions or operators).
Costs are changed to reflect the changed behaviour.
---
 src/backend/executor/execExpr.c                    |  41 ++
 src/backend/executor/execExprInterp.c              | 196 +++++-
 src/backend/optimizer/path/costsize.c              |  84 ++-
 src/include/fmgr.h                                 |   4 +
 src/include/nodes/primnodes.h                      |   2 +
 .../expected/precalculate_stable_functions.out     | 784 +++++++++++++++++++++
 src/test/regress/serial_schedule                   |   1 +
 .../regress/sql/precalculate_stable_functions.sql  | 240 +++++++
 8 files changed, 1321 insertions(+), 31 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 15d693f..8ba1dcf 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2061,6 +2061,22 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 	int			argno;
 	ListCell   *lc;
 
+	/*
+	 * Function is not volatile if:
+	 * 1) it doesn't return set,
+	 * 2) it's not volatile itself,
+	 * 3) its arguments are constants or nonvolatile too (functions or
+	 * operators).
+	 * 
+	 * Operator is not volatile if:
+	 * 1) its function doesn't return set,
+	 * 1) its function is not volatile itself,
+	 * 3) its arguments are constants or nonvolatile too (functions or
+	 * operators).
+	 */
+	bool		is_volatile;
+	bool		has_nonconst_or_volatile_input = false;
+
 	/* Check permission to call function */
 	aclresult = pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE);
 	if (aclresult != ACLCHECK_OK)
@@ -2110,12 +2126,16 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 	foreach(lc, args)
 	{
 		Expr	   *arg = (Expr *) lfirst(lc);
+		bool		arg_is_nonvolatile_function,
+					arg_is_nonvolatile_operator;
 
 		if (IsA(arg, Const))
 		{
 			/*
 			 * Don't evaluate const arguments every round; especially
 			 * interesting for constants in comparisons.
+			 *
+			 * Const arg doesn't affect value of has_nonconst_or_volatile_input.
 			 */
 			Const	   *con = (Const *) arg;
 
@@ -2126,10 +2146,31 @@ ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
 		{
 			ExecInitExprRec(arg, parent, state,
 							&fcinfo->arg[argno], &fcinfo->argnull[argno]);
+
+			/* arg is not const, but it may be volatile function or operator */
+			arg_is_nonvolatile_function = IsA(arg, FuncExpr) &&
+				!((const FuncExpr *) arg)->isVolatile;
+			arg_is_nonvolatile_operator = IsA(arg, OpExpr) &&
+				!((const OpExpr *) arg)->isVolatile;
+			if (!(arg_is_nonvolatile_function || arg_is_nonvolatile_operator))
+				has_nonconst_or_volatile_input = true;
 		}
 		argno++;
 	}
 
+	is_volatile = has_nonconst_or_volatile_input ||
+					contain_volatile_functions((Node *) node);
+	/* for recursive check of volatile functions or operators */
+	if (IsA(node, FuncExpr))
+		((FuncExpr *) node)->isVolatile = is_volatile;
+	else if (IsA(node, OpExpr))
+		((OpExpr *) node)->isVolatile = is_volatile;
+	/* for execution of this expression */
+	fcinfo->isVolatile = is_volatile;
+	fcinfo->isExecuted = false;
+	fcinfo->nonVolatileResultIsNull = false;
+	fcinfo->nonVolatileResult = (Datum) 0;
+
 	/* Insert appropriate opcode depending on strictness and stats level */
 	if (pgstat_track_functions <= flinfo->fn_stats)
 	{
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index fed0052..7220f61 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;
 
 	/*
 	 * This array has to be in the same order as enum ExprEvalOp.
@@ -646,9 +647,40 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		{
 			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
 
-			fcinfo->isnull = false;
-			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
-			*op->resnull = fcinfo->isnull;
+			if (!fcinfo->isVolatile && fcinfo->isExecuted)
+			{
+				/* use saved result */
+				fcinfo->isnull = fcinfo->nonVolatileResultIsNull;
+				*op->resvalue = fcinfo->nonVolatileResult;
+				*op->resnull = fcinfo->isnull;
+			}
+			else
+			{
+				/*
+				 * If function is not volatile then switch per-query memory
+				 * context. It is necessary to save result between all tuples.
+				 */
+				if (!fcinfo->isVolatile)
+					oldContext = MemoryContextSwitchTo(
+								econtext->ecxt_per_query_memory);
+
+				/* execute function as usual */
+				fcinfo->isnull = false;
+				*op->resvalue = (op->d.func.fn_addr) (fcinfo);
+				*op->resnull = fcinfo->isnull;
+
+				/*
+				 * Save result for nonvolatile functions and switch memory
+				 * context back.
+				 */
+				if (!fcinfo->isVolatile)
+				{
+					fcinfo->nonVolatileResultIsNull = fcinfo->isnull;
+					fcinfo->nonVolatileResult = *op->resvalue;
+					fcinfo->isExecuted = true;
+					MemoryContextSwitchTo(oldContext);
+				}	
+			}
 
 			EEO_NEXT();
 		}
@@ -659,18 +691,58 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			bool	   *argnull = fcinfo->argnull;
 			int			argno;
 
-			/* strict function, so check for NULL args */
-			for (argno = 0; argno < op->d.func.nargs; argno++)
+			if (!fcinfo->isVolatile && fcinfo->isExecuted)
+			{
+				/* use saved result */
+				fcinfo->isnull = fcinfo->nonVolatileResultIsNull;
+				if (!fcinfo->isnull)
+					*op->resvalue = fcinfo->nonVolatileResult;
+				*op->resnull = fcinfo->isnull;
+			}
+			else
 			{
-				if (argnull[argno])
+				/* strict function, so check for NULL args */
+				for (argno = 0; argno < op->d.func.nargs; argno++)
 				{
-					*op->resnull = true;
-					goto strictfail;
+					if (argnull[argno])
+					{
+						*op->resnull = true;
+
+						if (!fcinfo->isVolatile)
+						{
+							fcinfo->nonVolatileResultIsNull = *op->resnull;
+							fcinfo->isExecuted = true;
+						}
+
+						goto strictfail;
+					}
 				}
+
+				/*
+				 * If function is not volatile then switch per-query memory
+				 * context. It is necessary to save result between all tuples.
+				 */
+				if (!fcinfo->isVolatile)
+					oldContext = MemoryContextSwitchTo(
+								econtext->ecxt_per_query_memory);
+
+				/* execute function as usual */
+				fcinfo->isnull = false;
+				*op->resvalue = (op->d.func.fn_addr) (fcinfo);
+				*op->resnull = fcinfo->isnull;
+
+				/*
+				 * Save result for nonvolatile functions and switch memory
+				 * context back.
+				 */
+				if (!fcinfo->isVolatile)
+				{
+					fcinfo->nonVolatileResultIsNull = fcinfo->isnull;
+					fcinfo->nonVolatileResult = *op->resvalue;
+					fcinfo->isExecuted = true;
+					MemoryContextSwitchTo(oldContext);
+				}	
 			}
-			fcinfo->isnull = false;
-			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
-			*op->resnull = fcinfo->isnull;
 
 	strictfail:
 			EEO_NEXT();
@@ -681,13 +753,44 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
 			PgStat_FunctionCallUsage fcusage;
 
-			pgstat_init_function_usage(fcinfo, &fcusage);
+			if (!fcinfo->isVolatile && fcinfo->isExecuted)
+			{
+				/* use saved result */
+				fcinfo->isnull = fcinfo->nonVolatileResultIsNull;
+				*op->resvalue = fcinfo->nonVolatileResult;
+				*op->resnull = fcinfo->isnull;
+			}
+			else
+			{
+				pgstat_init_function_usage(fcinfo, &fcusage);
 
-			fcinfo->isnull = false;
-			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
-			*op->resnull = fcinfo->isnull;
+				/*
+				 * If function is not volatile then switch per-query memory
+				 * context. It is necessary to save result between all tuples.
+				 */
+				if (!fcinfo->isVolatile)
+					oldContext = MemoryContextSwitchTo(
+								econtext->ecxt_per_query_memory);
 
-			pgstat_end_function_usage(&fcusage, true);
+				/* execute function as usual */
+				fcinfo->isnull = false;
+				*op->resvalue = (op->d.func.fn_addr) (fcinfo);
+				*op->resnull = fcinfo->isnull;
+
+				/*
+				 * Save result for nonvolatile functions and switch memory
+				 * context back.
+				 */
+				if (!fcinfo->isVolatile)
+				{
+					fcinfo->nonVolatileResultIsNull = fcinfo->isnull;
+					fcinfo->nonVolatileResult = *op->resvalue;
+					fcinfo->isExecuted = true;
+					MemoryContextSwitchTo(oldContext);
+				}
+
+				pgstat_end_function_usage(&fcusage, true);
+			}
 
 			EEO_NEXT();
 		}
@@ -699,23 +802,62 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			bool	   *argnull = fcinfo->argnull;
 			int			argno;
 
-			/* strict function, so check for NULL args */
-			for (argno = 0; argno < op->d.func.nargs; argno++)
+			if (!fcinfo->isVolatile && fcinfo->isExecuted)
+			{
+				/* use saved result */
+				fcinfo->isnull = fcinfo->nonVolatileResultIsNull;
+				if (!fcinfo->isnull)
+					*op->resvalue = fcinfo->nonVolatileResult;
+				*op->resnull = fcinfo->isnull;
+			}
+			else
 			{
-				if (argnull[argno])
+				/* strict function, so check for NULL args */
+				for (argno = 0; argno < op->d.func.nargs; argno++)
 				{
-					*op->resnull = true;
-					goto strictfail_fusage;
+					if (argnull[argno])
+					{
+						*op->resnull = true;
+
+						if (!fcinfo->isVolatile)
+						{
+							fcinfo->nonVolatileResultIsNull = *op->resnull;
+							fcinfo->isExecuted = true;
+						}
+
+						goto strictfail_fusage;
+					}
 				}
-			}
 
-			pgstat_init_function_usage(fcinfo, &fcusage);
+				pgstat_init_function_usage(fcinfo, &fcusage);
 
-			fcinfo->isnull = false;
-			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
-			*op->resnull = fcinfo->isnull;
+				/*
+				 * If function is not volatile then switch per-query memory
+				 * context. It is necessary to save result between all tuples.
+				 */
+				if (!fcinfo->isVolatile)
+					oldContext = MemoryContextSwitchTo(
+								econtext->ecxt_per_query_memory);
 
-			pgstat_end_function_usage(&fcusage, true);
+				/* execute function as usual */
+				fcinfo->isnull = false;
+				*op->resvalue = (op->d.func.fn_addr) (fcinfo);
+				*op->resnull = fcinfo->isnull;
+
+				/*
+				 * Save result for nonvolatile functions and switch memory
+				 * context back.
+				 */
+				if (!fcinfo->isVolatile)
+				{
+					fcinfo->nonVolatileResultIsNull = fcinfo->isnull;
+					fcinfo->nonVolatileResult = *op->resvalue;
+					fcinfo->isExecuted = true;
+					MemoryContextSwitchTo(oldContext);
+				}
+
+				pgstat_end_function_usage(&fcusage, true);
+			}
 
 	strictfail_fusage:
 			EEO_NEXT();
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 52643d0..310ca0f 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3539,11 +3539,87 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 	 */
 	if (IsA(node, FuncExpr))
 	{
-		context->total.per_tuple +=
-			get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+		/* firstly recurse into children */
+		bool		children_result = expression_tree_walker(node,
+													cost_qual_eval_walker,
+													(void *) context);
+		FuncExpr   *funcexpr = (FuncExpr *) node;
+		ListCell   *arg;
+		bool		has_nonconst_or_volatile_input = false;
+		double		func_cost = get_func_cost(funcexpr->funcid) *
+			cpu_operator_cost;
+		bool		func_returns_set = funcexpr->funcretset ||
+			expression_returns_set((Node *) funcexpr->args);
+
+		foreach(arg, funcexpr->args) {
+			void	   *arg_lfirst = lfirst(arg);
+			bool		arg_is_const = IsA(arg_lfirst, Const);
+			bool		arg_is_nonvolatile_function = IsA(arg_lfirst, FuncExpr) &&
+				!((const FuncExpr *) arg_lfirst)->isVolatile;
+			bool		arg_is_nonvolatile_operator = IsA(arg_lfirst, OpExpr) &&
+				!((const OpExpr *) arg_lfirst)->isVolatile;
+			if (!(arg_is_const ||
+				  arg_is_nonvolatile_function ||
+				  arg_is_nonvolatile_operator))
+				has_nonconst_or_volatile_input = true;
+		}
+
+		funcexpr->isVolatile = func_returns_set ||
+			has_nonconst_or_volatile_input ||
+			contain_volatile_functions((Node *) &funcexpr->xpr);
+
+		if (funcexpr->isVolatile)
+			context->total.per_tuple += func_cost;
+		else
+			context->total.startup += func_cost;
+
+		return children_result;
+	}
+	else if (IsA(node, OpExpr))
+	{	
+		bool		children_result;
+		OpExpr     *opexpr = (OpExpr *) node;
+		ListCell   *arg;
+		bool		has_nonconst_or_volatile_input = false;
+		double		func_cost;
+		bool		op_returns_set;
+
+		/* rely on struct equivalence to treat these all alike */
+		set_opfuncid(opexpr);
+
+		/* firstly recurse into children */
+		children_result = expression_tree_walker(node, cost_qual_eval_walker,
+												 (void *) context);
+		op_returns_set = opexpr->opretset ||
+			expression_returns_set((Node *) opexpr->args);
+		
+		func_cost = get_func_cost(opexpr->opfuncid) * cpu_operator_cost;
+
+		foreach(arg, opexpr->args) {
+			void	   *arg_lfirst = lfirst(arg);
+			bool		arg_is_const = IsA(arg_lfirst, Const);
+			bool		arg_is_nonvolatile_function = IsA(arg_lfirst, FuncExpr) &&
+				!((const FuncExpr *) arg_lfirst)->isVolatile;
+			bool		arg_is_nonvolatile_operator = IsA(arg_lfirst, OpExpr) &&
+				!((const OpExpr *) arg_lfirst)->isVolatile;
+			if (!(arg_is_const ||
+				  arg_is_nonvolatile_function ||
+				  arg_is_nonvolatile_operator))
+				has_nonconst_or_volatile_input = true;
+		}
+
+		opexpr->isVolatile = op_returns_set ||
+			has_nonconst_or_volatile_input ||
+			contain_volatile_functions((Node *) &opexpr->xpr);
+
+		if (opexpr->isVolatile)
+			context->total.per_tuple += func_cost;
+		else
+			context->total.startup += func_cost;
+
+		return children_result;
 	}
-	else if (IsA(node, OpExpr) ||
-			 IsA(node, DistinctExpr) ||
+	else if (IsA(node, DistinctExpr) ||
 			 IsA(node, NullIfExpr))
 	{
 		/* rely on struct equivalence to treat these all alike */
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index cfb7b77..57b89eb 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -84,6 +84,10 @@ typedef struct FunctionCallInfoData
 	short		nargs;			/* # arguments actually passed */
 	Datum		arg[FUNC_MAX_ARGS];		/* Arguments passed to function */
 	bool		argnull[FUNC_MAX_ARGS]; /* T if arg[i] is actually NULL */
+	bool		isVolatile;
+	bool		isExecuted;		/* for nonvolatile functions */
+	bool		nonVolatileResultIsNull;
+	Datum		nonVolatileResult;
 } FunctionCallInfoData;
 
 /*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index b87fe84..d3ba632 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -455,6 +455,7 @@ typedef struct FuncExpr
 	Oid			inputcollid;	/* OID of collation that function should use */
 	List	   *args;			/* arguments to the function */
 	int			location;		/* token location, or -1 if unknown */
+	bool		isVolatile;
 } FuncExpr;
 
 /*
@@ -500,6 +501,7 @@ typedef struct OpExpr
 	Oid			inputcollid;	/* OID of collation that operator should use */
 	List	   *args;			/* arguments to the operator (1 or 2) */
 	int			location;		/* token location, or -1 if unknown */
+	bool		isVolatile;
 } OpExpr;
 
 /*
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..f3f26d5
--- /dev/null
+++ b/src/test/regress/expected/precalculate_stable_functions.out
@@ -0,0 +1,784 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+-- Create volatile functions for testing
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create stable functions for testing
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create immutable functions for testing
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+-- Create operators for testing
+CREATE operator === (PROCEDURE = equal_integers_vlt, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ==== (PROCEDURE = equal_integers_stl, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ===== (PROCEDURE = equal_integers_imm, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ====== (PROCEDURE = equal_booleans_stl_strict, LEFTARG = boolean, RIGHTARG = boolean);
+-- Simple functions testing
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+-- Functions with constant arguments and nested functions testing
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+-- Strict functions testing
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+-- Strict functions with null arguments testing
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+-- Operators testing
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Nested and strict operators testing
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+-- Mixed functions and operators testing
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+-- Tracking functions testing
+SET track_functions TO 'all';
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 3) x;
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+NOTICE:  v
+ x_vlt 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  s
+ x_stl 
+-------
+     1
+     1
+     1
+(3 rows)
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+NOTICE:  v
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+NOTICE:  v
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  s2
+ x_stl2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+NOTICE:  i2
+ x_imm2 
+--------
+      1
+      1
+      1
+      1
+(4 rows)
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+NOTICE:  v
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+NOTICE:  v
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  s2 strict
+ x_stl2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+NOTICE:  s2 strict
+NOTICE:  i2 strict
+ x_imm2_strict 
+---------------
+             1
+             1
+             1
+             1
+(4 rows)
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_stl2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+NOTICE:  s2
+ x_imm2_strict 
+---------------
+              
+              
+              
+              
+(4 rows)
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+NOTICE:  equal integers volatile
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  equal integers stable
+NOTICE:  equal booleans stable strict
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+NOTICE:  s2 boolean
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ 
+ 
+ 
+ 
+(4 rows)
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+NOTICE:  v
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+NOTICE:  v
+NOTICE:  equal integers immutable
+ ?column? 
+----------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+NOTICE:  s
+NOTICE:  s
+NOTICE:  equal integers stable
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+(4 rows)
+
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+NOTICE:  equal integers stable
+NOTICE:  s2 boolean
+ x_stl2_boolean 
+----------------
+ f
+ f
+ f
+ f
+(4 rows)
+
+SET track_functions TO DEFAULT;
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..bcef2ca
--- /dev/null
+++ b/src/test/regress/sql/precalculate_stable_functions.sql
@@ -0,0 +1,240 @@
+--
+-- PRECALCULATE STABLE FUNCTIONS
+--
+
+-- Create volatile functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_vlt (
+)
+RETURNS integer VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'v';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_vlt (
+  integer,
+  integer
+)
+RETURNS boolean VOLATILE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers volatile';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create stable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_stl (
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's';
+  RETURN 1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2 (
+     integer
+)
+RETURNS integer STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_strict (
+     integer
+)
+RETURNS integer STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_stl (
+  integer,
+  integer
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers stable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_stl2_boolean (
+  boolean
+)
+RETURNS boolean STABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 's2 boolean';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_booleans_stl_strict (
+  boolean,
+  boolean
+)
+RETURNS boolean STABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal booleans stable strict';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create immutable functions for testing
+
+CREATE OR REPLACE FUNCTION public.x_imm2 (
+     integer
+)
+RETURNS integer IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.x_imm2_strict (
+     integer
+)
+RETURNS integer IMMUTABLE STRICT AS
+$body$
+BEGIN
+  RAISE NOTICE 'i2 strict';
+  RETURN $1;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+CREATE OR REPLACE FUNCTION public.equal_integers_imm (
+  integer,
+  integer
+)
+RETURNS boolean IMMUTABLE AS
+$body$
+BEGIN
+  RAISE NOTICE 'equal integers immutable';
+  RETURN $1 = $2;
+END;
+$body$
+LANGUAGE 'plpgsql';
+
+-- Create operators for testing
+
+CREATE operator === (PROCEDURE = equal_integers_vlt, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ==== (PROCEDURE = equal_integers_stl, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ===== (PROCEDURE = equal_integers_imm, LEFTARG = integer, RIGHTARG = integer);
+CREATE operator ====== (PROCEDURE = equal_booleans_stl_strict, LEFTARG = boolean, RIGHTARG = boolean);
+
+-- Simple functions testing
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+-- Functions with constant arguments and nested functions testing
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions testing
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+-- Strict functions with null arguments testing
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+-- Operators testing
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+
+-- Nested and strict operators testing
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+
+-- Mixed functions and operators testing
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+-- Tracking functions testing
+
+SET track_functions TO 'all';
+
+SELECT x_vlt() FROM generate_series(1, 3) x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 3) x;
+
+SELECT x_vlt() FROM generate_series(1, 4) x WHERE x_vlt() < x; -- should not be precalculated
+SELECT x_stl() FROM generate_series(1, 4) x WHERE x_stl() < x;
+
+SELECT x_stl2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2(x_stl2(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2(x_stl2(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_imm2_strict(x_vlt()) FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2_strict(1)) FROM generate_series(1, 4) x;
+
+SELECT x_stl2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+SELECT x_imm2_strict(x_stl2(NULL)) FROM generate_series(1, 4) x;
+
+SELECT 1 === 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT 1 ==== 2 FROM generate_series(1, 4) x;
+SELECT 1 ===== 2 FROM generate_series(1, 4) x;
+
+SELECT (x_vlt() ==== 2) ====== (x_vlt() ===== 3) FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT (1 ==== 2) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(NULL) ====== (3 ==== 3) FROM generate_series(1, 4) x;
+
+SELECT x_vlt() ==== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+SELECT x_vlt() ===== 2 FROM generate_series(1, 4) x; -- should not be precalculated
+
+SELECT x_stl() ==== x_stl() FROM generate_series(1, 4) x;
+SELECT x_stl2_boolean(1 ==== 2) FROM generate_series(1, 4) x;
+
+SET track_functions TO DEFAULT;
\ No newline at end of file
-- 
1.9.1

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

Reply via email to