Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1

Поиск
Список
Период
Сортировка
Искать
От
Aleksander Alekseev
Тема
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1
Дата
в 18:55:51
Msg-id
20170530155550.GB10525@e733.localdomain
Ответ на
Список
Дерево обсуждения
[HACKERS] WIP Patch: Precalculate stable functions, infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions, infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Alexander Korotkov <a.korotkov@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Aleksander Alekseev <a.alekseev@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Andres Freund <andres@anarazel.de>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Aleksander Alekseev <a.alekseev@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Robert Haas <robertmhaas@gmail.com>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Dmitry Dolgov <9erthalion6@gmail.com>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Aleksander Alekseev <a.alekseev@postgrespro.ru>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Tom Lane <tgl@sss.pgh.pa.us>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Tom Lane <tgl@sss.pgh.pa.us>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Andres Freund <andres@anarazel.de>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Tom Lane <tgl@sss.pgh.pa.us>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Andres Freund <andres@anarazel.de>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Andres Freund <andres@anarazel.de>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Andres Freund <andres@anarazel.de>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: Re: WIP Patch: Precalculate stable functions, infrastructure v1 David Steele <david@pgmasters.net>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Michael Paquier <michael@paquier.xyz>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Dmitry Dolgov <9erthalion6@gmail.com>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Andres Freund <andres@anarazel.de>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 David Geier <geidav.pg@gmail.com>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Thomas Munro <thomas.munro@enterprisedb.com>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: WIP Patch: Precalculate stable functions, infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions, infrastructure v1 Aleksander Alekseev <a.alekseev@postgrespro.ru>
Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 Marina Polyakova <m.polyakova@postgrespro.ru>
Hi Marina,

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

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

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

On Mon, May 22, 2017 at 06:32:17PM +0300, Marina Polyakova wrote:
> > Hi,
> 
> Hello!
> 
> > I've not followed this thread, but just scanned this quickly because it
> > affects execExpr* stuff.
> 
> Thank you very much for your comments! Thanks to them I have made v4 of the
> patches (as in the previous one, only planning and execution part is
> changed).
> 
> > Looks like having something like struct CachedExprState would be better,
> > than these separate allocations?  That also allows to aleviate some size
> > concerns when adding new fields (see below)
> 
> > I'd rather not have this on function scope - a) the stack pressure in
> > ExecInterpExpr is quite noticeable in profiles already b) this is going
> > to trigger warnings because of unused vars, because the compiler doesn't
> > understand that EEOP_CACHEDEXPR_IF_CACHED always follows
> > EEOP_CACHEDEXPR_SUBEXPR_END.
> > 
> > How about instead storing oldcontext in the expression itself?
> 
> Thanks, in new version I did all of it in this way.
> 
> > I'm also not sure how acceptable it is to just assume it's ok to leave
> > stuff in per_query_memory, in some cases that could prove to be
> > problematic.
> 
> I agree with you and in new version context is changed only for copying
> datum of result value (if it's a pointer, its data should be allocated in
> per_query_memory, or we will lost it for next tuples).
> 
> > Is this actually a meaningful path?  Shouldn't always have done const
> > evaluation before adding CachedExpr's?
> 
> eval_const_expressions_mutator is used several times, and one of them in
> functions for selectivity evaluation (set_baserel_size_estimates ->
> clauselist_selectivity -> clause_selectivity -> restriction_selectivity ->
> ... -> get_restriction_variable -> estimate_expression_value ->
> eval_const_expressions_mutator). In set_baserel_size_estimates function
> right after selectivity evaluation there's costs evaluation and cached
> expressions should be replaced before costs. I'm not sure that it is a good
> idea to insert cached expressions replacement in set_baserel_size_estimates,
> because in comments to it it's said "The rel's targetlist and restrictinfo
> list must have been constructed already, and rel->tuples must be set." and
> its file costsize.c is entitled as "Routines to compute (and set) relation
> sizes and path costs". So I have inserted cached expressions replacement
> just before it (but I'm not sure that I have seen all places where it should
> be inserted). What do you think about all of this?
> 
> -- 
> Marina Polyakova
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company

