Skip to content

Support EXPLAIN FORMAT JSON for Table Model#17430

Open
JackieTien97 wants to merge 8 commits intomasterfrom
ty/explain_format
Open

Support EXPLAIN FORMAT JSON for Table Model#17430
JackieTien97 wants to merge 8 commits intomasterfrom
ty/explain_format

Conversation

@JackieTien97
Copy link
Copy Markdown
Contributor

Summary

  • Add FORMAT option to EXPLAIN and EXPLAIN ANALYZE statements in the Table Model, allowing users to choose the output format (JSON, GRAPHVIZ, or TEXT)
  • JSON format outputs a single-row, machine-parseable JSON document instead of multi-line text, making it easy for tools and scripts to programmatically analyze query plans
  • Backward compatible: default output formats remain unchanged (GRAPHVIZ for EXPLAIN, TEXT for EXPLAIN ANALYZE)

Syntax

-- EXPLAIN with format option
EXPLAIN (FORMAT <format>) <query>

-- EXPLAIN ANALYZE with format option
EXPLAIN ANALYZE [VERBOSE] (FORMAT <format>) <query>

Supported formats:

Format EXPLAIN EXPLAIN ANALYZE Description
GRAPHVIZ ✅ (default) Box-drawing plan visualization
TEXT ✅ (default) Text-based output
JSON Structured JSON output

Examples

1. EXPLAIN (FORMAT JSON)

EXPLAIN (FORMAT JSON) SELECT * FROM testtb

Output (single row, single JSON object representing the distributed plan tree):

{
  "name": "OutputNode-12",
  "id": "12",
  "properties": {
    "OutputColumns": ["time", "deviceid", "voltage"],
    "OutputSymbols": ["time", "deviceid", "voltage"]
  },
  "children": [
    {
      "name": "CollectNode-11",
      "id": "11",
      "children": [
        {
          "name": "ExchangeNode-9",
          "id": "9",
          "children": [
            {
              "name": "DeviceTableScanNode-7",
              "id": "7",
              "properties": {
                "QualifiedTableName": "testdb.testtb",
                "OutputSymbols": ["time", "deviceid", "voltage"],
                "DeviceNumber": "1",
                "ScanOrder": "ASC",
                "PushDownOffset": "0",
                "PushDownLimit": "0",
                "PushDownLimitToEachDevice": "false",
                "RegionId": "4"
              }
            }
          ]
        }
      ]
    }
  ]
}

2. EXPLAIN (FORMAT JSON) with CTE

EXPLAIN (FORMAT JSON)
WITH cte1 AS MATERIALIZED (SELECT * FROM cte_tb)
SELECT * FROM testtb WHERE testtb.deviceid IN (SELECT deviceid FROM cte1)

When CTEs are present, the JSON wraps the plan with cteQueries and mainQuery:

{
  "cteQueries": [
    {
      "name": "cte1",
      "plan": { "name": "OutputNode-...", "id": "...", ... }
    }
  ],
  "mainQuery": {
    "name": "OutputNode-...",
    "id": "...",
    "properties": { ... },
    "children": [ ... ]
  }
}

3. EXPLAIN ANALYZE (FORMAT JSON)

EXPLAIN ANALYZE (FORMAT JSON) SELECT * FROM testtb

Output (single row, single JSON object with plan statistics + fragment instances):

{
  "planStatistics": {
    "analyzeCostMs": 12.5,
    "fetchPartitionCostMs": 1.2,
    "fetchSchemaCostMs": 0.8,
    "logicalPlanCostMs": 3.1,
    "logicalOptimizationCostMs": 2.0,
    "distributionPlanCostMs": 1.5,
    "dispatchCostMs": 0.9
  },
  "fragmentInstancesCount": 3,
  "fragmentInstances": [
    {
      "id": "queryId.fragmentId.instanceId",
      "ip": "127.0.0.1:10730",
      "dataRegion": "virtual_data_region",
      "state": "FINISHED",
      "totalWallTimeMs": 50.0,
      "initDataQuerySourceCostMs": 5.0,
      "seqFileUnclosed": 0,
      "seqFileClosed": 0,
      "unseqFileUnclosed": 0,
      "unseqFileClosed": 0,
      "readyQueuedTimeMs": 1.0,
      "blockQueuedTimeMs": 0.5,
      "queryStatistics": {
        "timeSeriesIndexFilteredRows": 0,
        "chunkIndexFilteredRows": 0,
        "pageIndexFilteredRows": 0
      },
      "operators": {
        "planNodeId": "1",
        "nodeType": "IdentitySinkNode",
        "operatorType": "IdentitySinkOperator",
        "cpuTimeMs": 2.0,
        "outputRows": 5,
        "hasNextCalledCount": 5,
        "nextCalledCount": 4,
        "estimatedMemorySize": 1024,
        "specifiedInfo": { "DownStreamPlanNodeId": "2" },
        "children": [ ... ]
      }
    }
  ]
}

4. EXPLAIN ANALYZE VERBOSE (FORMAT JSON)

EXPLAIN ANALYZE VERBOSE (FORMAT JSON) SELECT * FROM testtb

