Summary
Parser.apply(query: String) is documented and typed as total: Either[ParserError, Statement] (sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala). But three throw ValidationError(...) sites in WhereParser can escape apply, so certain inputs crash the caller instead of returning Left.
Reproduction (verified at 044a9f02)
Parser("select id from emp where (name like_regex 'Jo.*')")
// throws app.softnetwork.elastic.sql.parser.ValidationError: Unbalanced parentheses
// expected: Left(ParserError(...))
Found by round-tripping a jOOQ rendering back through the parser: jOOQ parses SELECT id FROM emp WHERE name RLIKE 'Jo.*' and renders the predicate as (name like_regex 'Jo.*') — a construct elasticsql does not know. The expected outcome is a clean parse rejection; the actual outcome is an unchecked exception. Reproduced identically under three parser configurations.
Root cause
ValidationError is a plain Exception (parser/Validator.scala:56).
WhereParser.extractSubTokens throws it when the token stream ends before a closing delimiter is matched (parser/WhereParser.scala:434, case Nil => throw ValidationError("Unbalanced parentheses")).
- Two sibling sites in the same file can escape the same way:
:378 (Invalid stack state for predicate creation) and :411 (Empty sub-expression).
- Nothing between these sites and
Parser.apply's boundary converts the exception into Left.
Impact
Every caller that pattern-matches on the Either (REPL, GatewayApi.run, the JDBC/Flight drivers, the searchAs macro) gets an unhandled exception on such input instead of a parse error. Any pipeline that feeds machine-generated or user-typed SQL into the parser (BI tools, jOOQ-rendered leaves) turns a loud rejection into a crash.
Family
PR #218 already fixed nine throws escaping Parser.apply (the render/parse asymmetry batch). These three WhereParser sites are survivors of the same defect class.
Suggested fix
Catch ValidationError at the Parser.apply boundary and convert it to Left(ParserError(message)) (or turn the three sites into combinator failures). Regression test: assert Parser(...) returns Left — not throws — for the repro string plus a plain unbalanced input like SELECT a FROM t WHERE (b = 1.
Summary
Parser.apply(query: String)is documented and typed as total:Either[ParserError, Statement](sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala). But threethrow ValidationError(...)sites inWhereParsercan escapeapply, so certain inputs crash the caller instead of returningLeft.Reproduction (verified at
044a9f02)Found by round-tripping a jOOQ rendering back through the parser: jOOQ parses
SELECT id FROM emp WHERE name RLIKE 'Jo.*'and renders the predicate as(name like_regex 'Jo.*')— a construct elasticsql does not know. The expected outcome is a clean parse rejection; the actual outcome is an unchecked exception. Reproduced identically under three parser configurations.Root cause
ValidationErroris a plainException(parser/Validator.scala:56).WhereParser.extractSubTokensthrows it when the token stream ends before a closing delimiter is matched (parser/WhereParser.scala:434,case Nil => throw ValidationError("Unbalanced parentheses")).:378(Invalid stack state for predicate creation) and:411(Empty sub-expression).Parser.apply's boundary converts the exception intoLeft.Impact
Every caller that pattern-matches on the
Either(REPL,GatewayApi.run, the JDBC/Flight drivers, the searchAs macro) gets an unhandled exception on such input instead of a parse error. Any pipeline that feeds machine-generated or user-typed SQL into the parser (BI tools, jOOQ-rendered leaves) turns a loud rejection into a crash.Family
PR #218 already fixed nine
throws escapingParser.apply(the render/parse asymmetry batch). These threeWhereParsersites are survivors of the same defect class.Suggested fix
Catch
ValidationErrorat theParser.applyboundary and convert it toLeft(ParserError(message))(or turn the three sites into combinator failures). Regression test: assertParser(...)returnsLeft— not throws — for the repro string plus a plain unbalanced input likeSELECT a FROM t WHERE (b = 1.