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

jackietien pushed a commit to branch ty/explain_format
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit 14d31578f49aca7c57d14836dff8f03583783843
Author: JackieTien97 <[email protected]>
AuthorDate: Thu Apr 2 16:06:15 2026 +0800

    add IT
---
 .../it/query/recent/IoTExplainJsonFormatIT.java    | 142 ++++++++++++++++++++-
 .../TableModelStatementMemorySourceVisitor.java    |   2 +-
 2 files changed, 142 insertions(+), 2 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTExplainJsonFormatIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTExplainJsonFormatIT.java
index e023d6ce386..0f3d0313124 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTExplainJsonFormatIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTExplainJsonFormatIT.java
@@ -26,6 +26,7 @@ import org.apache.iotdb.it.framework.IoTDBTestRunner;
 import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
 import org.apache.iotdb.itbase.env.BaseEnv;
 
+import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import org.junit.AfterClass;
@@ -52,7 +53,12 @@ public class IoTExplainJsonFormatIT {
   public static void setUp() {
     Locale.setDefault(Locale.ENGLISH);
 
-    
EnvFactory.getEnv().getConfig().getCommonConfig().setPartitionInterval(1000);
+    EnvFactory.getEnv()
+        .getConfig()
+        .getCommonConfig()
+        .setPartitionInterval(1000)
+        .setMemtableSizeThreshold(10000)
+        .setMaxRowsInCteBuffer(100);
     EnvFactory.getEnv().initClusterEnvironment();
 
     try (Connection connection = 
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
@@ -61,9 +67,12 @@ public class IoTExplainJsonFormatIT {
       statement.execute("USE " + DATABASE_NAME);
       statement.execute(
           "CREATE TABLE IF NOT EXISTS testtb(deviceid STRING TAG, voltage 
FLOAT FIELD)");
+      // Insert data across multiple time partitions (partitionInterval=1000)
       statement.execute("INSERT INTO testtb VALUES(1000, 'd1', 100.0)");
       statement.execute("INSERT INTO testtb VALUES(2000, 'd1', 200.0)");
+      statement.execute("INSERT INTO testtb VALUES(3000, 'd1', 150.0)");
       statement.execute("INSERT INTO testtb VALUES(1000, 'd2', 300.0)");
+      statement.execute("INSERT INTO testtb VALUES(2000, 'd2', 250.0)");
     } catch (Exception e) {
       fail(e.getMessage());
     }
@@ -254,4 +263,135 @@ public class IoTExplainJsonFormatIT {
               || e.getMessage().toUpperCase().contains("XML"));
     }
   }
+
+  @Test
+  public void testExplainAnalyzeJsonMultipleFragmentInstances() {
+    String sql = "EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM testtb";
+    try (Connection conn = 
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+        Statement statement = conn.createStatement()) {
+      statement.execute("USE " + DATABASE_NAME);
+      ResultSet resultSet = statement.executeQuery(sql);
+
+      Assert.assertTrue(resultSet.next());
+      String jsonStr = resultSet.getString(1);
+      JsonObject root = JsonParser.parseString(jsonStr).getAsJsonObject();
+
+      // Verify fragmentInstancesCount matches the size of fragmentInstances 
array
+      int declaredCount = root.get("fragmentInstancesCount").getAsInt();
+      JsonArray fragmentInstances = root.getAsJsonArray("fragmentInstances");
+      Assert.assertNotNull("fragmentInstances array should be present", 
fragmentInstances);
+      Assert.assertEquals(
+          "fragmentInstancesCount should match fragmentInstances array size",
+          declaredCount,
+          fragmentInstances.size());
+      Assert.assertTrue(
+          "Should have at least 2 fragment instances for multi-partition 
data", declaredCount >= 2);
+
+      // Verify each fragment instance has required fields
+      for (int i = 0; i < fragmentInstances.size(); i++) {
+        JsonObject fi = fragmentInstances.get(i).getAsJsonObject();
+        Assert.assertTrue("Fragment instance should have 'id'", fi.has("id"));
+        Assert.assertTrue("Fragment instance should have 'state'", 
fi.has("state"));
+        Assert.assertTrue("Fragment instance should have 'dataRegion'", 
fi.has("dataRegion"));
+      }
+
+      resultSet.close();
+    } catch (SQLException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void testExplainJsonWithCte() {
+    try (Connection conn = 
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+        Statement statement = conn.createStatement()) {
+      statement.execute("USE " + DATABASE_NAME);
+      statement.execute(
+          "CREATE TABLE IF NOT EXISTS cte_tb(deviceid STRING TAG, voltage 
FLOAT FIELD)");
+      statement.execute("INSERT INTO cte_tb VALUES(1000, 'd1', 50.0)");
+
+      String sql =
+          "EXPLAIN (FORMAT JSON) WITH cte1 AS MATERIALIZED (SELECT * FROM 
cte_tb) "
+              + "SELECT * FROM testtb WHERE testtb.deviceid IN (SELECT 
deviceid FROM cte1)";
+      ResultSet resultSet = statement.executeQuery(sql);
+
+      Assert.assertTrue(resultSet.next());
+      String jsonStr = resultSet.getString(1);
+      JsonObject root = JsonParser.parseString(jsonStr).getAsJsonObject();
+
+      // When CTEs are present, the JSON should have cteQueries and mainQuery
+      Assert.assertTrue("JSON with CTE should have 'cteQueries' field", 
root.has("cteQueries"));
+      Assert.assertTrue("JSON with CTE should have 'mainQuery' field", 
root.has("mainQuery"));
+
+      JsonArray cteQueries = root.getAsJsonArray("cteQueries");
+      Assert.assertEquals("Should have exactly 1 CTE query", 1, 
cteQueries.size());
+
+      JsonObject cte = cteQueries.get(0).getAsJsonObject();
+      Assert.assertTrue("CTE should have 'name' field", cte.has("name"));
+      Assert.assertEquals("cte1", cte.get("name").getAsString());
+      Assert.assertTrue("CTE should have 'plan' field", cte.has("plan"));
+
+      // The main query plan should be a JSON object with 'name' field (plan 
node)
+      JsonObject mainQuery = root.getAsJsonObject("mainQuery");
+      Assert.assertTrue("Main query plan should have 'name' field", 
mainQuery.has("name"));
+
+      statement.execute("DROP TABLE IF EXISTS cte_tb");
+      resultSet.close();
+    } catch (SQLException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void testExplainJsonWithScalarSubquery() {
+    String sql =
+        "EXPLAIN (FORMAT JSON) SELECT * FROM testtb "
+            + "WHERE voltage > (SELECT avg(voltage) FROM testtb)";
+    try (Connection conn = 
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+        Statement statement = conn.createStatement()) {
+      statement.execute("USE " + DATABASE_NAME);
+      ResultSet resultSet = statement.executeQuery(sql);
+
+      Assert.assertTrue(resultSet.next());
+      String jsonStr = resultSet.getString(1);
+      JsonObject root = JsonParser.parseString(jsonStr).getAsJsonObject();
+
+      // Verify it's a valid plan tree with children (subquery creates a more 
complex plan)
+      Assert.assertTrue("JSON should have 'name' field", root.has("name"));
+      Assert.assertTrue("JSON should have 'id' field", root.has("id"));
+      Assert.assertTrue("Plan with scalar subquery should have 'children'", 
root.has("children"));
+
+      resultSet.close();
+    } catch (SQLException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void testExplainAnalyzeJsonWithScalarSubquery() {
+    String sql =
+        "EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM testtb "
+            + "WHERE voltage > (SELECT avg(voltage) FROM testtb)";
+    try (Connection conn = 
EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT);
+        Statement statement = conn.createStatement()) {
+      statement.execute("USE " + DATABASE_NAME);
+      ResultSet resultSet = statement.executeQuery(sql);
+
+      Assert.assertTrue(resultSet.next());
+      String jsonStr = resultSet.getString(1);
+      JsonObject root = JsonParser.parseString(jsonStr).getAsJsonObject();
+
+      Assert.assertTrue(root.has("planStatistics"));
+      Assert.assertTrue(root.has("fragmentInstances"));
+      Assert.assertTrue(root.has("fragmentInstancesCount"));
+
+      int declaredCount = root.get("fragmentInstancesCount").getAsInt();
+      JsonArray fragmentInstances = root.getAsJsonArray("fragmentInstances");
+      Assert.assertEquals(declaredCount, fragmentInstances.size());
+
+      resultSet.close();
+    } catch (SQLException e) {
+      fail(e.getMessage());
+    }
+  }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/memory/TableModelStatementMemorySourceVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/memory/TableModelStatementMemorySourceVisitor.java
index 2633805074d..35ec7bcb1c4 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/memory/TableModelStatementMemorySourceVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/memory/TableModelStatementMemorySourceVisitor.java
@@ -182,7 +182,7 @@ public class TableModelStatementMemorySourceVisitor
     for (Map.Entry<NodeRef<Table>, Pair<Integer, List<String>>> entry :
         cteExplainResults.entrySet()) {
       JsonObject cte = new JsonObject();
-      cte.addProperty("name", entry.getKey().getNode().getName());
+      cte.addProperty("name", entry.getKey().getNode().getName().toString());
       cte.add("plan", 
JsonParser.parseString(entry.getValue().getRight().get(0)));
       cteArray.add(cte);
     }

Reply via email to