> From 02262b9f3a3215d3884b6ac188bafa6517ac543d Mon Sep 17 00:00:00 2001
> From: Marina Polyakova 
> Date: Mon, 15 May 2017 14:24:36 +0300
> Subject: [PATCH v4 1/3] Precalculate stable functions, infrastructure
> 
> Now in Postgresql only immutable functions are precalculated; stable functions
> are calculated for every row so in fact they don't differ from volatile
> functions.
> 
> This patch includes:
> - creation of CachedExpr node
> - usual node functions for it
> - mutator to replace nonovolatile functions' and operators' expressions by
> appropriate cached expressions.
> ---
>  src/backend/nodes/copyfuncs.c        |  31 +++++
>  src/backend/nodes/equalfuncs.c       |  31 +++++
>  src/backend/nodes/nodeFuncs.c        | 151 ++++++++++++++++++++
>  src/backend/nodes/outfuncs.c         |  56 ++++++++
>  src/backend/nodes/readfuncs.c        |  48 +++++++
>  src/backend/optimizer/plan/planner.c | 259 +++++++++++++++++++++++++++++++++++
>  src/include/nodes/nodeFuncs.h        |   1 +
>  src/include/nodes/nodes.h            |   1 +
>  src/include/nodes/primnodes.h        |  38 +++++
>  9 files changed, 616 insertions(+)
> 
> diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
> index 6ad3844..f9f69a1 100644
> --- a/src/backend/nodes/copyfuncs.c
> +++ b/src/backend/nodes/copyfuncs.c
> @@ -1527,6 +1527,34 @@ _copyNullIfExpr(const NullIfExpr *from)
>  	return newnode;
>  }
>  
> +static CachedExpr *
> +_copyCachedExpr(const CachedExpr *from)
> +{
> +	CachedExpr *newnode = makeNode(CachedExpr);
> +
> +	COPY_SCALAR_FIELD(subexprtype);
> +	switch(from->subexprtype)
> +	{
> +		case CACHED_FUNCEXPR:
> +			COPY_NODE_FIELD(subexpr.funcexpr);
> +			break;
> +		case CACHED_OPEXPR:
> +			COPY_NODE_FIELD(subexpr.opexpr);
> +			break;
> +		case CACHED_DISTINCTEXPR:
> +			COPY_NODE_FIELD(subexpr.distinctexpr);
> +			break;
> +		case CACHED_NULLIFEXPR:
> +			COPY_NODE_FIELD(subexpr.nullifexpr);
> +			break;
> +		case CACHED_SCALARARRAYOPEXPR:
> +			COPY_NODE_FIELD(subexpr.saopexpr);
> +			break;
> +	}
> +
> +	return newnode;
> +}
> +
>  /*
>   * _copyScalarArrayOpExpr
>   */
> @@ -4867,6 +4895,9 @@ copyObjectImpl(const void *from)
>  		case T_NullIfExpr:
>  			retval = _copyNullIfExpr(from);
>  			break;
> +		case T_CachedExpr:
> +			retval = _copyCachedExpr(from);
> +			break;
>  		case T_ScalarArrayOpExpr:
>  			retval = _copyScalarArrayOpExpr(from);
>  			break;
> diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
> index c9a8c34..8863759 100644
> --- a/src/backend/nodes/equalfuncs.c
> +++ b/src/backend/nodes/equalfuncs.c
> @@ -384,6 +384,34 @@ _equalNullIfExpr(const NullIfExpr *a, const NullIfExpr *b)
>  }
>  
>  static bool
> +_equalCachedExpr(const CachedExpr *a, const CachedExpr *b)
> +{
> +	COMPARE_SCALAR_FIELD(subexprtype);
> +
> +	/* the same subexprtype for b because we have already compared it */
> +	switch(a->subexprtype)
> +	{
> +		case CACHED_FUNCEXPR:
> +			COMPARE_NODE_FIELD(subexpr.funcexpr);
> +			break;
> +		case CACHED_OPEXPR:
> +			COMPARE_NODE_FIELD(subexpr.opexpr);
> +			break;
> +		case CACHED_DISTINCTEXPR:
> +			COMPARE_NODE_FIELD(subexpr.distinctexpr);
> +			break;
> +		case CACHED_NULLIFEXPR:
> +			COMPARE_NODE_FIELD(subexpr.nullifexpr);
> +			break;
> +		case CACHED_SCALARARRAYOPEXPR:
> +			COMPARE_NODE_FIELD(subexpr.saopexpr);
> +			break;
> +	}
> +
> +	return true;
> +}
> +
> +static bool
>  _equalScalarArrayOpExpr(const ScalarArrayOpExpr *a, const ScalarArrayOpExpr *b)
>  {
>  	COMPARE_SCALAR_FIELD(opno);
> @@ -3031,6 +3059,9 @@ equal(const void *a, const void *b)
>  		case T_NullIfExpr:
>  			retval = _equalNullIfExpr(a, b);
>  			break;
> +		case T_CachedExpr:
> +			retval = _equalCachedExpr(a, b);
> +			break;
>  		case T_ScalarArrayOpExpr:
>  			retval = _equalScalarArrayOpExpr(a, b);
>  			break;
> diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
> index 3e8189c..e3dd576 100644
> --- a/src/backend/nodes/nodeFuncs.c
> +++ b/src/backend/nodes/nodeFuncs.c
> @@ -32,6 +32,7 @@ static bool planstate_walk_subplans(List *plans, bool (*walker) (),
>  												void *context);
>  static bool planstate_walk_members(List *plans, PlanState **planstates,
>  					   bool (*walker) (), void *context);
> +static const Node *get_const_subexpr(const CachedExpr *cachedexpr);
>  
>  
>  /*
> @@ -92,6 +93,9 @@ exprType(const Node *expr)
>  		case T_NullIfExpr:
>  			type = ((const NullIfExpr *) expr)->opresulttype;
>  			break;
> +		case T_CachedExpr:
> +			type = exprType(get_const_subexpr((const CachedExpr *) expr));
> +			break;
>  		case T_ScalarArrayOpExpr:
>  			type = BOOLOID;
>  			break;
> @@ -311,6 +315,8 @@ exprTypmod(const Node *expr)
>  				return exprTypmod((Node *) linitial(nexpr->args));
>  			}
>  			break;
> +		case T_CachedExpr:
> +			return exprTypmod(get_const_subexpr((const CachedExpr *) expr));
>  		case T_SubLink:
>  			{
>  				const SubLink *sublink = (const SubLink *) expr;
> @@ -573,6 +579,10 @@ exprIsLengthCoercion(const Node *expr, int32 *coercedTypmod)
>  		return true;
>  	}
>  
> +	if (expr && IsA(expr, CachedExpr))
> +		return exprIsLengthCoercion(
> +			get_const_subexpr((const CachedExpr *) expr), coercedTypmod);
> +
>  	return false;
>  }
>  
> @@ -655,6 +665,10 @@ strip_implicit_coercions(Node *node)
>  		if (c->coercionformat == COERCE_IMPLICIT_CAST)
>  			return strip_implicit_coercions((Node *) c->arg);
>  	}
> +	else if (IsA(node, CachedExpr))
> +	{
> +		return strip_implicit_coercions(get_subexpr((CachedExpr *) node));
> +	}
>  	return node;
>  }
>  
> @@ -727,6 +741,8 @@ expression_returns_set_walker(Node *node, void *context)
>  		return false;
>  	if (IsA(node, XmlExpr))
>  		return false;
> +	if (IsA(node, CachedExpr))
> +		return false;
>  
>  	return expression_tree_walker(node, expression_returns_set_walker,
>  								  context);
> @@ -790,6 +806,9 @@ exprCollation(const Node *expr)
>  		case T_NullIfExpr:
>  			coll = ((const NullIfExpr *) expr)->opcollid;
>  			break;
> +		case T_CachedExpr:
> +			coll = exprCollation(get_const_subexpr((const CachedExpr *) expr));
> +			break;
>  		case T_ScalarArrayOpExpr:
>  			coll = InvalidOid;	/* result is always boolean */
>  			break;
> @@ -973,6 +992,10 @@ exprInputCollation(const Node *expr)
>  		case T_NullIfExpr:
>  			coll = ((const NullIfExpr *) expr)->inputcollid;
>  			break;
> +		case T_CachedExpr:
> +			coll = exprInputCollation(
> +				get_const_subexpr((const CachedExpr *) expr));
> +			break;
>  		case T_ScalarArrayOpExpr:
>  			coll = ((const ScalarArrayOpExpr *) expr)->inputcollid;
>  			break;
> @@ -1034,6 +1057,9 @@ exprSetCollation(Node *expr, Oid collation)
>  		case T_NullIfExpr:
>  			((NullIfExpr *) expr)->opcollid = collation;
>  			break;
> +		case T_CachedExpr:
> +			exprSetCollation(get_subexpr((CachedExpr *) expr), collation);
> +			break;
>  		case T_ScalarArrayOpExpr:
>  			Assert(!OidIsValid(collation));		/* result is always boolean */
>  			break;
> @@ -1168,6 +1194,10 @@ exprSetInputCollation(Node *expr, Oid inputcollation)
>  		case T_NullIfExpr:
>  			((NullIfExpr *) expr)->inputcollid = inputcollation;
>  			break;
> +		case T_CachedExpr:
> +			exprSetInputCollation(get_subexpr((CachedExpr *) expr),
> +								  inputcollation);
> +			break;
>  		case T_ScalarArrayOpExpr:
>  			((ScalarArrayOpExpr *) expr)->inputcollid = inputcollation;
>  			break;
> @@ -1277,6 +1307,9 @@ exprLocation(const Node *expr)
>  								  exprLocation((Node *) opexpr->args));
>  			}
>  			break;
> +		case T_CachedExpr:
> +			loc = exprLocation(get_const_subexpr((const CachedExpr *) expr));
> +			break;
>  		case T_ScalarArrayOpExpr:
>  			{
>  				const ScalarArrayOpExpr *saopexpr = (const ScalarArrayOpExpr *) expr;
> @@ -1611,6 +1644,8 @@ fix_opfuncids_walker(Node *node, void *context)
>  {
>  	if (node == NULL)
>  		return false;
> +	if (IsA(node, CachedExpr))
> +		return fix_opfuncids_walker(get_subexpr((CachedExpr *) node), context);
>  	if (IsA(node, OpExpr))
>  		set_opfuncid((OpExpr *) node);
>  	else if (IsA(node, DistinctExpr))
> @@ -1710,6 +1745,9 @@ check_functions_in_node(Node *node, check_function_callback checker,
>  					return true;
>  			}
>  			break;
> +		case T_CachedExpr:
> +			return check_functions_in_node(get_subexpr((CachedExpr *) node),
> +										   checker, context);
>  		case T_ScalarArrayOpExpr:
>  			{
>  				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
> @@ -1980,6 +2018,17 @@ expression_tree_walker(Node *node,
>  					return true;
>  			}
>  			break;
> +		case T_CachedExpr:
> +			{
> +				/*
> +				 * cachedexpr is processed by my_walker, so its subexpr is
> +				 * processed too and we need to process sub-nodes of subexpr.
> +				 */
> +				if (expression_tree_walker(get_subexpr((CachedExpr *) node),
> +										   walker, context))
> +					return true;
> +			}
> +			break;
>  		case T_ScalarArrayOpExpr:
>  			{
>  				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
> @@ -2617,6 +2666,54 @@ expression_tree_mutator(Node *node,
>  				return (Node *) newnode;
>  			}
>  			break;
> +		case T_CachedExpr:
> +			{
> +				CachedExpr *expr = (CachedExpr *) node;
> +				CachedExpr *newnode;
> +
> +				FLATCOPY(newnode, expr, CachedExpr);
> +
> +				/*
> +				 * expr is already mutated, so its subexpr is already mutated
> +				 * too and we need to mutate sub-nodes of subexpr.
> +				 */
> +				switch(newnode->subexprtype)
> +				{
> +					case CACHED_FUNCEXPR:
> +						newnode->subexpr.funcexpr = (FuncExpr *)
> +							expression_tree_mutator(
> +								(Node *) expr->subexpr.funcexpr, mutator,
> +								context);
> +						break;
> +					case CACHED_OPEXPR:
> +						newnode->subexpr.opexpr = (OpExpr *)
> +							expression_tree_mutator(
> +								(Node *) expr->subexpr.opexpr, mutator,
> +								context);
> +						break;
> +					case CACHED_DISTINCTEXPR:
> +						newnode->subexpr.distinctexpr = (DistinctExpr *)
> +							expression_tree_mutator(
> +								(Node *) expr->subexpr.distinctexpr, mutator,
> +								context);
> +						break;
> +					case CACHED_NULLIFEXPR:
> +						newnode->subexpr.nullifexpr = (NullIfExpr *)
> +							expression_tree_mutator(
> +								(Node *) expr->subexpr.nullifexpr, mutator,
> +								context);
> +						break;
> +					case CACHED_SCALARARRAYOPEXPR:
> +						newnode->subexpr.saopexpr = (ScalarArrayOpExpr *)
> +							expression_tree_mutator(
> +								(Node *) expr->subexpr.saopexpr, mutator,
> +								context);
> +						break;
> +				}
> +
> +				return (Node *) newnode;
> +			}
> +			break;
>  		case T_ScalarArrayOpExpr:
>  			{
>  				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
> @@ -3838,3 +3935,57 @@ planstate_walk_members(List *plans, PlanState **planstates,
>  
>  	return false;
>  }
> +
> +/*
> + * get_const_subexpr
> + *		Get const subexpression of given const cached expression.
> + */
> +static const Node *
> +get_const_subexpr(const CachedExpr *cachedexpr)
> +{
> +	if (cachedexpr == NULL)
> +		return NULL;
> +
> +	switch (cachedexpr->subexprtype)
> +	{
> +		case CACHED_FUNCEXPR:
> +			return (const Node *) cachedexpr->subexpr.funcexpr;
> +		case CACHED_OPEXPR:
> +			return (const Node *) cachedexpr->subexpr.opexpr;
> +		case CACHED_DISTINCTEXPR:
> +			return (const Node *) cachedexpr->subexpr.distinctexpr;
> +		case CACHED_NULLIFEXPR:
> +			return (const Node *) cachedexpr->subexpr.nullifexpr;
> +		case CACHED_SCALARARRAYOPEXPR:
> +			return (const Node *) cachedexpr->subexpr.saopexpr;
> +	}
> +
> +	return NULL;
> +}
> +
> +/*
> + * get_subexpr
> + *		Get subexpression of given cached expression.
> + */
> +Node *
> +get_subexpr(CachedExpr *cachedexpr)
> +{
> +	if (cachedexpr == NULL)
> +		return NULL;
> +
> +	switch (cachedexpr->subexprtype)
> +	{
> +		case CACHED_FUNCEXPR:
> +			return (Node *) cachedexpr->subexpr.funcexpr;
> +		case CACHED_OPEXPR:
> +			return (Node *) cachedexpr->subexpr.opexpr;
> +		case CACHED_DISTINCTEXPR:
> +			return (Node *) cachedexpr->subexpr.distinctexpr;
> +		case CACHED_NULLIFEXPR:
> +			return (Node *) cachedexpr->subexpr.nullifexpr;
> +		case CACHED_SCALARARRAYOPEXPR:
> +			return (Node *) cachedexpr->subexpr.saopexpr;
> +	}
> +
> +	return NULL;
> +}
> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
> index 8d9ff63..c0c8363 100644
> --- a/src/backend/nodes/outfuncs.c
> +++ b/src/backend/nodes/outfuncs.c
> @@ -1237,6 +1237,59 @@ _outNullIfExpr(StringInfo str, const NullIfExpr *node)
>  }
>  
>  static void
> +_outCachedExpr(StringInfo str, const CachedExpr *node)
> +{
> +	WRITE_NODE_TYPE("CACHEDEXPR");
> +
> +	/* do-it-yourself enum representation; out subexprtype begin... */
> +	appendStringInfoString(str, " :subexprtype ");
> +
> +	switch(node->subexprtype)
> +	{
> +		case CACHED_FUNCEXPR:
> +			{
> +				/* ... out subexprtype end */
> +				outToken(str, "cached_funcexpr");
> +
> +				WRITE_NODE_FIELD(subexpr.funcexpr);
> +			}
> +			break;
> +		case CACHED_OPEXPR:
> +			{
> +				/* ... out subexprtype end */
> +				outToken(str, "cached_opexpr");
> +
> +				WRITE_NODE_FIELD(subexpr.opexpr);
> +			}
> +			break;
> +		case CACHED_DISTINCTEXPR:
> +			{
> +				/* ... out subexprtype end */
> +				outToken(str, "cached_distinctexpr");
> +
> +				WRITE_NODE_FIELD(subexpr.distinctexpr);
> +			}
> +			break;
> +		case CACHED_NULLIFEXPR:
> +			{
> +				/* ... out subexprtype end */
> +				outToken(str, "cached_nullifexpr");
> +
> +				WRITE_NODE_FIELD(subexpr.nullifexpr);
> +			}
> +			break;
> +		case CACHED_SCALARARRAYOPEXPR:
> +			{
> +				/* ... out subexprtype end */
> +				outToken(str, "cached_scalararrayopexpr");
> +
> +				WRITE_NODE_FIELD(subexpr.saopexpr);
> +			}
> +			break;
> +	}
> +}
> +
> +static void
>  _outScalarArrayOpExpr(StringInfo str, const ScalarArrayOpExpr *node)
>  {
>  	WRITE_NODE_TYPE("SCALARARRAYOPEXPR");
> @@ -3767,6 +3820,9 @@ outNode(StringInfo str, const void *obj)
>  			case T_NullIfExpr:
>  				_outNullIfExpr(str, obj);
>  				break;
> +			case T_CachedExpr:
> +				_outCachedExpr(str, obj);
> +				break;
>  			case T_ScalarArrayOpExpr:
>  				_outScalarArrayOpExpr(str, obj);
>  				break;
> diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
> index e24f5d6..acb14f9 100644
> --- a/src/backend/nodes/readfuncs.c
> +++ b/src/backend/nodes/readfuncs.c
> @@ -750,6 +750,52 @@ _readNullIfExpr(void)
>  }
>  
>  /*
> + * _readCachedExpr
> + */
> +static CachedExpr *
> +_readCachedExpr(void)
> +{
> +	READ_LOCALS(CachedExpr);
> +
> +	/* do-it-yourself enum representation */
> +	token = pg_strtok(&length); /* skip :subexprtype */
> +	token = pg_strtok(&length); /* get field value */
> +	if (strncmp(token, "cached_funcexpr", 15) == 0)
> +		local_node->subexprtype = CACHED_FUNCEXPR;
> +	else if (strncmp(token, "cached_opexpr", 13) == 0)
> +		local_node->subexprtype = CACHED_OPEXPR;
> +	else if (strncmp(token, "cached_distinctexpr", 19) == 0)
> +		local_node->subexprtype = CACHED_DISTINCTEXPR;
> +	else if (strncmp(token, "cached_nullifexpr", 17) == 0)
> +		local_node->subexprtype = CACHED_NULLIFEXPR;
> +	else if (strncmp(token, "cached_scalararrayopexpr", 24) == 0)
> +		local_node->subexprtype = CACHED_SCALARARRAYOPEXPR;
> +	else
> +		elog(ERROR, "unrecognized subexprtype \"%.*s\"", length, token);
> +
> +	switch (local_node->subexprtype)
> +	{
> +		case CACHED_FUNCEXPR:
> +			READ_NODE_FIELD(subexpr.funcexpr);
> +			break;
> +		case CACHED_OPEXPR:
> +			READ_NODE_FIELD(subexpr.opexpr);
> +			break;
> +		case CACHED_DISTINCTEXPR:
> +			READ_NODE_FIELD(subexpr.distinctexpr);
> +			break;
> +		case CACHED_NULLIFEXPR:
> +			READ_NODE_FIELD(subexpr.nullifexpr);
> +			break;
> +		case CACHED_SCALARARRAYOPEXPR:
> +			READ_NODE_FIELD(subexpr.saopexpr);
> +			break;
> +	}
> +
> +	READ_DONE();
> +}
> +
> +/*
>   * _readScalarArrayOpExpr
>   */
>  static ScalarArrayOpExpr *
> @@ -2462,6 +2508,8 @@ parseNodeString(void)
>  		return_value = _readDistinctExpr();
>  	else if (MATCH("NULLIFEXPR", 10))
>  		return_value = _readNullIfExpr();
> +	else if (MATCH("CACHEDEXPR", 10))
> +		return_value = _readCachedExpr();
>  	else if (MATCH("SCALARARRAYOPEXPR", 17))
>  		return_value = _readScalarArrayOpExpr();
>  	else if (MATCH("BOOLEXPR", 8))
> diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
> index c4a5651..552b73d 100644
> --- a/src/backend/optimizer/plan/planner.c
> +++ b/src/backend/optimizer/plan/planner.c
> @@ -184,6 +184,7 @@ static PathTarget *make_sort_input_target(PlannerInfo *root,
>  					   bool *have_postponed_srfs);
>  static void adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
>  					  List *targets, List *targets_contain_srfs);
> +static Node *replace_cached_expressions_mutator(Node *node);
>  
>  
>  /*****************************************************************************
> @@ -6086,3 +6087,261 @@ get_partitioned_child_rels(PlannerInfo *root, Index rti)
>  
>  	return result;
>  }
> +
> +static Node *
> +replace_cached_expressions_mutator(Node *node)
> +{
> +	if (node == NULL)
> +		return NULL;
> +
> +	/* mutate certain types of nodes */
> +	if (IsA(node, RestrictInfo))
> +	{
> +		RestrictInfo *rinfo = (RestrictInfo *) node;
> +
> +		/*
> +		 * For an OR clause, recurse into the marked-up tree so that we replace
> +		 * cached expressions for contained RestrictInfos too.
> +		 */
> +		if (rinfo->orclause)
> +			rinfo->orclause = (Expr *) replace_cached_expressions_mutator(
> +				(Node *) rinfo->orclause);
> +		else
> +			rinfo->clause = (Expr *) replace_cached_expressions_mutator(
> +				(Node *) rinfo->clause);
> +
> +		/* do NOT recurse into children */
> +		return node;
> +	}
> +	else if (IsA(node, FuncExpr))
> +	{
> +		/*
> +		 * Function is cached if:
> +		 * 1) it doesn't return set,
> +		 * 2) it's not volatile itself,
> +		 * 3) its arguments are constants or cached expressions too.
> +		 */
> +		FuncExpr   *funcexpr;
> +		ListCell   *arg;
> +		bool		has_nonconst_or_noncached_input = false;
> +		bool		func_returns_set;
> +
> +		/* firstly recurse into children */
> +		funcexpr = (FuncExpr *) expression_tree_mutator(node,
> +											replace_cached_expressions_mutator,
> +											NULL);
> +		func_returns_set = funcexpr->funcretset ||
> +			expression_returns_set((Node *) funcexpr->args);
> +
> +		foreach(arg, funcexpr->args)
> +		{
> +			void	   *arg_lfirst = lfirst(arg);
> +			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
> +				has_nonconst_or_noncached_input = true;
> +		}
> +
> +		if (func_returns_set ||
> +			has_nonconst_or_noncached_input ||
> +			contain_volatile_functions((Node *) &funcexpr->xpr))
> +		{
> +			/* return FuncExpr, which will not be cached */
> +			return (Node *) funcexpr;
> +		}
> +		else
> +		{
> +			/* create and return CachedExpr */
> +			CachedExpr *new_node = makeNode(CachedExpr);
> +			new_node->subexprtype = CACHED_FUNCEXPR;
> +			new_node->subexpr.funcexpr = funcexpr;
> +
> +			return (Node *) new_node;
> +		}	
> +	}
> +	else if (IsA(node, OpExpr))
> +	{
> +		/*
> +		 * Operator is cached if:
> +		 * 1) its function doesn't return set,
> +		 * 1) its function is not volatile itself,
> +		 * 3) its arguments are constants or cached expressions too.
> +		 */
> +		OpExpr     *opexpr = (OpExpr *) node;
> +		ListCell   *arg;
> +		bool		has_nonconst_or_noncached_input = false;
> +		bool		op_returns_set;
> +
> +		/* rely on struct equivalence to treat these all alike */
> +		set_opfuncid(opexpr);
> +
> +		/* firstly recurse into children */
> +		opexpr = (OpExpr *) expression_tree_mutator(node,
> +											replace_cached_expressions_mutator,
> +											NULL);
> +		op_returns_set = opexpr->opretset ||
> +			expression_returns_set((Node *) opexpr->args);
> +
> +		foreach(arg, opexpr->args)
> +		{
> +			void	   *arg_lfirst = lfirst(arg);
> +			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
> +				has_nonconst_or_noncached_input = true;
> +		}
> +
> +		if (op_returns_set ||
> +			has_nonconst_or_noncached_input ||
> +			contain_volatile_functions((Node *) &opexpr->xpr))
> +		{
> +			/* return OpExpr, which will not be cached */
> +			return (Node *) opexpr;
> +		}
> +		else
> +		{
> +			/* create and return CachedExpr */
> +			CachedExpr *new_node = makeNode(CachedExpr);
> +			new_node->subexprtype = CACHED_OPEXPR;
> +			new_node->subexpr.opexpr = opexpr;
> +
> +			return (Node *) new_node;
> +		}
> +	}
> +	else if (IsA(node, DistinctExpr))
> +	{
> +		/*
> +		 * Operator of DistinctExpr is cached if:
> +		 * 1) its function doesn't return set,
> +		 * 1) its function is not volatile itself,
> +		 * 3) its arguments are constants or cached expressions too.
> +		 */
> +		DistinctExpr *distinctexpr = (DistinctExpr *) node;
> +		ListCell   *arg;
> +		bool		has_nonconst_or_noncached_input = false;
> +		bool		op_returns_set;
> +
> +		/* rely on struct equivalence to treat these all alike */
> +		set_opfuncid((OpExpr *) distinctexpr);
> +
> +		/* firstly recurse into children */
> +		distinctexpr = (DistinctExpr *) expression_tree_mutator(node,
> +											replace_cached_expressions_mutator,
> +											NULL);
> +		op_returns_set = distinctexpr->opretset ||
> +			expression_returns_set((Node *) distinctexpr->args);
> +
> +		foreach(arg, distinctexpr->args)
> +		{
> +			void	   *arg_lfirst = lfirst(arg);
> +			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
> +				has_nonconst_or_noncached_input = true;
> +		}
> +
> +		if (op_returns_set ||
> +			has_nonconst_or_noncached_input ||
> +			contain_volatile_functions((Node *) &distinctexpr->xpr))
> +		{
> +			/* return DistinctExpr, which will not be cached */
> +			return (Node *) distinctexpr;
> +		}
> +		else
> +		{
> +			/* create and return CachedExpr */
> +			CachedExpr *new_node = makeNode(CachedExpr);
> +			new_node->subexprtype = CACHED_DISTINCTEXPR;
> +			new_node->subexpr.distinctexpr = distinctexpr;
> +
> +			return (Node *) new_node;
> +		}
> +	}
> +	else if (IsA(node, NullIfExpr))
> +	{
> +		/*
> +		 * Operator of NullIfExpr is cached if:
> +		 * 1) its function doesn't return set,
> +		 * 1) its function is not volatile itself,
> +		 * 3) its arguments are constants or cached expressions too.
> +		 */
> +		NullIfExpr *nullifexpr = (NullIfExpr *) node;
> +		ListCell   *arg;
> +		bool		has_nonconst_or_noncached_input = false;
> +		bool		op_returns_set;
> +
> +		/* rely on struct equivalence to treat these all alike */
> +		set_opfuncid((OpExpr *) nullifexpr);
> +
> +		/* firstly recurse into children */
> +		nullifexpr = (NullIfExpr *) expression_tree_mutator(node,
> +											replace_cached_expressions_mutator,
> +											NULL);
> +		op_returns_set = nullifexpr->opretset ||
> +			expression_returns_set((Node *) nullifexpr->args);
> +
> +		foreach(arg, nullifexpr->args)
> +		{
> +			void	   *arg_lfirst = lfirst(arg);
> +			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
> +				has_nonconst_or_noncached_input = true;
> +		}
> +
> +		if (op_returns_set ||
> +			has_nonconst_or_noncached_input ||
> +			contain_volatile_functions((Node *) &nullifexpr->xpr))
> +		{
> +			/* return NullIfExpr, which will not be cached */
> +			return (Node *) nullifexpr;
> +		}
> +		else
> +		{
> +			/* create and return CachedExpr */
> +			CachedExpr *new_node = makeNode(CachedExpr);
> +			new_node->subexprtype = CACHED_NULLIFEXPR;
> +			new_node->subexpr.nullifexpr = nullifexpr;
> +
> +			return (Node *) new_node;
> +		}
> +	}
> +	else if (IsA(node, ScalarArrayOpExpr))
> +	{
> +		/*
> +		 * Operator of ScalarArrayOpExpr is cached if:
> +		 * 1) its function is not volatile itself,
> +		 * 2) its arguments are constants or cached expressions too.
> +		 * (it returns boolean so we don't need to check if it returns set)
> +		 */
> +		ScalarArrayOpExpr *saopexpr = (ScalarArrayOpExpr *) node;
> +		ListCell   *arg;
> +		bool		has_nonconst_or_noncached_input = false;
> +
> +		set_sa_opfuncid(saopexpr);
> +
> +		/* firstly recurse into children */
> +		saopexpr = (ScalarArrayOpExpr *) expression_tree_mutator(node,
> +											replace_cached_expressions_mutator,
> +											NULL);
> +
> +		foreach(arg, saopexpr->args)
> +		{
> +			void	   *arg_lfirst = lfirst(arg);
> +			if (!(IsA(arg_lfirst, Const) || IsA(arg_lfirst, CachedExpr)))
> +				has_nonconst_or_noncached_input = true;
> +		}
> +
> +		if (has_nonconst_or_noncached_input ||
> +			contain_volatile_functions((Node *) &saopexpr->xpr))
> +		{
> +			/* return ScalarArrayOpExpr, which will not be cached */
> +			return (Node *) saopexpr;
> +		}
> +		else
> +		{
> +			/* create and return CachedExpr */
> +			CachedExpr *new_node = makeNode(CachedExpr);
> +			new_node->subexprtype = CACHED_SCALARARRAYOPEXPR;
> +			new_node->subexpr.saopexpr = saopexpr;
> +
> +			return (Node *) new_node;
> +		}
> +	}
> +
> +	/* otherwise recurse into children */
> +	return expression_tree_mutator(node, replace_cached_expressions_mutator,
> +								   NULL);
> +}
> diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
> index b6c9b48..0dbfa12 100644
> --- a/src/include/nodes/nodeFuncs.h
> +++ b/src/include/nodes/nodeFuncs.h
> @@ -76,5 +76,6 @@ extern bool raw_expression_tree_walker(Node *node, bool (*walker) (),
>  struct PlanState;
>  extern bool planstate_tree_walker(struct PlanState *planstate, bool (*walker) (),
>  											  void *context);
> +extern Node * get_subexpr(CachedExpr *cachedexpr);
>  
>  #endif   /* NODEFUNCS_H */
> diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
> index f59d719..054bc61 100644
> --- a/src/include/nodes/nodes.h
> +++ b/src/include/nodes/nodes.h
> @@ -155,6 +155,7 @@ typedef enum NodeTag
>  	T_OpExpr,
>  	T_DistinctExpr,
>  	T_NullIfExpr,
> +	T_CachedExpr,
>  	T_ScalarArrayOpExpr,
>  	T_BoolExpr,
>  	T_SubLink,
> diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
> index 86ec82e..3f89653 100644
> --- a/src/include/nodes/primnodes.h
> +++ b/src/include/nodes/primnodes.h
> @@ -1498,4 +1498,42 @@ typedef struct OnConflictExpr
>  	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
>  } OnConflictExpr;
>  
> +/*
> + * Discriminator for CachedExpr.
> + *
> + * Identifies the subexpression to be cached in execution (= executed only once
> + * and then used cached value) and which member in the CachedExpr->subexpr union
> + * is valid.
> + */
> +typedef enum CachedSubExprType
> +{
> +	CACHED_FUNCEXPR,			/* cached FuncExpr */
> +	CACHED_OPEXPR,				/* cached OpExpr */
> +	CACHED_DISTINCTEXPR,		/* cached DistinctExpr */
> +	CACHED_NULLIFEXPR,			/* cached NullIfExpr */
> +	CACHED_SCALARARRAYOPEXPR	/* cached ScalarArrayOpExpr */
> +} CachedSubExprType;
> +
> +/*
> + * CachedExpr - expression node for precalculated stable and immutable functions
> + * (= they are calculated once for all output rows, but as many times as
> + * function is mentioned in query), if they don't return a set and their
> + * arguments are constants or recursively precalculated functions. The same for
> + * operators' functions.
> + */
> +typedef struct CachedExpr
> +{
> +	Expr		xpr;
> +	CachedSubExprType subexprtype;  /* expression to be cached */
> +
> +	union SubExpr
> +	{
> +		FuncExpr   *funcexpr;	/* for CACHED_FUNCEXPR */
> +		OpExpr     *opexpr;		/* for CACHED_OPEXPR */
> +		DistinctExpr *distinctexpr; /* for CACHED_DISTINCTEXPR */
> +		NullIfExpr *nullifexpr; /* for CACHED_NULLIFEXPR */
> +		ScalarArrayOpExpr *saopexpr;	/* for CACHED_SCALARARRAYOPEXPR */
> +	} subexpr;
> +} CachedExpr;
> +
>  #endif   /* PRIMNODES_H */
> -- 
> 1.9.1
> 

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

> From 2382fa68414f6bbed42ff66c7abbc3c9b200d244 Mon Sep 17 00:00:00 2001
> From: Marina Polyakova 
> Date: Mon, 15 May 2017 16:05:38 +0300
> Subject: [PATCH v4 3/3] Precalculate stable functions, costs
> 
> Now in Postgresql only immutable functions are precalculated; stable functions
> are calculated for every row so in fact they don't differ from volatile
> functions.
> 
> This patch includes:
> - cost changes for cached expressions (according to their behaviour)
> ---
>  src/backend/optimizer/path/costsize.c | 89 ++++++++++++++++++++++++++---------
>  1 file changed, 67 insertions(+), 22 deletions(-)
> 
> diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
> index 52643d0..505772a 100644
> --- a/src/backend/optimizer/path/costsize.c
> +++ b/src/backend/optimizer/path/costsize.c
> @@ -140,6 +140,7 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root,
>  			   PathKey *pathkey);
>  static void cost_rescan(PlannerInfo *root, Path *path,
>  			Cost *rescan_startup_cost, Cost *rescan_total_cost);
> +static double cost_eval_cacheable_expr_per_tuple(Node *node);
>  static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
>  static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
>  						  ParamPathInfo *param_info,
> @@ -3464,6 +3465,59 @@ cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root)
>  	*cost = context.total;
>  }
>  
> +/*
> + * cost_eval_cacheable_expr_per_tuple
> + *		Evaluate per tuple cost for expressions that can be cacheable.
> + *
> + * This function was created to not duplicate code for some expression and
> + * cached some expression.
> + */
> +static double
> +cost_eval_cacheable_expr_per_tuple(Node *node)
> +{
> +	double result;
> +
> +	/*
> +	 * For each operator or function node in the given tree, we charge the
> +	 * estimated execution cost given by pg_proc.procost (remember to multiply
> +	 * this by cpu_operator_cost).
> +	 */
> +	if (IsA(node, FuncExpr))
> +	{
> +		result = get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
> +	}
> +	else if (IsA(node, OpExpr) ||
> +			 IsA(node, DistinctExpr) ||
> +			 IsA(node, NullIfExpr))
> +	{
> +		OpExpr     *opexpr = (OpExpr *) node;
> +
> +		/* rely on struct equivalence to treat these all alike */
> +		set_opfuncid(opexpr);
> +
> +		result = get_func_cost(opexpr->opfuncid) * cpu_operator_cost;
> +	}
> +	else if (IsA(node, ScalarArrayOpExpr))
> +	{
> +		/*
> +		 * Estimate that the operator will be applied to about half of the
> +		 * array elements before the answer is determined.
> +		 */
> +		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
> +		Node	   *arraynode = (Node *) lsecond(saop->args);
> +
> +		set_sa_opfuncid(saop);
> +		result = get_func_cost(saop->opfuncid) * cpu_operator_cost *
> +			estimate_array_length(arraynode) * 0.5;
> +	}
> +	else
> +	{
> +		elog(ERROR, "non cacheable expression node type: %d", (int) nodeTag(node));
> +	}
> +
> +	return result;
> +}
> +
>  static bool
>  cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
>  {
> @@ -3537,32 +3591,23 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
>  	 * moreover, since our rowcount estimates for functions tend to be pretty
>  	 * phony, the results would also be pretty phony.
>  	 */
> -	if (IsA(node, FuncExpr))
> +	if (IsA(node, FuncExpr) ||
> +		IsA(node, OpExpr) ||
> +		IsA(node, DistinctExpr) ||
> +		IsA(node, NullIfExpr) ||
> +		IsA(node, ScalarArrayOpExpr))
>  	{
> -		context->total.per_tuple +=
> -			get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
> +		context->total.per_tuple += cost_eval_cacheable_expr_per_tuple(node);
>  	}
> -	else if (IsA(node, OpExpr) ||
> -			 IsA(node, DistinctExpr) ||
> -			 IsA(node, NullIfExpr))
> -	{
> -		/* rely on struct equivalence to treat these all alike */
> -		set_opfuncid((OpExpr *) node);
> -		context->total.per_tuple +=
> -			get_func_cost(((OpExpr *) node)->opfuncid) * cpu_operator_cost;
> -	}
> -	else if (IsA(node, ScalarArrayOpExpr))
> -	{
> +	else if (IsA(node, CachedExpr))
> +	{	
>  		/*
> -		 * Estimate that the operator will be applied to about half of the
> -		 * array elements before the answer is determined.
> +		 * Calculate subexpression cost per tuple as usual and add it to startup
> +		 * cost (because subexpression will be executed only once for all
> +		 * tuples).
>  		 */
> -		ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
> -		Node	   *arraynode = (Node *) lsecond(saop->args);
> -
> -		set_sa_opfuncid(saop);
> -		context->total.per_tuple += get_func_cost(saop->opfuncid) *
> -			cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
> +		context->total.startup += cost_eval_cacheable_expr_per_tuple(
> +			get_subexpr((CachedExpr *) node));
>  	}
>  	else if (IsA(node, Aggref) ||
>  			 IsA(node, WindowFunc))
> -- 
> 1.9.1
> 


-- 
Best regards,
Aleksander Alekseev
В списке pgsql-hackers по дате отправления
От: Peter Eisentraut
Дата:
От: Andres Freund
Дата:
FAQ