0.1.13: the SELECT inside a SELECT is its own statement
A SELECT inside parentheses is its own statement: the list of an IN (SELECT ...), the body of an EXISTS (SELECT ...), the table behind FROM (SELECT ...) d, and a bracketed UNION branch. Four open-source MyBatis projects (378 mapper XML files, 1666 statement records) contain 17 such bodies between them; all 17 now get examined, 11 extra findings appeared and none disappeared.
0.1.13 — 2026-09-22
Closes the one miss the previous release documented instead of fixing: a table that
only appears inside parentheses was never examined.
Added
- A
SELECTinside parentheses is analysed as a statement of its own, at any
nesting depth: the list of anIN (SELECT ...), the body of an
EXISTS (SELECT ...), the derived table behindFROM (SELECT ...) d, and a
bracketedUNIONbranch. Each one runs against its own tables and needs its own
indexes, so the outer statement's parse could not stand in for it. They enter the
run as sibling records (src/core/subqueries.ts), inheriting the parent's
occurrences and cost, and identical bodies fold into one record by fingerprint.
Measured over four open-source MyBatis projects — 378 mapper XML files, 1,666
statement records — which hold 17 such bodies between them, and every one of them
produced advice:paicoding'sEXISTS (SELECT 1 FROM column_article ca WHERE ca.article_id = a.id AND ca.column_id = ?),zheng'supms_user_role, whose DDL
has no secondary index at all, andmall's two bracketedUNIONbranches. Cost
on the 908-recordmallrun: +40 ms. - A skip now says how much of the run it covers.
skippedwas a per-rule
summary, so aWITHstatement — outer query with no resolvable table, inner body
with a real access path — printed "not evaluated: SIA001" underneath the
suggestion SIA001 had just produced. The footer, the CI annotation and the
migration header now readno table could be identified, on 1 of 2 statements,
and stay unqualified when the skip really is the whole run.
Fixed
- Inside a subquery, a bare column on the right of
=is no longer an index
candidate. MySQL resolves such a name inner-first, soWHERE child_id = parent_idmay be testing the outer query's column, and an unqualified name
thatorder_itemdoes not have would have been written intoALTER TABLE order_item. Qualified names needed no new rule — an unknown qualifier was
already dropped. Pinned both ways intests/subquery.test.ts. - The
WITHcaveat reached an English report half in Chinese
(不支持的语句类型, skipped(支持 …)). The reason map now covers that note and
the two new ones, and a test asserts the caveats added for known gaps contain no
untranslated Chinese when--lang enis asked for.
What this still does not do
A filter on a derived table's alias (WHERE d.total > 10) names no physical table
and is reported rather than guessed at; an unbracketed UNION branch is still not
analysed, because a trailing ORDER BY / LIMIT belongs to the union result
rather than to the last query; and no cost model is applied to the correlation
itself (a correlated body is weighted as one execution per parent execution, which
understates a per-row probe and overstates a one-shot IN list).