Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1 - Mailing list pgsql-hackers

From Aleksander Alekseev
Subject Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1
Date
Msg-id 20170530155550.GB10525@e733.localdomain
Whole thread Raw
In response to Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1  (Marina Polyakova <m.polyakova@postgrespro.ru>)
Responses Re: [HACKERS] WIP Patch: Precalculate stable functions,infrastructure v1  (Marina Polyakova <m.polyakova@postgrespro.ru>)
List pgsql-hackers
Hi Marina,

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

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

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

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

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

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

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


--
Best regards,
Aleksander Alekseev

pgsql-hackers by date:

Previous
From: Peter Eisentraut
Date:
Subject: Re: [HACKERS] ALTER PUBLICATION documentation
Next
From: Andres Freund
Date:
Subject: Re: [HACKERS] [PATCH] relocation truncated to fit: citus buildfailure on s390x