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());

Reply via email to