This is an automated email from the ASF dual-hosted git repository.
JackieTien97 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/dev/1.3 by this push:
new 9ec2add768c [To dev/1.3] Fix the clear logic of specified info of one
operator when print plan in explain analyze (#18168)
9ec2add768c is described below
commit 9ec2add768c533b4568db6338a6252334c9948f2
Author: CYB <[email protected]>
AuthorDate: Thu Jul 9 19:42:53 2026 +0800
[To dev/1.3] Fix the clear logic of specified info of one operator when
print plan in explain analyze (#18168)
---
.../db/it/query/IoTDBExplainAnalyzePrintIT.java | 116 +++++++++++++++++++++
.../fragment/FragmentInstanceExecution.java | 1 -
.../statistics/SpecifiedInfoMergerFactory.java | 13 +++
.../statistics/StatisticsMergeUtil.java | 2 -
4 files changed, 129 insertions(+), 3 deletions(-)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBExplainAnalyzePrintIT.java
b/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBExplainAnalyzePrintIT.java
new file mode 100644
index 00000000000..01793f06a81
--- /dev/null
+++
b/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBExplainAnalyzePrintIT.java
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.db.it.query;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.LocalStandaloneIT;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({LocalStandaloneIT.class})
+public class IoTDBExplainAnalyzePrintIT {
+
+ private static final String MERGE_THRESHOLD_OF_EXPLAIN_ANALYZE =
+ "merge_threshold_of_explain_analyze";
+ private static final String SERIES_SCAN_OPERATOR = "SeriesScanOperator";
+ private static final String FILTER_AND_PROJECT_OPERATOR =
"FilterAndProjectOperator";
+ private static final String FILTERED_ROWS = "Filtered Rows";
+
+ private static final String[] creationSqls =
+ new String[] {
+ "insert into root.test.device_0(s1, s2, s3, s4, s5, s6, s7, s8, s9,
s10) values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)",
+ };
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ EnvFactory.getEnv().initClusterEnvironment();
+ prepareData();
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ EnvFactory.getEnv().cleanClusterEnvironment();
+ }
+
+ private static void prepareData() {
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement()) {
+ statement.execute(
+ String.format("set configuration \"%s\"=\"2\"",
MERGE_THRESHOLD_OF_EXPLAIN_ANALYZE));
+ for (String sql : creationSqls) {
+ statement.execute(sql);
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ @Test
+ public void testOperatorStatisticsWhenMergedInAnalyze() throws SQLException {
+ String output =
+ getExplainAnalyzeResult(
+ "explain analyze select s1 from root.test.device_0 where s1 > 100
or s2 > 100");
+ assertTrue(output, containsLine(output, SERIES_SCAN_OPERATOR, "Count: *
2"));
+ assertTrue(output, output.contains(FILTER_AND_PROJECT_OPERATOR));
+ assertTrue(output, output.contains(FILTERED_ROWS));
+ }
+
+ private static String getExplainAnalyzeResult(String sql) throws
SQLException {
+ StringBuilder output = new StringBuilder();
+ try (Connection connection = EnvFactory.getEnv().getConnection();
+ Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery(sql)) {
+ while (resultSet.next()) {
+ output.append(resultSet.getString(1)).append('\n');
+ }
+ }
+ return output.toString();
+ }
+
+ private static boolean containsLine(String output, String... expectedValues)
{
+ for (String line : output.split("\\R")) {
+ boolean matched = true;
+ for (String expectedValue : expectedValues) {
+ if (!line.contains(expectedValue)) {
+ matched = false;
+ break;
+ }
+ }
+ if (matched) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceExecution.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceExecution.java
index a4a8d465500..be77077adcc 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceExecution.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceExecution.java
@@ -213,7 +213,6 @@ public class FragmentInstanceExecution {
} else {
String planNodeId = operatorContext.getPlanNodeId().toString();
operatorStatistics.setCount(1);
- operatorStatistics.getSpecifiedInfo().clear();
leadOverloadOperators.put(operatorType, planNodeId);
operatorStatisticsMap.put(planNodeId, operatorStatistics);
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/statistics/SpecifiedInfoMergerFactory.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/statistics/SpecifiedInfoMergerFactory.java
index 08e0ffd6b12..11606bd3cf8 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/statistics/SpecifiedInfoMergerFactory.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/statistics/SpecifiedInfoMergerFactory.java
@@ -41,12 +41,25 @@ public class SpecifiedInfoMergerFactory {
return first;
};
+ // currently the sink and shuffle operator only have the field of string
type,
+ // and the case that two operators contained in one FI do not exist yet
+ private static final SpecifiedInfoMerger SINK_SHUFFLE_MERGER =
+ (first, second) -> {
+ first.replaceAll((k, v) -> v + " " + second.get(k));
+ return first;
+ };
+
+ /** Maintain different merge logic for specified info for different
operators. */
public static SpecifiedInfoMerger getMerger(String operatorType) {
switch (operatorType) {
case "SortOperator":
case "MergeSortOperator":
case "FilterAndProjectOperator":
+ case "ExchangeOperator":
return LONG_MERGER;
+ case "IdentitySinkOperator":
+ case "ShuffleHelperOperator":
+ return SINK_SHUFFLE_MERGER;
default:
return DEFAULT_MERGER;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/statistics/StatisticsMergeUtil.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/statistics/StatisticsMergeUtil.java
index b2f39d12672..85870e1b599 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/statistics/StatisticsMergeUtil.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/statistics/StatisticsMergeUtil.java
@@ -70,8 +70,6 @@ public class StatisticsMergeUtil {
} else {
TOperatorStatistics operatorStatistics = entry.getValue();
operatorStatistics.setCount(1);
- // Can't merge specifiedInfo of String-type, so just clear it
- operatorStatistics.getSpecifiedInfo().clear();
// keep the first one in operatorStatisticsMap as the only-one
leadOverloadOperator
leadOverloadOperators.put(
operatorStatistics.getOperatorType(),
operatorStatistics.getPlanNodeId());