Re: WIP Patch: Precalculate stable functions, infrastructure v1

From: Aleksander Alekseev <a(dot)alekseev(at)postgrespro(dot)ru>
To: Marina Polyakova <m(dot)polyakova(at)postgrespro(dot)ru>
Cc: Andres Freund <andres(at)anarazel(dot)de>, Alexander Korotkov <a(dot)korotkov(at)postgrespro(dot)ru>, pgsql-hackers <pgsql-hackers(at)postgresql(dot)org>, Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Subject: Re: WIP Patch: Precalculate stable functions, infrastructure v1
Date: 2017-05-30 15:55:51
Message-ID: 20170530155550.GB10525@e733.localdomain
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: 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(dot)polyakova(at)postgrespro(dot)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(dot)polyakova(at)postgrespro(dot)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(dot)polyakova(at)postgrespro(dot)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

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Andres Freund 2017-05-30 16:15:41 Re: [HACKERS] [PATCH] relocation truncated to fit: citus build failure on s390x
Previous Message Peter Eisentraut 2017-05-30 15:51:37 Re: ALTER PUBLICATION documentation