postgres.git / summary / log / commit / refs

commit    f033abc6c4f2639f0e498ff256f6c9407793abbb
Author:   Michael Paquier <michael@paquier.xyz>
Date:     Mon Mar 02 00:38:46 2026 +0000

    Fix set of issues with extended statistics on expressions
    
    This commit addresses two defects regarding extended statistics on
    expressions:
    - When building extended statistics in lookup_var_attr_stats(), the call
    to examine_attribute() did not account for the possibility of a NULL
    return value.  This can happen depending on the behavior of a typanalyze
    callback — for example, if the callback returns false, if no rows are
    sampled, or if no statistics are computed.  In such cases, the code
    attempted to build MCV, dependency, and ndistinct statistics using a
    NULL pointer, incorrectly assuming valid statistics were available,
    which could lead to a server crash.
    - When loading extended statistics for expressions,
    statext_expressions_load() did not account for NULL entries in the
    pg_statistic array storing expression statistics.  Such NULL entries can
    be generated when statistics collection fails for an expression, as may
    occur during the final step of serialize_expr_stats().  An extended
    statistics object defining N expressions requires N corresponding
    elements in the pg_statistic array stored for the expressions, and some
    of these elements can be NULL.  This situation is reachable when a
    typanalyze callback returns true, but sets stats_valid to indicate that
    no useful statistics could be computed.
    
    While these scenarios cannot occur with in-core typanalyze callbacks, as
    far as I have analyzed, they can be triggered by custom data types with
    custom typanalyze implementations, at least.
    
    No tests are added in this commit.  A follow-up commit will introduce a
    test module that can be extended to cover similar edge cases if
    additional issues are discovered.  This takes care of the core of the
    problem.
    
    Attribute and relation statistics already offer similar protections:
    - ANALYZE detects and skips the build of invalid statistics.
    - Invalid catalog data is handled defensively when loading statistics.
    
    This issue exists since the support for extended statistics on
    expressions has been added, down to v14 as of a4d75c86bf15.  Backpatch
    to all supported stable branches.
    
    Author: Michael Paquier <michael@paquier.xyz>
    Reviewed-by: Corey Huinker <corey.huinker@gmail.com>
    Reviewed-by: Chao Li <li.evan.chao@gmail.com>
    Discussion: https://postgr.es/m/aaDrJsE1I5mrE-QF@paquier.xyz
    Backpatch-through: 14


src/backend/statistics/extended_stats.c | 20 ++++++++++++++++++++ src/backend/utils/adt/selfuncs.c | 6 +++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index 31aab79a8fc..3a3e8ef7fb8 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -764,6 +764,16 @@ lookup_var_attr_stats(Relation rel, Bitmapset *attrs, List *exprs, stats[i] = examine_attribute(expr); + /* + * If the expression has been found as non-analyzable, give up. We + * will not be able to build extended stats with it. + */ + if (stats[i] == NULL) + { + pfree(stats); + return NULL; + } + /* * XXX We need tuple descriptor later, and we just grab it from * stats[0]->tupDesc (see e.g. statext_mcv_build). But as coded @@ -2429,6 +2439,9 @@ serialize_expr_stats(AnlExprData *exprdata, int nexprs) /* * Loads pg_statistic record from expression statistics for expression * identified by the supplied index. + * + * Returns the pg_statistic record found, or NULL if there is no statistics + * data to use. */ HeapTuple statext_expressions_load(Oid stxoid, bool inh, int idx) @@ -2457,6 +2470,13 @@ statext_expressions_load(Oid stxoid, bool inh, int idx) deconstruct_expanded_array(eah); + if (eah->dnulls && eah->dnulls[idx]) + { + /* No data found for this expression, give up. */ + ReleaseSysCache(htup); + return NULL; + } + td = DatumGetHeapTupleHeader(eah->dvalues[idx]); /* Build a temporary HeapTuple control structure */ diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 829aa0128d9..e7f3bc9b6ee 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -5222,7 +5222,11 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, vardata->statsTuple = statext_expressions_load(info->statOid, rte->inh, pos); - vardata->freefunc = ReleaseDummy; + /* Nothing to release if no data found */ + if (vardata->statsTuple != NULL) + { + vardata->freefunc = ReleaseDummy; + } /* * Test if user has permission to access all rows from the [parent: cf370919a996]