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 a586954dde7d49b73c7a499ffb1572e98718cc9a Author: JackieTien97 <[email protected]> AuthorDate: Thu Apr 2 18:45:06 2026 +0800 add comments for expected json format for it --- .../it/query/recent/IoTExplainJsonFormatIT.java | 293 +++++++++++++++++++++ .../optimizations/UnaliasSymbolReferences.java | 3 +- 2 files changed, 295 insertions(+), 1 deletion(-) 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 0f3d0313124..4cbfd02bf5d 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 @@ -91,6 +91,54 @@ public class IoTExplainJsonFormatIT { @Test public void testExplainJsonFormat() { + // Expected output (single row, single JSON object representing the distributed plan tree): + // { + // "name": "OutputNode-<id>", + // "id": "<id>", + // "properties": { + // "OutputColumns": ["time", "deviceid", "voltage"], + // "OutputSymbols": ["time", "deviceid", "voltage"] + // }, + // "children": [ + // { + // "name": "CollectNode-<id>", + // "id": "<id>", + // "children": [ + // { + // "name": "ExchangeNode-<id>", + // "id": "<id>", + // "children": [ + // { + // "name": "DeviceTableScanNode-<id>", + // "id": "<id>", + // "properties": { + // "QualifiedTableName": "testdb_json.testtb", + // "OutputSymbols": ["time", "deviceid", "voltage"], + // "DeviceNumber": "1", + // "ScanOrder": "ASC", + // "PushDownOffset": "0", + // "PushDownLimit": "0", + // "PushDownLimitToEachDevice": "false", + // "RegionId": "<regionId>" + // } + // } + // ] + // }, + // { + // "name": "ExchangeNode-<id>", + // "id": "<id>", + // "children": [ + // { + // "name": "DeviceTableScanNode-<id>", + // "id": "<id>", + // "properties": { ... } + // } + // ] + // } + // ] + // } + // ] + // } String sql = "EXPLAIN (FORMAT JSON) SELECT * FROM testtb"; try (Connection conn = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); Statement statement = conn.createStatement()) { @@ -134,6 +182,76 @@ public class IoTExplainJsonFormatIT { @Test public void testExplainAnalyzeJsonFormat() { + // Expected output (single row, single JSON object with plan statistics + fragment instances): + // { + // "planStatistics": { + // "analyzeCostMs": <ms>, + // "fetchPartitionCostMs": <ms>, + // "fetchSchemaCostMs": <ms>, + // "logicalPlanCostMs": <ms>, + // "logicalOptimizationCostMs": <ms>, + // "distributionPlanCostMs": <ms>, + // "dispatchCostMs": <ms> + // }, + // "fragmentInstancesCount": 3, + // "fragmentInstances": [ + // { + // "id": "<queryId>.<fragmentId>.<instanceId>", + // "ip": "127.0.0.1:<port>", + // "dataRegion": "virtual_data_region", + // "state": "FINISHED", + // "totalWallTimeMs": <ms>, + // "initDataQuerySourceCostMs": <ms>, + // "seqFileUnclosed": 0, "seqFileClosed": 0, + // "unseqFileUnclosed": 0, "unseqFileClosed": 0, + // "readyQueuedTimeMs": <ms>, + // "blockQueuedTimeMs": <ms>, + // "queryStatistics": { + // "timeSeriesIndexFilteredRows": 0, + // "chunkIndexFilteredRows": 0, + // "pageIndexFilteredRows": 0 + // }, + // "operators": { + // "planNodeId": "<id>", + // "nodeType": "IdentitySinkNode", + // "operatorType": "IdentitySinkOperator", + // "cpuTimeMs": <ms>, + // "outputRows": 5, + // "hasNextCalledCount": 5, + // "nextCalledCount": 4, + // "estimatedMemorySize": 1024, + // "specifiedInfo": { "DownStreamPlanNodeId": "<id>" }, + // "children": [ + // { + // "planNodeId": "<id>", + // "nodeType": "CollectNode", + // "operatorType": "CollectOperator", + // ... + // "children": [ + // { "planNodeId": "<id>", "nodeType": "ExchangeNode", ... }, + // { "planNodeId": "<id>", "nodeType": "ExchangeNode", ... } + // ] + // } + // ] + // } + // }, + // { + // "id": "...", "dataRegion": "4", "state": "FINISHED", + // ... + // "operators": { + // "nodeType": "IdentitySinkNode", ... + // "children": [ + // { "nodeType": "DeviceTableScanNode", "operatorType": "TableScanOperator", ... } + // ] + // } + // }, + // { + // "id": "...", "dataRegion": "3", "state": "FINISHED", + // ... + // "operators": { ... } + // } + // ] + // } String sql = "EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM testtb"; try (Connection conn = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); Statement statement = conn.createStatement()) { @@ -168,6 +286,47 @@ public class IoTExplainJsonFormatIT { @Test public void testExplainAnalyzeVerboseJsonFormat() { + // Expected output (same structure as testExplainAnalyzeJsonFormat, but queryStatistics + // includes verbose fields like bloom filter, metadata, chunk reader, and page decoder stats): + // { + // "planStatistics": { ... }, + // "fragmentInstancesCount": 3, + // "fragmentInstances": [ + // { + // "id": "...", "dataRegion": "virtual_data_region", ... + // "queryStatistics": { + // "loadBloomFilterFromCacheCount": 0, + // "loadBloomFilterFromDiskCount": 0, + // "loadBloomFilterActualIOSize": 0, + // "loadBloomFilterTimeMs": 0.0, + // "loadTimeSeriesMetadataFromCacheCount": 0, + // "loadTimeSeriesMetadataFromDiskCount": 0, + // "loadTimeSeriesMetadataActualIOSize": 0, + // "loadChunkFromCacheCount": 0, + // "loadChunkFromDiskCount": 0, + // "loadChunkActualIOSize": 0, + // "timeSeriesIndexFilteredRows": 0, + // "chunkIndexFilteredRows": 0, + // "pageIndexFilteredRows": 0, + // "rowScanFilteredRows": 0 + // }, + // "operators": { ... } + // }, + // { + // "id": "...", "dataRegion": "4", ... + // "queryStatistics": { + // ... (same as above, plus non-zero fields like:) + // "loadTimeSeriesMetadataAlignedMemSeqCount": 2, + // "loadTimeSeriesMetadataAlignedMemSeqTimeMs": <ms>, + // "pageReadersDecodeAlignedMemCount": 2, + // "pageReadersDecodeAlignedMemTimeMs": <ms>, + // ... + // }, + // "operators": { ... } + // }, + // { "id": "...", "dataRegion": "3", ... } + // ] + // } String sql = "EXPLAIN ANALYZE VERBOSE (FORMAT JSON) SELECT * FROM testtb"; try (Connection conn = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); Statement statement = conn.createStatement()) { @@ -266,6 +425,17 @@ public class IoTExplainJsonFormatIT { @Test public void testExplainAnalyzeJsonMultipleFragmentInstances() { + // Expected output (same structure as testExplainAnalyzeJsonFormat): + // { + // "planStatistics": { ... }, + // "fragmentInstancesCount": 3, // >= 2 due to multi-partition data + // "fragmentInstances": [ + // { "id": "...", "dataRegion": "virtual_data_region", "state": "FINISHED", ... }, + // { "id": "...", "dataRegion": "4", "state": "FINISHED", ... }, + // { "id": "...", "dataRegion": "3", "state": "FINISHED", ... } + // ] + // } + // Each fragment instance must have "id", "state", and "dataRegion" fields. String sql = "EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM testtb"; try (Connection conn = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); Statement statement = conn.createStatement()) { @@ -303,6 +473,42 @@ public class IoTExplainJsonFormatIT { @Test public void testExplainJsonWithCte() { + // Expected output (when CTE is present, the JSON wraps cteQueries + mainQuery): + // { + // "cteQueries": [ + // { + // "name": "cte1", + // "plan": <plan node for CTE query> + // } + // ], + // "mainQuery": { + // "name": "OutputNode-<id>", + // "id": "<id>", + // "properties": { + // "OutputColumns": ["time", "deviceid", "voltage"], + // "OutputSymbols": ["time", "deviceid", "voltage"] + // }, + // "children": [ + // { + // "name": "ProjectNode-<id>", ... + // "children": [ + // { + // "name": "FilterNode-<id>", ... + // "children": [ + // { + // "name": "SemiJoinNode-<id>", ... + // "children": [ + // { "name": "ExchangeNode-<id>", ... }, // main table scan branch + // { "name": "ExchangeNode-<id>", ... } // CTE scan branch + // ] + // } + // ] + // } + // ] + // } + // ] + // } + // } try (Connection conn = EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); Statement statement = conn.createStatement()) { statement.execute("USE " + DATABASE_NAME); @@ -344,6 +550,43 @@ public class IoTExplainJsonFormatIT { @Test public void testExplainJsonWithScalarSubquery() { + // Expected output (scalar subquery is inlined during optimization, so the plan is a simple + // tree with the constant predicate pushed down to DeviceTableScanNode): + // { + // "name": "OutputNode-<id>", + // "id": "<id>", + // "properties": { + // "OutputColumns": ["time", "deviceid", "voltage"], + // "OutputSymbols": ["time", "deviceid", "voltage"] + // }, + // "children": [ + // { + // "name": "CollectNode-<id>", + // "id": "<id>", + // "children": [ + // { + // "name": "ExchangeNode-<id>", ... + // "children": [ + // { + // "name": "DeviceTableScanNode-<id>", + // "properties": { + // "QualifiedTableName": "testdb_json.testtb", + // "PushDownPredicate": "(\"voltage\" > 2E2)", + // ... + // } + // } + // ] + // }, + // { + // "name": "ExchangeNode-<id>", ... + // "children": [ + // { "name": "DeviceTableScanNode-<id>", ... } + // ] + // } + // ] + // } + // ] + // } String sql = "EXPLAIN (FORMAT JSON) SELECT * FROM testtb " + "WHERE voltage > (SELECT avg(voltage) FROM testtb)"; @@ -369,6 +612,56 @@ public class IoTExplainJsonFormatIT { @Test public void testExplainAnalyzeJsonWithScalarSubquery() { + // Expected output (same EXPLAIN ANALYZE JSON structure, but the scalar subquery is resolved + // at planning time, so the executed plan only scans with a constant predicate): + // { + // "planStatistics": { + // "analyzeCostMs": <ms>, + // "fetchPartitionCostMs": <ms>, + // "fetchSchemaCostMs": <ms>, + // "logicalPlanCostMs": <ms>, // higher than simple query due to subquery planning + // "logicalOptimizationCostMs": <ms>, + // "distributionPlanCostMs": <ms>, + // "dispatchCostMs": <ms> + // }, + // "fragmentInstancesCount": 3, + // "fragmentInstances": [ + // { + // "id": "...", "dataRegion": "virtual_data_region", "state": "FINISHED", + // ... + // "operators": { + // "nodeType": "IdentitySinkNode", ... + // "children": [ + // { + // "nodeType": "CollectNode", ... + // "children": [ + // { "nodeType": "ExchangeNode", "outputRows": 2, ... }, + // { "nodeType": "ExchangeNode", "outputRows": 0, ... } + // ] + // } + // ] + // } + // }, + // { + // "dataRegion": "4", ... + // "operators": { + // "nodeType": "IdentitySinkNode", ... + // "children": [ + // { "nodeType": "DeviceTableScanNode", "operatorType": "TableScanOperator", ... } + // ] + // } + // }, + // { + // "dataRegion": "3", ... + // "operators": { + // "nodeType": "IdentitySinkNode", ... + // "children": [ + // { "nodeType": "DeviceTableScanNode", "outputRows": 0, ... } + // ] + // } + // } + // ] + // } String sql = "EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM testtb " + "WHERE voltage > (SELECT avg(voltage) FROM testtb)"; diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java index 24adb746df4..4b7ab3f3ab4 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java @@ -385,7 +385,8 @@ public class UnaliasSymbolReferences implements PlanOptimizer { node.getQueryId(), node.getTimeout(), node.getOutputSymbols().get(0), - newChildPermittedOutputs), + newChildPermittedOutputs, + node.getOutputFormat()), mapping); }
