Hi Marina,

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

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

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

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

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

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

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


-- 
Best regards,
Aleksander Alekseev

Attachment: signature.asc
Description: PGP signature

Reply via email to