Skip to content

fix: allow reserved keywords as JSON path names (Snowflake, Databricks) - #2913

Open
harryfoster2006 wants to merge 2 commits into
JSQLParser:masterfrom
harryfoster2006:fix/snowflake-json-path-keywords
Open

harryfoster2006 wants to merge 2 commits into
JSQLParser:masterfrom
harryfoster2006:fix/snowflake-json-path-keywords

Conversation

@harryfoster2006

Copy link
Copy Markdown

Fixes a ParseException on JSON paths whose element names are reserved keywords. For example:

INSERT INTO "MY_DB"."MY_SCHEMA"."T" ("c")
SELECT "F"."DATA_VALUE":window::VARCHAR
FROM "MY_DB"."MY_SCHEMA"."RAW_TRACES" AS "F"

Produces error[SqlParseException]: unexpected ":" (DOUBLE_COLON)

Path elements after : were parsed as ordinary column names or expressions. Reserved words such as WINDOW, OUTPUT, FROM and SELECT have no identifier token, so the parser threw. After : a name is mandatory, so any word there is a name, reserved or not.

Changes

The two grammar sites that read a path element (PrimaryExpression and JsonExpression) now go through a new JsonPathElement production. It looks ahead before choosing how to parse:

  • isKeywordJsonPathName(): the word after : is a keyword and is not followed by (. It is parsed by the new JsonPathKeyword production, which takes the token as the name. It then continues like a primary expression: further steps, :: casts and the same arithmetic tier as before, including MySQL's lower floor.
  • isJsonPathWithKeywordAfterDot(): the path starts with an identifier but reaches a keyword after a ., such as v:a.output or v:a[0].from.x. The expression parser would read that as a qualified column name, which doesn't accept a keyword as a part. A new JsonPathStep production takes the keyword as an element name.

Behaviour that stays the same:

  • A word followed by ( stays a function call (v:left(x)).
  • Keywords after a . outside a JSON path are still rejected (SELECT t.output still throws).
  • Paths with no keywords take the same parsing route as before.

AST links

JsonPathKeyword linked the previous element before building each step, so the last step or cast of a path never got an AST node (for example the cast in v:select::INT, or v:from::INT + 1). ASTNodeCoverageTest didn't catch this because it only walks the SQL test resources. Each step and cast is now linked where it's built.

harryfoster2006 and others added 2 commits November 11, 2026 15:14
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…r AST nodes

The previous commit lets a keyword name the first element after ":" in a
JSON path. A keyword further along the path still threw a ParseException:

  SELECT v:a.output, v:a.b.from::INT, v:a[0].from.x[1] FROM t

The expression parser reads v:a.output as a qualified column name, and a
qualified name does not accept a keyword as a part. JsonPathElement now
also looks ahead with isJsonPathWithKeywordAfterDot(), which walks the
identifiers, dots and [...] subscripts of the path and reports whether a
keyword follows a ".". If so, JsonPathKeyword parses the path, with a new
JsonPathStep production taking a keyword after "." as an element name.
isKeywordJsonPathName() shares its keyword test with it through
isKeywordWord(). A word followed by "(" stays a function call, and
t.output outside a JSON path is still rejected.

JsonPathKeyword also left the last step or cast of a path without an AST
node, for example the cast in v:select::INT or v:from::INT + 1. It linked
the previous element before building each step, so the one built last was
never linked. ASTNodeCoverageTest did not notice, as it only walks the SQL
resources. Each step and cast is now linked where it is built, and the
keyword-path cases are added to testStatementsTheCorpusLacks.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants