You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudberry.apache.org by ch...@apache.org on 2026/05/04 03:38:53 UTC
(cloudberry) 14/26: Fix colNDVBySeg attnum index mismatch in column-specific ANALYZE (#1680)
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 <yj...@gmail.com>
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: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org