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 7a7e86cd9be [improvement](statistics) Accept WITH HOT VALUE on a
sample analyze (#69225)
7a7e86cd9be is described below
commit 7a7e86cd9beff6242b82408d96e9d8b7467a4cc9
Author: minghong <[email protected]>
AuthorDate: Tue Oct 20 14:23:23 2026 +0800
[improvement](statistics) Accept WITH HOT VALUE on a sample analyze (#69225)
### What problem does this PR solve?
Issue Number: None
Related PR: #69209
Problem Summary:
`ANALYZE ... WITH SAMPLE ... WITH HOT VALUE` was rejected by
`AnalyzeCommand.validate`. The
rejection was introduced together with `WITH HOT VALUE`: at that time a
sample analyze always
collected hot values, so the flag was redundant and rejecting it early
kept the command from
silently doing nothing.
After #69209 the linear estimator no longer collects hot values, but a
sample analyze still
collects them for every column the DUJ1 estimator serves - and which
estimator a column gets is
decided per column at execution time
(`OlapAnalysisTask.getSampleCollectInfo`: table row count,
sampled rows, single distribution / single key column, partition flags),
so the command layer
cannot tell whether the flag would be honored for a particular column.
Rejecting the combination
is therefore wrong for the columns that do collect hot values (the DUJ1
ones), while for the
linear ones the flag never could force collection anyway.
This PR accepts the combination. The flag keeps its only real meaning -
it selects the hot value
template of a full analyze - and has no effect on a sample analyze: hot
values still come from the
DUJ1 estimator, and a full analyze with `WITH HOT VALUE` remains the way
to collect them for a
linear-estimated column.
Before this PR, both command paths that reach the check (`ANALYZE TABLE`
and `ANALYZE DATABASE`)
returned:
```
ERROR 1105 (HY000): errCode = 2, detailMessage = Sample analyze does not
support WITH HOT VALUE
```
After it, the statement succeeds and the analyze runs with the estimator
the sample picks.
### Release note
`ANALYZE TABLE ... WITH SAMPLE ... WITH HOT VALUE` is no longer
rejected. The flag only affects a
full analyze; on a sample analyze hot values are still collected only
for the columns analyzed by
the DUJ1 estimator.
---
.../trees/plans/commands/AnalyzeCommand.java | 9 +++--
.../plans/commands/AnalyzeTableCommandTest.java | 44 ++++++++++++++++++++--
.../statistics/test_full_analyze_hot_value.groovy | 25 +++++++++---
3 files changed, 65 insertions(+), 13 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeCommand.java
index 2b612683458..fab038f35d8 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeCommand.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeCommand.java
@@ -140,14 +140,17 @@ public abstract class AnalyzeCommand extends Command
implements ForwardWithSync
/**
* Validate analyze command properties.
+ *
+ * <p>{@code WITH HOT VALUE} is deliberately not validated against the
analysis method. The flag
+ * only selects the hot value template of a full analyze; a sample analyze
collects hot values
+ * depending on the estimator it picks per column at execution time (DUJ1
does, the linear
+ * estimator does not), which cannot be known here. The combination is
therefore accepted, and
+ * the flag simply has no effect on a sample analyze.
*/
public void validate(ConnectContext ctx) throws UserException {
if (analyzeProperties != null) {
analyzeProperties.check();
}
- if (analyzeProperties.hasCollectHotValue() && getAnalysisMethod() ==
AnalysisInfo.AnalysisMethod.SAMPLE) {
- throw new AnalysisException("Sample analyze does not support WITH
HOT VALUE");
- }
}
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeTableCommandTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeTableCommandTest.java
index 369644bf193..21ae3e05fe4 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeTableCommandTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/AnalyzeTableCommandTest.java
@@ -19,11 +19,15 @@ package org.apache.doris.nereids.trees.plans.commands;
import org.apache.doris.analysis.AnalyzeProperties;
import org.apache.doris.backup.CatalogMocker;
+import org.apache.doris.catalog.DatabaseIf;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.NameSpaceContext;
+import org.apache.doris.catalog.TableIf;
import org.apache.doris.catalog.info.PartitionNamesInfo;
import org.apache.doris.catalog.info.TableNameInfo;
import org.apache.doris.common.AnalysisException;
+import org.apache.doris.datasource.CatalogIf;
+import org.apache.doris.datasource.CatalogMgr;
import org.apache.doris.datasource.InternalCatalog;
import org.apache.doris.mysql.privilege.AccessControllerManager;
import org.apache.doris.mysql.privilege.PrivPredicate;
@@ -151,13 +155,45 @@ public class AnalyzeTableCommandTest {
}
@Test
- void testSampleAnalyzeWithHotValueRejected() {
+ void testSampleAnalyzeWithHotValueAccepted() throws Exception {
+ // WITH HOT VALUE is accepted on a sample analyze: the flag only
selects the hot value
+ // template of a full analyze, while a sample analyze collects hot
values depending on the
+ // estimator it picks per column at execution time.
+ TableIf table = Mockito.mock(TableIf.class);
+ DatabaseIf database = Mockito.mock(DatabaseIf.class);
+ CatalogIf catalog = Mockito.mock(CatalogIf.class);
+ CatalogMgr catalogMgr = Mockito.mock(CatalogMgr.class);
+ Mockito.when(ctx.getEnv()).thenReturn(env);
+ Mockito.when(env.getCatalogMgr()).thenReturn(catalogMgr);
+
Mockito.when(catalogMgr.getCatalogOrAnalysisException(internalCtl)).thenReturn(catalog);
+
Mockito.when(catalog.getDbOrAnalysisException(CatalogMocker.TEST_DB_NAME)).thenReturn(database);
+
Mockito.when(database.getTableOrAnalysisException(CatalogMocker.TEST_TBL_NAME)).thenReturn(table);
+
Mockito.when(accessManager.checkTblPriv(Mockito.nullable(ConnectContext.class),
Mockito.eq(internalCtl),
+ Mockito.eq(CatalogMocker.TEST_DB_NAME),
Mockito.eq(CatalogMocker.TEST_TBL_NAME),
+ Mockito.eq(PrivPredicate.SELECT))).thenReturn(true);
+
NereidsParser parser = new NereidsParser();
AnalyzeTableCommand command = (AnalyzeTableCommand) parser.parseSingle(
"ANALYZE TABLE test_db.test_tbl WITH SAMPLE ROWS 100 WITH HOT
VALUE");
- AnalysisException exception =
Assertions.assertThrows(AnalysisException.class,
- () -> command.validate(ctx));
- Assertions.assertEquals("Sample analyze does not support WITH HOT
VALUE", exception.getDetailMessage());
+ Assertions.assertDoesNotThrow(() -> command.validate(ctx));
+
Assertions.assertTrue(command.getAnalyzeProperties().hasCollectHotValue());
+ Assertions.assertEquals(AnalysisInfo.AnalysisMethod.SAMPLE,
command.getAnalysisMethod());
+ }
+
+ @Test
+ void testSampleAnalyzeDatabaseWithHotValueAccepted() throws Exception {
+ // The rejection lived in the validate shared by both commands, so
ANALYZE DATABASE accepts
+ // the flag as well.
+ DatabaseIf database = Mockito.mock(DatabaseIf.class);
+ CatalogIf catalog = Mockito.mock(CatalogIf.class);
+ Mockito.when(env.getCurrentCatalog()).thenReturn(catalog);
+
Mockito.when(catalog.getDbOrAnalysisException(CatalogMocker.TEST_DB_NAME)).thenReturn(database);
+
+ NereidsParser parser = new NereidsParser();
+ AnalyzeDatabaseCommand command = (AnalyzeDatabaseCommand)
parser.parseSingle(
+ "ANALYZE DATABASE test_db WITH SAMPLE ROWS 100 WITH HOT
VALUE");
+ Assertions.assertDoesNotThrow(() -> command.validate(ctx));
+ Assertions.assertEquals(AnalysisInfo.AnalysisMethod.SAMPLE,
command.getAnalysisMethod());
}
private Map<String, String> defaultAnalyzeProperties() {
diff --git
a/regression-test/suites/statistics/test_full_analyze_hot_value.groovy
b/regression-test/suites/statistics/test_full_analyze_hot_value.groovy
index c709f20ed42..14928393224 100644
--- a/regression-test/suites/statistics/test_full_analyze_hot_value.groovy
+++ b/regression-test/suites/statistics/test_full_analyze_hot_value.groovy
@@ -142,12 +142,16 @@ suite("test_full_analyze_hot_value") {
assertEquals(1, result.size())
assertEquals("null", String.valueOf(result[0][17]))
- // Test 6: Sample analyze rejects WITH HOT VALUE because it cannot honor
the flag: inside a
- // sample analyze hot values only come from the DUJ1 estimator.
- test {
- sql """analyze table full_hot_skew with sample rows 400 with sync with
hot value"""
- exception "Sample analyze does not support WITH HOT VALUE"
- }
+ // Test 6: WITH HOT VALUE is accepted on a sample analyze and has no
effect here: hot values are
+ // collected only for the columns the DUJ1 estimator serves, and
full_hot_skew is small enough
+ // that its sample analyze falls back to a full scan (rendered with the
linear template), which
+ // does not collect them.
+ sql """drop stats full_hot_skew"""
+ sql """analyze table full_hot_skew with sample rows 400 with sync with hot
value"""
+ result = sql """show column stats full_hot_skew(value1)"""
+ logger.info("Test6 result: " + result)
+ assertEquals(1, result.size())
+ assertEquals("null", String.valueOf(result[0][17]))
// Test 7: Explicit full analyze produces the same hot_value as a sample
analyze that really
// samples. The column value is uniform, so the sampled rows always carry
the same
@@ -182,6 +186,15 @@ suite("test_full_analyze_hot_value") {
// Both full and sample should produce the same hot_value entry.
assertEquals(fullResult[0][17], sampleResult[0][17])
+ // WITH HOT VALUE is accepted on a sample analyze and does not interfere
with the estimator:
+ // this table really samples, so DUJ1 still collects the hot value.
+ sql """drop stats full_hot_sample_agree"""
+ sql """analyze table full_hot_sample_agree with sample rows 400 with sync
with hot value"""
+ result = sql """show column stats full_hot_sample_agree(value1)"""
+ logger.info("Test7 sample with hot value result: " + result)
+ assertEquals(1, result.size())
+ assertEquals("' : ;a':1.0", result[0][17])
+
// Test 8: Explicit full analyze on empty table should produce an empty
hot_value string.
sql """drop table if exists full_hot_empty"""
sql """CREATE TABLE full_hot_empty (
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]