This is an automated email from the ASF dual-hosted git repository. oppenheimer01 pushed a commit to branch cbdb-postgres-merge in repository https://gitbox.apache.org/repos/asf/cloudberry.git
commit 108f3b086ad1dad6764faca53e99487d0aca3db9 Author: Jianghua.yjh <[email protected]> AuthorDate: Wed Apr 22 20:38:59 2026 -0700 Fix colNDVBySeg attnum index mismatch in column-specific ANALYZE (#1680) * Fix colNDVBySeg index mismatch in do_analyze_rel When ANALYZE is run on specific columns (e.g., ANALYZE t (col)) or when a table has dropped columns, the vacattrstats loop index `i` diverges from the attribute's actual attnum-1 index used by colNDVBySeg. Two fixes: 1. QD side (line 887): read colNDVBySeg[attnum-1] instead of colNDVBySeg[i] when storing stadistinctbyseg. 2. Segment side (line 1011): write ctx->stadistincts[attnum-1] instead of ctx->stadistincts[i] when collecting per-segment NDV. * Add regression test for colNDVBySeg index mismatch in do_analyze_rel ANALYZE t(b) puts column b at loop index i=0 on the QD, but b has attnum=2, so attnum-1=1 != i=0. The fix in do_analyze_rel (using attnum-1 instead of i to index colNDVBySeg) ensures stadistinctbyseg is read from the correct per-segment NDV slot. Test verifies stadistinctbyseg for column b equals 100 (all distinct) rather than ~5 (NDV of column a at index 0). --- src/backend/commands/analyze.c | 4 ++-- src/test/regress/expected/analyze.out | 27 +++++++++++++++++++++++++++ src/test/regress/sql/analyze.sql | 23 +++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index cdfa82a9904..90a5f9ed0bd 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -914,7 +914,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params, if (Gp_role == GP_ROLE_DISPATCH && GpPolicyIsPartitioned(onerel->rd_cdbpolicy)) { - stats->stadistinctbyseg = colNDVBySeg[i]; + stats->stadistinctbyseg = colNDVBySeg[stats->attr->attnum - 1]; } stats->tupDesc = onerel->rd_att; @@ -1038,7 +1038,7 @@ do_analyze_rel(Relation onerel, VacuumParams *params, if (Gp_role == GP_ROLE_EXECUTE) { Assert(ctx->stadistincts); - ctx->stadistincts[i] = Float8GetDatum(stats->stadistinct); + ctx->stadistincts[stats->attr->attnum - 1] = Float8GetDatum(stats->stadistinct); } MemoryContextResetAndDeleteChildren(col_context); diff --git a/src/test/regress/expected/analyze.out b/src/test/regress/expected/analyze.out index 294cc4daa16..78f1bc05f7e 100644 --- a/src/test/regress/expected/analyze.out +++ b/src/test/regress/expected/analyze.out @@ -1317,3 +1317,30 @@ select * from pg_stats where tablename like 'part2'; (1 row) drop table multipart cascade; +-- +-- Test column-specific ANALYZE correctly uses attnum-based NDV index (not loop index). +-- When ANALYZE t(b) is run, the QD loop has i=0 for column b (attnum=2), +-- so attnum-1=1 != i=0. Without the fix, colNDVBySeg[i=0] reads column a's NDV +-- instead of column b's NDV. +-- +CREATE TABLE analyze_col_ndv_drop (a int, b int, c int) DISTRIBUTED BY (a); +INSERT INTO analyze_col_ndv_drop SELECT i%5, i, i%50 FROM generate_series(1, 100) i; +-- ANALYZE specific column b: QD loop has i=0, b.attnum=2, so attnum-1=1 != i=0 +ANALYZE analyze_col_ndv_drop (b); +-- stadistinctbyseg for b should be 100 (all distinct), not ~5 (NDV of column a at index 0) +SELECT a.attname, + CASE WHEN s.stakind1 = 8 THEN array_to_string(s.stavalues1, ',') + WHEN s.stakind2 = 8 THEN array_to_string(s.stavalues2, ',') + WHEN s.stakind3 = 8 THEN array_to_string(s.stavalues3, ',') + WHEN s.stakind4 = 8 THEN array_to_string(s.stavalues4, ',') + WHEN s.stakind5 = 8 THEN array_to_string(s.stavalues5, ',') + END AS stadistinctbyseg +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'analyze_col_ndv_drop'::regclass AND a.attname = 'b'; + attname | stadistinctbyseg +---------+------------------ + b | 100 +(1 row) + +DROP TABLE analyze_col_ndv_drop; diff --git a/src/test/regress/sql/analyze.sql b/src/test/regress/sql/analyze.sql index fa2ce8b834f..6d1c7ddd159 100644 --- a/src/test/regress/sql/analyze.sql +++ b/src/test/regress/sql/analyze.sql @@ -677,3 +677,26 @@ analyze verbose p2; select * from pg_stats where tablename like 'part2'; drop table multipart cascade; + +-- +-- Test column-specific ANALYZE correctly uses attnum-based NDV index (not loop index). +-- When ANALYZE t(b) is run, the QD loop has i=0 for column b (attnum=2), +-- so attnum-1=1 != i=0. Without the fix, colNDVBySeg[i=0] reads column a's NDV +-- instead of column b's NDV. +-- +CREATE TABLE analyze_col_ndv_drop (a int, b int, c int) DISTRIBUTED BY (a); +INSERT INTO analyze_col_ndv_drop SELECT i%5, i, i%50 FROM generate_series(1, 100) i; +-- ANALYZE specific column b: QD loop has i=0, b.attnum=2, so attnum-1=1 != i=0 +ANALYZE analyze_col_ndv_drop (b); +-- stadistinctbyseg for b should be 100 (all distinct), not ~5 (NDV of column a at index 0) +SELECT a.attname, + CASE WHEN s.stakind1 = 8 THEN array_to_string(s.stavalues1, ',') + WHEN s.stakind2 = 8 THEN array_to_string(s.stavalues2, ',') + WHEN s.stakind3 = 8 THEN array_to_string(s.stavalues3, ',') + WHEN s.stakind4 = 8 THEN array_to_string(s.stavalues4, ',') + WHEN s.stakind5 = 8 THEN array_to_string(s.stavalues5, ',') + END AS stadistinctbyseg +FROM pg_statistic s +JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum +WHERE s.starelid = 'analyze_col_ndv_drop'::regclass AND a.attname = 'b'; +DROP TABLE analyze_col_ndv_drop; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
