diff options
| author | David Rowley <drowley@postgresql.org> | 2025-11-27 10:43:28 +1300 |
|---|---|---|
| committer | David Rowley <drowley@postgresql.org> | 2025-11-27 10:43:28 +1300 |
| commit | 42473b3b31238b15cc3c030b4416b2ee79508d8c (patch) | |
| tree | 41e1c0dd763abfaa6db3cf9fc1fe7d738dd8164b /src/backend | |
| parent | dbdc717ac6743074c3a55fc5c380638c91d24afd (diff) | |
Have the planner replace COUNT(ANY) with COUNT(*), when possible
This adds SupportRequestSimplifyAggref to allow pg_proc.prosupport
functions to receive an Aggref and allow them to determine if there is a
way that the Aggref call can be optimized.
Also added is a support function to allow transformation of COUNT(ANY)
into COUNT(*). This is possible to do when the given "ANY" cannot be
NULL and also that there are no ORDER BY / DISTINCT clauses within the
Aggref. This is a useful transformation to do as it is common that
people write COUNT(1), which until now has added unneeded overhead.
When counting a NOT NULL column. The overheads can be worse as that
might mean deforming more of the tuple, which for large fact tables may
be many columns in.
It may be possible to add prosupport functions for other aggregates. We
could consider if ORDER BY could be dropped for some calls, e.g. the
ORDER BY is quite useless in MAX(c ORDER BY c).
There is a little bit of passing fallout from adjusting
expr_is_nonnullable() to handle Const which results in a plan change in
the aggregates.out regression test. Previously, nothing was able to
determine that "One-Time Filter: (100 IS NOT NULL)" was always true,
therefore useless to include in the plan.
Author: David Rowley <dgrowleyml@gmail.com>
Reviewed-by: Corey Huinker <corey.huinker@gmail.com>
Reviewed-by: Matheus Alcantara <matheusssilv97@gmail.com>
Discussion: https://postgr.es/m/CAApHDvqGcPTagXpKfH=CrmHBqALpziThJEDs_MrPqjKVeDF9wA@mail.gmail.com
Diffstat (limited to 'src/backend')
| -rw-r--r-- | src/backend/optimizer/plan/initsplan.c | 20 | ||||
| -rw-r--r-- | src/backend/optimizer/util/clauses.c | 73 | ||||
| -rw-r--r-- | src/backend/utils/adt/int8.c | 49 |
3 files changed, 123 insertions, 19 deletions
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 65d473d95b6..671c5cde8fc 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -3414,22 +3414,6 @@ add_base_clause_to_rel(PlannerInfo *root, Index relid, } /* - * expr_is_nonnullable - * Check to see if the Expr cannot be NULL - * - * Currently we only support simple Vars. - */ -static bool -expr_is_nonnullable(PlannerInfo *root, Expr *expr) -{ - /* For now only check simple Vars */ - if (!IsA(expr, Var)) - return false; - - return var_is_nonnullable(root, (Var *) expr, true); -} - -/* * restriction_is_always_true * Check to see if the RestrictInfo is always true. * @@ -3465,7 +3449,7 @@ restriction_is_always_true(PlannerInfo *root, if (nulltest->argisrow) return false; - return expr_is_nonnullable(root, nulltest->arg); + return expr_is_nonnullable(root, nulltest->arg, true); } /* If it's an OR, check its sub-clauses */ @@ -3530,7 +3514,7 @@ restriction_is_always_false(PlannerInfo *root, if (nulltest->argisrow) return false; - return expr_is_nonnullable(root, nulltest->arg); + return expr_is_nonnullable(root, nulltest->arg, true); } /* If it's an OR, check its sub-clauses */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 202ba8ed4bb..9975185934b 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -131,6 +131,8 @@ static Expr *simplify_function(Oid funcid, Oid result_collid, Oid input_collid, List **args_p, bool funcvariadic, bool process_args, bool allow_non_const, eval_const_expressions_context *context); +static Node *simplify_aggref(Aggref *aggref, + eval_const_expressions_context *context); static List *reorder_function_arguments(List *args, int pronargs, HeapTuple func_tuple); static List *add_function_defaults(List *args, int pronargs, @@ -2634,6 +2636,9 @@ eval_const_expressions_mutator(Node *node, newexpr->location = expr->location; return (Node *) newexpr; } + case T_Aggref: + node = ece_generic_processing(node); + return simplify_aggref((Aggref *) node, context); case T_OpExpr: { OpExpr *expr = (OpExpr *) node; @@ -4201,6 +4206,50 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, } /* + * simplify_aggref + * Call the Aggref.aggfnoid's prosupport function to allow it to + * determine if simplification of the Aggref is possible. Returns the + * newly simplified node if conversion took place; otherwise, returns the + * original Aggref. + * + * See SupportRequestSimplifyAggref comments in supportnodes.h for further + * details. + */ +static Node * +simplify_aggref(Aggref *aggref, eval_const_expressions_context *context) +{ + Oid prosupport = get_func_support(aggref->aggfnoid); + + if (OidIsValid(prosupport)) + { + SupportRequestSimplifyAggref req; + Node *newnode; + + /* + * Build a SupportRequestSimplifyAggref node to pass to the support + * function. + */ + req.type = T_SupportRequestSimplifyAggref; + req.root = context->root; + req.aggref = aggref; + + newnode = (Node *) DatumGetPointer(OidFunctionCall1(prosupport, + PointerGetDatum(&req))); + + /* + * We expect the support function to return either a new Node or NULL + * (when simplification isn't possible). + */ + Assert(newnode != (Node *) aggref || newnode == NULL); + + if (newnode != NULL) + return newnode; + } + + return (Node *) aggref; +} + +/* * var_is_nonnullable: check to see if the Var cannot be NULL * * If the Var is defined NOT NULL and meanwhile is not nulled by any outer @@ -4262,6 +4311,30 @@ var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info) } /* + * expr_is_nonnullable: check to see if the Expr cannot be NULL + * + * Returns true iff the given 'expr' cannot produce SQL NULLs. + * + * If 'use_rel_info' is true, nullability of Vars is checked via the + * corresponding RelOptInfo for the given Var. Some callers require + * nullability information before RelOptInfos are generated. These should + * pass 'use_rel_info' as false. + * + * For now, we only support Var and Const. Support for other node types may + * be possible. + */ +bool +expr_is_nonnullable(PlannerInfo *root, Expr *expr, bool use_rel_info) +{ + if (IsA(expr, Var)) + return var_is_nonnullable(root, (Var *) expr, use_rel_info); + if (IsA(expr, Const)) + return !castNode(Const, expr)->constisnull; + + return false; +} + +/* * expand_function_arguments: convert named-notation args to positional args * and/or insert default args, as needed * diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index bdea490202a..9cd420b4b9d 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -24,7 +24,7 @@ #include "nodes/supportnodes.h" #include "optimizer/optimizer.h" #include "utils/builtins.h" - +#include "utils/fmgroids.h" typedef struct { @@ -811,6 +811,53 @@ int8inc_support(PG_FUNCTION_ARGS) PG_RETURN_POINTER(req); } + if (IsA(rawreq, SupportRequestSimplifyAggref)) + { + SupportRequestSimplifyAggref *req = (SupportRequestSimplifyAggref *) rawreq; + Aggref *agg = req->aggref; + + /* + * Check for COUNT(ANY) and try to convert to COUNT(*). The input + * argument cannot be NULL, we can't have an ORDER BY / DISTINCT in + * the aggregate, and agglevelsup must be 0. + * + * Technically COUNT(ANY) must have 1 arg, but be paranoid and check. + */ + if (agg->aggfnoid == F_COUNT_ANY && list_length(agg->args) == 1) + { + TargetEntry *tle = (TargetEntry *) linitial(agg->args); + Expr *arg = tle->expr; + + /* Check for unsupported cases */ + if (agg->aggdistinct != NIL || agg->aggorder != NIL || + agg->agglevelsup != 0) + PG_RETURN_POINTER(NULL); + + /* If the arg isn't NULLable, do the conversion */ + if (expr_is_nonnullable(req->root, arg, false)) + { + Aggref *newagg; + + /* We don't expect these to have been set yet */ + Assert(agg->aggtransno == -1); + Assert(agg->aggtranstype == InvalidOid); + + /* Convert COUNT(ANY) to COUNT(*) by making a new Aggref */ + newagg = makeNode(Aggref); + memcpy(newagg, agg, sizeof(Aggref)); + newagg->aggfnoid = F_COUNT_; + + /* count(*) has no args */ + newagg->aggargtypes = NULL; + newagg->args = NULL; + newagg->aggstar = true; + newagg->location = -1; + + PG_RETURN_POINTER(newagg); + } + } + } + PG_RETURN_POINTER(NULL); } |
