From 719e60d1ddbb9b3d7a13cdc62e17fd4995c1b8ca Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Sun, 18 Oct 2026 20:55:27 -0400 Subject: [PATCH v2] Handle failed typanalyze when building extended statistics make_build_data() and build_expr_data() call examine_expression() with the computed statistics target and did not check for a NULL result. A typanalyze function that rejects the expression in that call made ANALYZE crash. Skip the statistics object with the usual warning instead. Discussion: https://postgr.es/m/19763-14da3f96c02f07e8@postgresql.org --- src/backend/statistics/extended_stats.c | 53 ++++++++++++++++--- .../expected/test_custom_types.out | 18 +++++++ .../sql/test_custom_types.sql | 14 +++++ .../test_custom_types--1.0.sql | 8 +++ .../test_custom_types/test_custom_types.c | 20 +++++++ 5 files changed, 105 insertions(+), 8 deletions(-) diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index 515095faa4b..887e7e78c0d 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -95,6 +95,7 @@ static void compute_expr_stats(Relation onerel, AnlExprData *exprdata, static Datum serialize_expr_stats(AnlExprData *exprdata, int nexprs); static Datum expr_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); static AnlExprData *build_expr_data(List *exprs, int stattarget); +static void report_ext_stats_not_computed(Relation onerel, StatExtEntry *stat); static StatsBuildData *make_build_data(Relation rel, StatExtEntry *stat, int numrows, HeapTuple *rows, @@ -170,14 +171,7 @@ BuildRelationExtStatistics(Relation onerel, bool inh, double totalrows, natts, vacattrstats); if (!stats) { - if (!AmAutoVacuumWorkerProcess()) - ereport(WARNING, - (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("statistics object \"%s.%s\" could not be computed for relation \"%s.%s\"", - stat->schema, stat->name, - get_namespace_name(onerel->rd_rel->relnamespace), - RelationGetRelationName(onerel)), - errtable(onerel))); + report_ext_stats_not_computed(onerel, stat); continue; } @@ -196,6 +190,11 @@ BuildRelationExtStatistics(Relation onerel, bool inh, double totalrows, /* evaluate expressions (if the statistics object has any) */ data = make_build_data(onerel, stat, numrows, rows, stats, stattarget); + if (!data) + { + report_ext_stats_not_computed(onerel, stat); + continue; + } /* compute statistic of each requested type */ foreach(lc2, stat->types) @@ -218,6 +217,12 @@ BuildRelationExtStatistics(Relation onerel, bool inh, double totalrows, elog(ERROR, "requested expression stats, but there are no expressions"); exprdata = build_expr_data(stat->exprs, stattarget); + if (!exprdata) + { + report_ext_stats_not_computed(onerel, stat); + data = NULL; + break; + } nexprs = list_length(stat->exprs); compute_expr_stats(onerel, exprdata, nexprs, rows, numrows); @@ -226,6 +231,9 @@ BuildRelationExtStatistics(Relation onerel, bool inh, double totalrows, } } + if (!data) + continue; + /* store the statistics in the catalog */ statext_store(stat->statOid, inh, ndistinct, dependencies, mcv, exprstats, stats); @@ -246,6 +254,22 @@ BuildRelationExtStatistics(Relation onerel, bool inh, double totalrows, table_close(pg_stext, RowExclusiveLock); } +/* + * Warn that a statistics object could not be built for this relation. + */ +static void +report_ext_stats_not_computed(Relation onerel, StatExtEntry *stat) +{ + if (!AmAutoVacuumWorkerProcess()) + ereport(WARNING, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("statistics object \"%s.%s\" could not be computed for relation \"%s.%s\"", + stat->schema, stat->name, + get_namespace_name(onerel->rd_rel->relnamespace), + RelationGetRelationName(onerel)), + errtable(onerel))); +} + /* * Test if the given relation has extended statistics objects. */ @@ -2303,6 +2327,11 @@ build_expr_data(List *exprs, int stattarget) thisdata->expr = expr; thisdata->vacattrstat = examine_expression(expr, stattarget); + if (thisdata->vacattrstat == NULL) + { + pfree(exprdata); + return NULL; + } idx++; } @@ -2496,6 +2525,8 @@ statext_expressions_load(Oid stxoid, bool inh, int idx) * Evaluate the expressions, so that we can use the results to build * all the requested statistics types. This matters especially for * expensive expressions, of course. + * + * Returns NULL if one of the expressions cannot be analyzed. */ static StatsBuildData * make_build_data(Relation rel, StatExtEntry *stat, int numrows, HeapTuple *rows, @@ -2585,6 +2616,12 @@ make_build_data(Relation rel, StatExtEntry *stat, int numrows, HeapTuple *rows, result->attnums[idx] = k; result->stats[idx] = examine_expression(expr, stattarget); + if (result->stats[idx] == NULL) + { + pfree(result); + return NULL; + } + idx++; k--; } diff --git a/src/test/modules/test_custom_types/expected/test_custom_types.out b/src/test/modules/test_custom_types/expected/test_custom_types.out index 59f0917c2d7..a654276e762 100644 --- a/src/test/modules/test_custom_types/expected/test_custom_types.out +++ b/src/test/modules/test_custom_types/expected/test_custom_types.out @@ -173,6 +173,24 @@ SELECT COUNT(*) FROM test_table GROUP BY (func_int_custom(data)); 1 (3 rows) +DROP STATISTICS test_stats; +-- Switch type to use typanalyze function that depends on attstattarget. +ALTER TYPE int_custom SET (ANALYZE = int_custom_typanalyze_stattarget); +-- Extended statistics with an expression accepted by the first lookup +-- but rejected when the statistics are built. +CREATE STATISTICS test_stats ON func_int_custom(data), (id) FROM test_table; +-- Computation of the stats fails, no data generated. +ANALYZE test_table; +WARNING: statistics object "public.test_stats" could not be computed for relation "public.test_table" +SELECT stxname, stxdexpr IS NULL as expr_stats_is_null + FROM pg_statistic_ext s + LEFT JOIN pg_statistic_ext_data d ON s.oid = d.stxoid + WHERE stxname = 'test_stats'; + stxname | expr_stats_is_null +------------+-------------------- + test_stats | t +(1 row) + DROP STATISTICS test_stats; -- Cleanup DROP FUNCTION func_int_custom; diff --git a/src/test/modules/test_custom_types/sql/test_custom_types.sql b/src/test/modules/test_custom_types/sql/test_custom_types.sql index f1c75e4ba5b..0f909e2468e 100644 --- a/src/test/modules/test_custom_types/sql/test_custom_types.sql +++ b/src/test/modules/test_custom_types/sql/test_custom_types.sql @@ -101,6 +101,20 @@ SELECT tablename, SELECT COUNT(*) FROM test_table GROUP BY (func_int_custom(data)); DROP STATISTICS test_stats; +-- Switch type to use typanalyze function that depends on attstattarget. +ALTER TYPE int_custom SET (ANALYZE = int_custom_typanalyze_stattarget); + +-- Extended statistics with an expression accepted by the first lookup +-- but rejected when the statistics are built. +CREATE STATISTICS test_stats ON func_int_custom(data), (id) FROM test_table; +-- Computation of the stats fails, no data generated. +ANALYZE test_table; +SELECT stxname, stxdexpr IS NULL as expr_stats_is_null + FROM pg_statistic_ext s + LEFT JOIN pg_statistic_ext_data d ON s.oid = d.stxoid + WHERE stxname = 'test_stats'; +DROP STATISTICS test_stats; + -- Cleanup DROP FUNCTION func_int_custom; DROP TABLE test_table; diff --git a/src/test/modules/test_custom_types/test_custom_types--1.0.sql b/src/test/modules/test_custom_types/test_custom_types--1.0.sql index ce0e905d636..aa680880e3a 100644 --- a/src/test/modules/test_custom_types/test_custom_types--1.0.sql +++ b/src/test/modules/test_custom_types/test_custom_types--1.0.sql @@ -32,6 +32,14 @@ RETURNS boolean AS 'MODULE_PATHNAME' LANGUAGE C STRICT; +-- +-- Typanalyze function whose result depends on attstattarget +-- +CREATE FUNCTION int_custom_typanalyze_stattarget(internal) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + -- -- The int_custom type definition -- diff --git a/src/test/modules/test_custom_types/test_custom_types.c b/src/test/modules/test_custom_types/test_custom_types.c index 41262044b47..4b5fd504776 100644 --- a/src/test/modules/test_custom_types/test_custom_types.c +++ b/src/test/modules/test_custom_types/test_custom_types.c @@ -28,6 +28,7 @@ PG_FUNCTION_INFO_V1(int_custom_in); PG_FUNCTION_INFO_V1(int_custom_out); PG_FUNCTION_INFO_V1(int_custom_typanalyze_false); PG_FUNCTION_INFO_V1(int_custom_typanalyze_invalid); +PG_FUNCTION_INFO_V1(int_custom_typanalyze_stattarget); PG_FUNCTION_INFO_V1(int_custom_eq); PG_FUNCTION_INFO_V1(int_custom_ne); PG_FUNCTION_INFO_V1(int_custom_lt); @@ -110,6 +111,25 @@ int_custom_typanalyze_invalid(PG_FUNCTION_ARGS) PG_RETURN_BOOL(true); } +/* + * int_custom_typanalyze_stattarget + * + * This function accepts the column only when attstattarget is negative, + * and returns false otherwise. For expressions in extended statistics, + * the first lookup passes -1 while the build passes the computed target, + * so the two calls give different answers. + */ +Datum +int_custom_typanalyze_stattarget(PG_FUNCTION_ARGS) +{ + VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0); + + if (stats->attstattarget >= 0) + PG_RETURN_BOOL(false); + + PG_RETURN_BOOL(std_typanalyze(stats)); +} + /* * Comparison functions for int_custom type */ -- 2.37.1 (Apple Git-137.1)