Same structure as EXPLAIN ANALYZE (FORMAT JSON), but queryStatistics includes additional verbose fields:

{
  "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,
    "loadTimeSeriesMetadataAlignedMemSeqCount": 2,
    "loadTimeSeriesMetadataAlignedMemSeqTimeMs": 0.1,
    "pageReadersDecodeAlignedMemCount": 2,
    "pageReadersDecodeAlignedMemTimeMs": 0.05
  }
}

5. Invalid format produces clear error

EXPLAIN (FORMAT XML) SELECT * FROM testtb
-- Error: Unsupported EXPLAIN output format: XML. Supported formats: GRAPHVIZ, TEXT, JSON

Key Design Decisions

  • Single-row output: JSON format returns all data in a single row (single JSON string), unlike the multi-line TEXT/GRAPHVIZ formats. This simplifies client-side parsing.
  • Plan node structure: Each plan node is represented as { "name", "id", "properties", "children" }, mirroring the existing GRAPHVIZ box-drawing format.
  • EXPLAIN ANALYZE operator tree: The operators field uses a nested { "planNodeId", "nodeType", "operatorType", "cpuTimeMs", "outputRows", ..., "children" } structure with runtime statistics.
  • Backward compatible: No syntax change for existing queries. The (FORMAT ...) clause is entirely optional.

Test Plan

  • Unit tests for PlanGraphJsonPrinter (plan node → JSON conversion)
  • Unit tests for ExplainFormatTest (SQL parsing of FORMAT clause)
  • Integration tests covering:
    • EXPLAIN (FORMAT JSON) basic query
    • EXPLAIN ANALYZE (FORMAT JSON) with plan statistics and fragment instances
    • EXPLAIN ANALYZE VERBOSE (FORMAT JSON) with verbose query statistics
    • Default format backward compatibility (GRAPHVIZ / TEXT)
    • Explicit FORMAT GRAPHVIZ and FORMAT TEXT
    • Invalid format error handling
    • Multi-partition fragment instance validation
    • CTE support
    • Scalar subquery support

🤖 Generated with Claude Code

JackieTien97 and others added 6 commits April 2, 2026 16:06
- Fix ExplainAnalyzeOperator to only instantiate the needed drawer (TEXT or JSON), avoiding wasted work
- Replace hand-concatenated JSON in mergeExplainResultsJson with Gson to prevent injection from unescaped CTE names
- Add proper imports in PlanGraphJsonPrinter, replace FQN with simple class names
- Use JsonArray for list-type properties instead of String.valueOf()
- Fix ExplainAnalyze.equals()/hashCode() to include outputFormat and verbose
- Add Javadoc to ExplainOutputFormat documenting valid formats per statement
- Default MPPQueryContext.explainOutputFormat to TEXT instead of null
- Mark old 5-arg ExplainAnalyzeOperator constructor @deprecated
- Improve testExplainInvalidFormat to assert on error message content
- Add common pitfalls section to CLAUDE.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 3, 2026

Codecov Report

❌ Patch coverage is 20.83333% with 323 lines in your changes missing coverage. Please review.
✅ Project coverage is 39.74%. Comparing base (55082b2) to head (c8cc5d7).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
...atistics/FragmentInstanceStatisticsJsonDrawer.java 0.00% 169 Missing ⚠️
...e/plan/planner/plan/node/PlanGraphJsonPrinter.java 36.66% 76 Missing ⚠️
...ine/execution/operator/ExplainAnalyzeOperator.java 0.00% 27 Missing ⚠️
...memory/TableModelStatementMemorySourceVisitor.java 0.00% 22 Missing ⚠️
...engine/plan/relational/sql/ast/ExplainAnalyze.java 33.33% 10 Missing ⚠️
...b/queryengine/plan/relational/sql/ast/Explain.java 30.76% 9 Missing ⚠️
...eryengine/plan/planner/TableOperatorGenerator.java 0.00% 4 Missing ⚠️
...an/relational/planner/node/ExplainAnalyzeNode.java 60.00% 2 Missing ⚠️
...yengine/plan/relational/sql/parser/AstBuilder.java 90.00% 2 Missing ⚠️
...e/iotdb/db/queryengine/common/MPPQueryContext.java 75.00% 1 Missing ⚠️
... and 1 more
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #17430      +/-   ##
============================================
- Coverage     39.75%   39.74%   -0.02%     
  Complexity      312      312              
============================================
  Files          5134     5138       +4     
  Lines        346907   347385     +478     
  Branches      44194    44267      +73     
============================================
+ Hits         137907   138054     +147     
- Misses       209000   209331     +331     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

JackieTien97 added a commit to JackieTien97/iotdb-profiler that referenced this pull request Apr 3, 2026
Requires apache/iotdb#17430 to be merged for EXPLAIN (FORMAT JSON) support.
JackieTien97 and others added 2 commits April 3, 2026 18:50
The grammar change added optional '(' FORMAT identifier ')' to EXPLAIN
and EXPLAIN ANALYZE rules, so '(' is now a valid expected token in
parser error messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 3, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant