Use the EXPLAIN syntax to inspect how Lindorm SQL executes a query. Query plans reveal the data scanning method, index selection, relational operations, and push-down behavior — giving you a clear view into what the query engine is doing so you can diagnose issues and tune performance.
Query plans are supported for LindormTable and LindormTSDB.
How it works
When Lindorm SQL receives a query, it parses the SQL into an abstract syntax tree, rewrites it into a logical plan, then applies rule-based or cost-based optimization to produce a physical plan. The physical plan is what actually runs — it specifies which operators execute in Lindorm SQL and which operations are pushed down to the storage engine.
EXPLAIN lets you inspect the plan at three depths:
| Depth | When to use |
|---|---|
| Physical plan (default) | Diagnose performance issues: check which index is selected, the scan range, and how operations are ordered |
| Logical plan | Debug semantic issues: inspect how Lindorm SQL rewrites your query before optimization |
| Type plan | Verify column types: confirm the output schema without running a full plan |
Query plan depths
Logical plan
The logical plan shows the query structure after rewriting, before optimization. Lindorm SQL generates it from the abstract syntax tree and may apply rewrites such as:
Replacing a constant expression with its computed value
Replacing
AVGwith equivalentSUMandCOUNTexpressions
Use EXPLAIN WITHOUT IMPLEMENTATION FOR to view the logical plan:
EXPLAIN WITHOUT IMPLEMENTATION FOR
SELECT LOCATE('9378', p1) AS lp, COUNT(c1)
FROM (SELECT * FROM test WHERE p1 LIKE '3_%')
GROUP BY lp
ORDER BY lp;Output:
********************* 1. row *********************
PLAN: LogicalSort(sort0=[$0], dir0=[ASC])
LogicalAggregate(group=[{0}], EXPR$1=[COUNT($1)])
LogicalProject(lp=[LOCATE('9378', $0)], c1=[$1])
LogicalFilter(condition=[LIKE($0, '3_%')])
LindormTableScan(table=[[db, test]])For the full EXPLAIN syntax, see EXPLAIN.
Physical plan
The physical plan is the optimized, executable version of the query. The optimizer selects the best execution mode based on rule-based or cost-based policies. This is the default output of EXPLAIN and shows the complete execution path: which operators run in Lindorm SQL, which operations are pushed down to LindormTable or LindormTSDB, the index chosen, the scan range, and the filter conditions.
Use EXPLAIN FOR (or just EXPLAIN) to view the physical plan:
EXPLAIN
SELECT LOCATE('9378', p1) AS lp, COUNT(c1)
FROM (SELECT * FROM test WHERE p1 LIKE '3_%')
GROUP BY lp
ORDER BY lp;Output:
********************* 1. row *********************
PLAN: EnumerableSort(sort0=[$0], dir0=[ASC])
EnumerableAggregate(group=[{0}], EXPR$1=[COUNT($1)])
EnumerableCalc(expr#0..3=[{inputs}], expr#4=['9378'], expr#5=[LOCATE($t4, $t0)], lp=[$t5], c1=[$t1])
EnumerableLindormDirectQuery(
explanation=[SELECT p1,c1,c2,c3 from test where ((p1 < 4) AND (p1 >= 3) AND (p1 LIKE 3_%)) supportEmptyResult true
Candidate tables:
Data table: test, scores=1..0..1, need query back=false, sort type=FORWARD
Chose data table [test].
---
SELECT p1,c1,c2,c3 from test supportEmptyResult true
SingleScan on test
ranges: [3\x00, 4\x00]
filter: ((p1 < 4) AND (p1 >= 3) AND (p1 LIKE 3_%))
])Type plan
The type plan contains only the output column types for each column in the result set. It is generated based on query types and is applicable in specific scenarios. It is the most lightweight of the three depths and is useful for quickly verifying the output schema without inspecting the full execution tree.
Use EXPLAIN WITH TYPE FOR to view the type plan:
EXPLAIN WITH TYPE FOR
SELECT LOCATE('9378', p1) AS lp, COUNT(c1)
FROM (SELECT * FROM test WHERE p1 LIKE '3_%')
GROUP BY lp
ORDER BY lp;Output:
********************* 1. row *********************
PLAN: lp INTEGER NOT NULL,
EXPR$1 BIGINT NOT NULLFor the full EXPLAIN syntax, see EXPLAIN.
Operators in query plans
Operators are the building blocks of a physical plan. Each operator represents a discrete step in query execution. Lindorm SQL includes general-purpose operators for computing and a set of extended operators for push-down and engine-specific operations.
General-purpose operators
These operators handle computing operations that run in Lindorm SQL — sorting, aggregation, projection, and calculation.
EnumerableLimit
Skips and truncates the result set based on OFFSET and LIMIT values. The plan output shows the source of the offset and fetch values.
When it appears: Any query with a LIMIT or OFFSET clause.
EnumerableSort
Sorts the result set based on the ORDER BY clause using a treemap-based sorting algorithm. The plan output shows the sorting key and direction.
When it appears: Queries with ORDER BY where sorting cannot be satisfied by the storage-layer scan order. If EnumerableSort appears in your plan, adding an index that matches the sort column may eliminate this operator.
EnumerableAggregate
Aggregates rows returned by lower-level operators using aggregate functions and optional grouping columns. Also handles the DISTINCT syntax. The plan output shows the grouping key and aggregate expressions.
When it appears: Queries with GROUP BY, aggregate functions (COUNT, SUM, AVG, etc.), or DISTINCT.
EnumerableCalc
Evaluates expressions — arithmetic operations, scalar functions (excluding aggregate and window functions) — and performs projection and filtering on rows. This is a general-purpose computation operator.
When it appears: Queries that apply expressions or filters that are not pushed down to the storage engine.
Extended operators
EnumerableLindormDirectQuery
Marks the portion of the query pushed down to the storage engine for execution. The explanation field describes what was pushed down and how the storage engine handled it. The content of explanation differs by engine:
LindormTable
| Field | Description |
|---|---|
| Candidate tables | The candidate indexes evaluated, with scores |
| Chose data table | The index selected for the query |
| Scan type | The scan method (e.g., SingleScan, RangeScan) |
| ranges | The key range scanned |
| filter | Filter conditions applied during the scan |
LindormTSDB
The explanation field contains the parameters of the LindormTSDB API call invoked for the query.
Extended operators for LindormTSDB
These operators are specific to LindormTSDB queries.
TSDBDataScanRel
A variant of LogicalTableScan that performs metric scanning operations. The plan output includes:
filter— filter conditions applied to the scanproject— column mapping between the operator and the time series tablehint— hints passed down with the scan, such as_l_series_only
When it appears: Any query that reads from a LindormTSDB metric table.
TSDBShowTagRel
Optimizes queries that retrieve all values for a specific tag column — equivalent to the SHOW TAG VALUES syntax in InfluxDB. This operator appears at the lowest level of the plan tree and indicates that the tag enumeration is handled directly by the storage engine.
When it appears: Only for queries in the form SELECT DISTINCT <tagkey column> FROM <table>.
TSDBAggScanRel
A combined operator that merges EnumerableAggregation and TSDBDataScanRel into a single push-down step for aggregating time series data.
When it appears: Queries that aggregate time series data where both the scan and aggregation can be pushed down to LindormTSDB.
EnumerableDownsampleQuery
Handles queries that use the SAMPLE BY syntax — a Lindorm SQL extension for downsampling time series data in LindormTSDB. Because SAMPLE BY is a non-standard SQL dialect, it has its own logical operator (LogicalDownsampleQuery) and physical operator (EnumerableDownsampleQuery). The plan output includes:
project— column mapping between the operator and the higher-level planfilter— filter conditions pushed down with the downsampling operationds_agg— the downsampling function and its parametersaggregator— aggregate operators pushed down for cross-time-series aggregationhint— hints pushed down to LindormTSDB
When it appears: Only in queries that use the SAMPLE BY syntax.
What's next
EXPLAIN — full syntax reference for all
EXPLAINvariants