Skip to content

jdbc-v2: heredoc containing !, &, | or ~ makes the JavaCC parser classify the statement as UNKNOWN #3029

Description

@polyglotAI-bot

Describe the bug

With the default SQL parser (jdbc_sql_parser=JAVACC), a heredoc string whose body contains a character that is not a valid standalone SQL token — !, &, | or ~ — aborts the whole statement parse. The statement is then reported as UNKNOWN, i.e. isInsert() == false, isHasResultSet() == true and hasErrors() == true, and no table name, values-list positions or value groups are extracted.

The JavaCC lexer has no heredoc token at all: $$a!b$$ is lexed as ordinary SQL tokens ($$a is even a valid IDENTIFIER), so ! produces a TokenMgrException. That exception is not a ParseException, so the recovery in dataClause() never sees it; it escapes to ClickHouseSqlParser.parse, which discards the parse result. The grammar's own FIXME in dataClause() already flags the missing heredoc lexical state.

The server accepts all of these (heredoc bodies are opaque string data), and both ANTLR4 backends classify them correctly.

Steps to reproduce

SqlParserFacade p = SqlParserFacade.getParser("JAVACC",
        new JdbcConfiguration("jdbc:ch:http://localhost:8123", new Properties()));
for (String sql : new String[] {
        "INSERT INTO t VALUES ($$a@b$$, 1)",
        "INSERT INTO t VALUES ($$a!b$$, 1)",
        "INSERT INTO t VALUES ($$a&b$$, 1)",
        "INSERT INTO t VALUES ($$a|b$$, 1)",
        "INSERT INTO t VALUES ($$a~b$$, 1)",
        "INSERT INTO t VALUES ($tag$a!b$tag$, 1)",
        "SELECT $$a!b$$ AS x FROM t" }) {
    ParsedPreparedStatement s = p.parsePreparedStatement(sql);
    System.out.println(sql + " -> insert=" + s.isInsert()
            + " rs=" + s.isHasResultSet() + " err=" + s.isHasErrors() + " table=" + s.getTable());
}

Observed on main (1a11756):

INSERT INTO t VALUES ($$a@b$$, 1)        -> insert=true  rs=false err=false table=t
INSERT INTO t VALUES ($$a!b$$, 1)        -> insert=false rs=true  err=true  table=unknown
INSERT INTO t VALUES ($$a&b$$, 1)        -> insert=false rs=true  err=true  table=unknown
INSERT INTO t VALUES ($$a|b$$, 1)        -> insert=false rs=true  err=true  table=unknown
INSERT INTO t VALUES ($$a~b$$, 1)        -> insert=false rs=true  err=true  table=unknown
INSERT INTO t VALUES ($tag$a!b$tag$, 1)  -> insert=false rs=true  err=true  table=unknown
SELECT $$a!b$$ AS x FROM t               -> insert=false rs=true  err=true  table=unknown

Expected: every one of these is recognized (insert=true / a SELECT with a result set, err=false, table=t), as the ANTLR4 backends already report.

Impact

A recognized INSERT is what enables the values-list template used by batch inserts, the table-name-based paths (e.g. the beta RowBinary writer) and correct execute() / getMetaData() routing, so a heredoc anywhere in the statement silently disables them and logs a parse warning. A heredoc body containing a ; is also at risk of being treated as a statement separator.

Configuration

  • Environment: Linux, JDK 17
  • ClickHouse client version: main @ 1a11756 (0.11.0-rc1)
  • ClickHouse server version: 26.5.1.882
  • Parser: default jdbc_sql_parser=JAVACC (both ANTLR4 backends are unaffected)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions