What happened
Split out of #90, where it was left out of scope (it predates that PR; output below is identical on main at e4f9d58).
For a plain SELECT / DELETE / UPDATE, both dialect parsers set HasWhere and HasLimit from the statement's top-level clause only. The FallbackParser matches WHERE / LIMIT anywhere in the sanitized text, including inside subqueries. Whenever the only WHERE or LIMIT sits in a subquery, the parser reports a finding the fallback does not — the direction the parity invariant forbids (TestParser_NeverAddsFindingTheFallbackDoesNot; no corpus row covers it).
SELECT a FROM (SELECT a FROM t LIMIT 3) s
fallback: [] pgparser/mysqlparser: [select-without-limit]
SELECT a FROM (SELECT a FROM t WHERE x = 1) s
fallback: [] pgparser: [select-without-limit]
SELECT a FROM t WHERE id IN (SELECT id FROM u LIMIT 1) ORDER BY a
fallback: [] pgparser: [orderby-without-limit]
DELETE t FROM t JOIN (SELECT id FROM u WHERE x = 1) s ON s.id = t.id
fallback: [] mysqlparser: [delete-without-where]
Set operations are already covered: #90 takes a UNION's HasWhere/HasLimit from the fallback for exactly this reason.
Also raised by CodeAnt on #90 (mysqlparser.go DELETE and fillSelect).
Expected behavior
A dialect parser never reports a finding the fallback does not.
Which side is right differs by case, so this needs a decision rather than a one-line fix:
- A
LIMIT / WHERE in a FROM-clause derived table does bound / filter the result — the parser findings are false positives, the fallback is right.
- A
LIMIT in an IN (...) or scalar subquery bounds nothing outer — the parser is right and the fallback has a false negative.
Options:
- Take
HasWhere/HasLimit from the fallback everywhere (as set operations now do). Parity-safe, but the parser then adds nothing for those two fields.
- Have the parsers count a
LIMIT/WHERE inside a derived table in FROM (correct semantics there), and teach the fallback the IN/scalar-subquery case so both agree — per the AGENTS.md rule that when a parser learns something, the fallback learns it too.
SQL
SELECT a FROM (SELECT a FROM t LIMIT 3) s;
SELECT a FROM (SELECT a FROM t WHERE x = 1) s;
SELECT a FROM t WHERE id IN (SELECT id FROM u LIMIT 1) ORDER BY a;
DELETE t FROM t JOIN (SELECT id FROM u WHERE x = 1) s ON s.id = t.id;
Minimal Go reproduction
a := analyzer.Default().WithParser(pgparser.New())
fmt.Println(analyzer.Default().Analyze("SELECT a FROM (SELECT a FROM t LIMIT 3) s")) // []
fmt.Println(a.Analyze("SELECT a FROM (SELECT a FROM t LIMIT 3) s")) // [select-without-limit]
Entry surface
Analyzer API (direct)
Parser in use
parsers/pgparser
sqlguard version
e4f9d58 (main, post-0.5.0); unchanged by #90
Go version
go1.27.1 linux/amd64
Database and dialect
No response
Additional context
No response
What happened
Split out of #90, where it was left out of scope (it predates that PR; output below is identical on
mainat e4f9d58).For a plain
SELECT/DELETE/UPDATE, both dialect parsers setHasWhereandHasLimitfrom the statement's top-level clause only. The FallbackParser matchesWHERE/LIMITanywhere in the sanitized text, including inside subqueries. Whenever the onlyWHEREorLIMITsits in a subquery, the parser reports a finding the fallback does not — the direction the parity invariant forbids (TestParser_NeverAddsFindingTheFallbackDoesNot; no corpus row covers it).Set operations are already covered: #90 takes a
UNION'sHasWhere/HasLimitfrom the fallback for exactly this reason.Also raised by CodeAnt on #90 (
mysqlparser.goDELETE andfillSelect).Expected behavior
A dialect parser never reports a finding the fallback does not.
Which side is right differs by case, so this needs a decision rather than a one-line fix:
LIMIT/WHEREin a FROM-clause derived table does bound / filter the result — the parser findings are false positives, the fallback is right.LIMITin anIN (...)or scalar subquery bounds nothing outer — the parser is right and the fallback has a false negative.Options:
HasWhere/HasLimitfrom the fallback everywhere (as set operations now do). Parity-safe, but the parser then adds nothing for those two fields.LIMIT/WHEREinside a derived table inFROM(correct semantics there), and teach the fallback theIN/scalar-subquery case so both agree — per the AGENTS.md rule that when a parser learns something, the fallback learns it too.SQL
Minimal Go reproduction
Entry surface
Analyzer API (direct)
Parser in use
parsers/pgparser
sqlguard version
e4f9d58 (main, post-0.5.0); unchanged by #90
Go version
go1.27.1 linux/amd64
Database and dialect
No response
Additional context
No response