This is an automated email from the ASF dual-hosted git repository.

morrySnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 8b52996cd9a [test](nereids) Replace flaky null stats case with unit 
test (#65445)
8b52996cd9a is described below

commit 8b52996cd9a74dba08423044e9ef3b154fd66b3b
Author: shuke <[email protected]>
AuthorDate: Mon Jul 13 17:40:40 2026 +0800

    [test](nereids) Replace flaky null stats case with unit test (#65445)
    
    ### What problem does this PR solve?
    
    Related PR: #62265
    
    Problem Summary:
    
    `test_scale_num_nulls` checks that table-level `numNulls=3` is scaled to
    `1` when partition pruning selects 4 of 12 rows. The regression case
    executes `EXPLAIN MEMO PLAN` immediately after insert and analyze, but
    Cloud P0 reports the physical partition row count asynchronously. The
    planner can therefore observe a transient selected-partition row count
    of zero, while waiting for the real row count can take up to two
    minutes.
    
    This change:
    
    - adds deterministic FE unit coverage that invokes
    `StatsCalculator.computeOlapScan()` with a three-partition OLAP scan;
    - fixes the table row count at 12, selected-partition row count at 4,
    and cached table-level column statistics at `ndv=1`, `min=max=2`, and
    `numNulls=3`;
    - verifies the final scan statistics have `ndv=1`, `min=max=2`,
    `count=4`, and scaled `numNulls=1`;
    - removes the timing-dependent Groovy regression case.
    
    The unit test exercises the production partition-pruning statistics path
    using fixed in-memory inputs. It has no cluster, physical row-count
    reporting, polling, sleep, network, or storage dependency. Production
    behavior is unchanged.
---
 .../doris/nereids/stats/StatsCalculatorTest.java   | 75 ++++++++++++++++++++++
 .../suites/statistics/test_scale_num_nulls.groovy  | 60 -----------------
 2 files changed, 75 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 8bfab4c4ec1..ae6d93e17fc 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;
@@ -54,9 +57,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 com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -64,6 +69,7 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 
 import java.lang.reflect.Field;
@@ -206,6 +212,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]

Reply via email to