Skip to content

Parser.apply can throw ValidationError instead of returning Left on parenthesized WHERE clauses #250

Description

@fupelaqu

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions