This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new a19d0f788eb branch-4.1: [test](nereids) Replace flaky null stats case
with unit test (#65656)
a19d0f788eb is described below
commit a19d0f788eb9529e61991061e93058c7da15f42d
Author: shuke <[email protected]>
AuthorDate: Thu Jul 16 11:03:35 2026 +0800
branch-4.1: [test](nereids) Replace flaky null stats case with unit test
(#65656)
### What problem does this PR solve?
Related PR: #65445
Problem Summary:
Backport #65445 to branch-4.1.
The regression case `test_scale_num_nulls` depends on asynchronously
visible physical row counts. When the physical row count is still 0,
Nereids scales the selected-partition column statistics to 0 even though
the injected partition/table statistics are correct, making the case
flaky.
Replace the environment-dependent Groovy regression case with a
deterministic FE unit test that directly constructs the partition/table
statistics and verifies selected-partition scaling, including row count,
NDV, min/max, count, and numNulls.
### Coverage rationale
- This is a branch backport of merged PR #65445. The same planner-level
coverage concern was raised in [the upstream review
thread](https://github.com/apache/doris/pull/65445#discussion_r3556961223),
then explicitly resolved by a maintainer before the PR was approved and
merged.
- The independent behavior introduced with #62265 is selected-partition
statistics scaling. The replacement test invokes the real
`StatsCalculator.computeOlapScan()` path and verifies row count, NDV,
min/max, count, and `numNulls`. SQL predicate-to-partition selection
remains covered by `PruneOlapScanPartitionTest` on this release branch.
- The removed cluster case sampled the strict physical partition row
count immediately after insert/analyze. In Cloud P0 that value can
remain 0 for up to two minutes even when table-level column statistics
are correct. Retaining the case or waiting for that report would
preserve the flake or add up to two minutes to the suite.
- `SHOW COLUMN STATS` was only failure diagnostics, and memo text
formatting was not an independent assertion target of this case.
### Release note
None
### Check List (For Author)
- Test
- [ ] Regression test
- [x] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason
Local validation:
-
`StatsCalculatorTest#testComputeOlapScanScalesNumNullsForSelectedPartitions`:
passed (1 test)
- `StatsCalculatorTest`: passed (17 tests)
- `mvn -pl fe-core -am checkstyle:check`: passed (0 violations)
- Behavior changed:
- [x] No.
- [ ] Yes.
- Does this need documentation?
- [x] No.
- [ ] Yes.
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
---
.../doris/nereids/stats/StatsCalculatorTest.java | 76 ++++++++++++++++++++++
.../suites/statistics/test_scale_num_nulls.groovy | 60 -----------------
2 files changed, 76 insertions(+), 60 deletions(-)
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/StatsCalculatorTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/StatsCalculatorTest.java
index 075e3731977..357d2514fb8 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/StatsCalculatorTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/stats/StatsCalculatorTest.java
@@ -17,8 +17,11 @@
package org.apache.doris.nereids.stats;
+import org.apache.doris.analysis.IntLiteral;
import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.Partition;
import org.apache.doris.catalog.PrimitiveType;
import org.apache.doris.common.Pair;
import org.apache.doris.nereids.CascadesContext;
@@ -53,9 +56,11 @@ import org.apache.doris.nereids.util.MemoTestUtils;
import org.apache.doris.nereids.util.PlanConstructor;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.SessionVariable;
+import org.apache.doris.statistics.AnalysisManager;
import org.apache.doris.statistics.ColumnStatistic;
import org.apache.doris.statistics.ColumnStatisticBuilder;
import org.apache.doris.statistics.Statistics;
+import org.apache.doris.statistics.StatisticsCache;
import org.apache.doris.statistics.TableStatsMeta;
import com.google.common.collect.ImmutableList;
@@ -66,6 +71,8 @@ import mockit.Mock;
import mockit.MockUp;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
import java.io.IOException;
import java.util.ArrayList;
@@ -207,6 +214,75 @@ public class StatsCalculatorTest {
Assertions.assertNotNull(stats.columnStatistics().get(slot1));
}
+ @Test
+ public void testComputeOlapScanScalesNumNullsForSelectedPartitions() {
+ ConnectContext previousContext = ConnectContext.get();
+ ConnectContext connectContext = new ConnectContext();
+ connectContext.setThreadLocalInfo();
+
+ Env env = Mockito.mock(Env.class);
+ AnalysisManager analysisManager = Mockito.mock(AnalysisManager.class);
+ StatisticsCache statisticsCache = Mockito.mock(StatisticsCache.class);
+ StatisticsCache.OlapTableStatistics olapTableStatistics
+ = Mockito.mock(StatisticsCache.OlapTableStatistics.class);
+ OlapTable table = Mockito.mock(OlapTable.class);
+ LogicalOlapScan scan = Mockito.mock(LogicalOlapScan.class);
+ Partition selectedPartition = Mockito.mock(Partition.class);
+
+ long selectedPartitionId = 1L;
+ long baseIndexId = 10L;
+ Column column = new Column("val", PrimitiveType.INT);
+ SlotReference slot = new SlotReference(new ExprId(1), "val",
IntegerType.INSTANCE, true,
+ ImmutableList.of("test", "tbl"), table, column, table, column);
+ ColumnStatistic tableStatistic = new ColumnStatisticBuilder(12)
+ .setNdv(1)
+ .setMinValue(2)
+ .setMaxValue(2)
+ .setMinExpr(new IntLiteral(2))
+ .setMaxExpr(new IntLiteral(2))
+ .setNumNulls(3)
+ .build();
+
+ Mockito.when(env.getAnalysisManager()).thenReturn(analysisManager);
+ Mockito.when(env.getStatisticsCache()).thenReturn(statisticsCache);
+
Mockito.when(statisticsCache.getOlapTableStats(scan)).thenReturn(olapTableStatistics);
+ Mockito.when(olapTableStatistics.getColumnStatistics("val",
connectContext)).thenReturn(tableStatistic);
+ Mockito.when(scan.getTable()).thenReturn(table);
+ Mockito.when(scan.getSelectedIndexId()).thenReturn(baseIndexId);
+
Mockito.when(scan.getSelectedPartitionIds()).thenReturn(ImmutableList.of(selectedPartitionId));
+ Mockito.when(scan.getOutput()).thenReturn(ImmutableList.of(slot));
+ Mockito.when(scan.getVirtualColumns()).thenReturn(ImmutableList.of());
+ Mockito.when(table.getBaseIndexId()).thenReturn(baseIndexId);
+ Mockito.when(table.getRowCountForIndex(baseIndexId,
true)).thenReturn(12L);
+ Mockito.when(table.getRowCountForPartitionIndex(selectedPartitionId,
baseIndexId, true)).thenReturn(4L);
+ Mockito.when(table.getPartitionNum()).thenReturn(3);
+
Mockito.when(table.getPartition(selectedPartitionId)).thenReturn(selectedPartition);
+ Mockito.when(table.getQualifiedDbName()).thenReturn("test");
+ Mockito.when(selectedPartition.getName()).thenReturn("p1");
+
+ try (MockedStatic<Env> mockedEnv = Mockito.mockStatic(Env.class)) {
+ mockedEnv.when(Env::getCurrentEnv).thenReturn(env);
+
+ Statistics statistics = new StatsCalculator((CascadesContext)
null).computeOlapScan(scan);
+ ColumnStatistic result = statistics.findColumnStatistics(slot);
+
+ Assertions.assertEquals(4, statistics.getRowCount(), 0.001);
+ Assertions.assertNotNull(result);
+ Assertions.assertEquals(1, result.ndv, 0.001);
+ Assertions.assertEquals(2, result.minValue, 0.001);
+ Assertions.assertEquals(2, result.maxValue, 0.001);
+ Assertions.assertEquals(4, result.count, 0.001);
+ Assertions.assertEquals(1, result.numNulls, 0.001);
+ Assertions.assertEquals("2", result.minExpr.getStringValue());
+ Assertions.assertEquals("2", result.maxExpr.getStringValue());
+ } finally {
+ ConnectContext.remove();
+ if (previousContext != null) {
+ previousContext.setThreadLocalInfo();
+ }
+ }
+ }
+
@Test
public void testLimit() {
List<String> qualifier = ImmutableList.of("test", "t");
diff --git a/regression-test/suites/statistics/test_scale_num_nulls.groovy
b/regression-test/suites/statistics/test_scale_num_nulls.groovy
deleted file mode 100644
index 043b072ca62..00000000000
--- a/regression-test/suites/statistics/test_scale_num_nulls.groovy
+++ /dev/null
@@ -1,60 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-suite("test_scale_num_nulls") {
- // For an OlapTable, when only a subset of partitions is selected,
- // the num_nulls value in column statistics needs to be scaled
proportionally.
- sql """
- drop table if exists ptable;
- CREATE TABLE `ptable` (
- `id` int NULL,
- `val` int NULL,
- `d` date NULL
- ) ENGINE=OLAP
- DUPLICATE KEY(`id`)
- PARTITION BY RANGE(`d`)
- (PARTITION p201701 VALUES [('2017-01-01'), ('2017-02-01')),
- PARTITION p201702 VALUES [('2017-02-01'), ('2017-03-01')),
- PARTITION p201703 VALUES [('2017-03-01'), ('2017-04-01')))
- DISTRIBUTED BY HASH(`id`) BUCKETS 16
- PROPERTIES (
- "replication_allocation" = "tag.location.default: 1",
- "min_load_replica_num" = "-1",
- "is_being_synced" = "false",
- "storage_medium" = "hdd",
- "storage_format" = "V2",
- "inverted_index_storage_format" = "V3",
- "light_schema_change" = "true",
- "disable_auto_compaction" = "false",
- "group_commit_interval_ms" = "10000",
- "group_commit_data_bytes" = "134217728"
- );
-
- insert into ptable values
- (1, null, '2017-01-01'), (11,2, '2017-01-01'), (111,2, '2017-01-01'),
(1111,2, '2017-01-01'),
- (2, null, '2017-02-01'), (22,2, '2017-02-01'), (222,2, '2017-02-01'),
(2222,2, '2017-02-01'),
- (3, null, '2017-03-01'), (33,2, '2017-03-01'), (333,2, '2017-03-01'),
(333,2, '2017-03-01');
-
- analyze table ptable with sync;
- """
- def colStats = sql "show column stats ptable";
- def memo = sql "explain memo plan select * from ptable where
d='2017-01-01'"
- // check numNulls=1.0000 for column val, which is scaled from 3 to 1
according to the partition pruning result.
- assertTrue(memo.toString().contains("val#1 -> ndv=1.0000,
min=2.000000(2), max=2.000000(2), count=4.0000, numNulls=1.0000"),
- "numNulls should be 1.0000, but is " + memo.toString() + "\n
column stats: \n" + colStats.toString());
-}
-
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]