Skip to content

Support BigQuery UNNEST WITH OFFSET, array OFFSET(), PARTITION BY expression, EXPORT/LOAD DATA, ASSERT and table snapshots - #2642

Open
hayssams wants to merge 8 commits into
JSQLParser:masterfrom
starlake-ai:feature/bigquery-syntax
Open

hayssams wants to merge 8 commits into
JSQLParser:masterfrom
starlake-ai:feature/bigquery-syntax

Conversation

@hayssams

Copy link
Copy Markdown
Contributor

Adds support for eight BigQuery (GoogleSQL) constructs that JSqlParser rejected. One commit per feature, each with tests that assert both the AST and a stable round-trip through toString() and the deparser.

Features, with the BigQuery documentation that specifies them

Feature Example GoogleSQL reference
UNNEST(...) [AS alias] WITH OFFSET [AS offsetAlias] FROM UNNEST([1, 2, 3]) AS x WITH OFFSET AS pos UNNEST operator
Array subscript OFFSET(n) SELECT arr[OFFSET(0)] Array subscript operator
ORDER BY after IGNORE NULLS in aggregates ARRAY_AGG(x IGNORE NULLS ORDER BY x LIMIT 5) ARRAY_AGG
PARTITION BY <expression> in CREATE TABLE PARTITION BY DATE(ts) CREATE TABLE statement
ASSERT expression [AS description] ASSERT (SELECT COUNT(*) FROM t) > 0 AS 'empty' Debugging statements
EXPORT DATA [WITH CONNECTION c] OPTIONS (...) AS query EXPORT DATA OPTIONS (uri = 'gs://b/*.csv') AS SELECT * FROM t Export statements
LOAD DATA {INTO | OVERWRITE} ... FROM FILES (...) LOAD DATA INTO ds.t FROM FILES (format = 'AVRO') Load statements
CREATE [SNAPSHOT] TABLE target CLONE source CREATE SNAPSHOT TABLE ds.snap CLONE ds.t FOR SYSTEM_TIME AS OF ... CREATE SNAPSHOT TABLE

Notes

  • ASSERT, EXPORT DATA and LOAD DATA are new Statement types, wired through StatementVisitor, StatementVisitorAdapter, StatementDeParser, TablesNamesFinder, StatementFeatureVisitor and StatementValidator. TablesNamesFinder reaches the exported query and the load target.
  • CLONE is kept on CreateTable and rendered by both toString() and CreateTableDeParser.
  • PARTITION BY <expression> is a new TablePartitioning.Type.EXPRESSION; the MySQL HASH/KEY/RANGE/LIST forms keep priority.
  • New keywords (ASSERT, CLONE, LOAD) are non-reserved and stay usable as identifiers; regression tests cover that.

Test plan

  • New tests in BigQueryTest, including negative/regression cases (LIMIT ... OFFSET, WITH ORDINALITY AS t(a, b), PARTITION BY HASH (id), SELECT assert FROM t).
  • Full suite: 6951 tests, 1 failure — SpecialOracleTest.testAllSqlsParseDeparse, which fails on an unmodified master in this environment too.
  • JavaCC still reports 0 errors and 1 warnings, the same warning as on master.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2

hayssams and others added 8 commits September 15, 2026 14:18
BigQuery addresses array elements positionally with OFFSET(n),
ORDINAL(n), SAFE_OFFSET(n) and SAFE_ORDINAL(n). The latter three already
parsed as functions; OFFSET is a reserved keyword and needed an explicit
array-index rule. The accessor is kept in the AST as a Function so it
deparses unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2
BigQuery writes UNNEST(array) [AS alias] WITH OFFSET [AS offsetAlias],
where WITH OFFSET follows the table alias and carries an alias of its
own. The pre-existing pre-alias form (WITH ORDINALITY|OFFSET AS t(a, b))
is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2
BigQuery orders aggregate arguments as
ARRAY_AGG(expr [IGNORE NULLS] [ORDER BY key] [LIMIT n]), so the ORDER BY
may follow the null handling. Only the pre-null-handling position was
accepted before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2
BigQuery partitions by a bare expression rather than a partitioning
method: PARTITION BY d, PARTITION BY DATE(ts), PARTITION BY
RANGE_BUCKET(id, GENERATE_ARRAY(0, 100, 10)). Adds
TablePartitioning.Type.EXPRESSION, which renders the expression without
a method keyword or parentheses. The MySQL HASH/KEY/RANGE/LIST forms
keep priority.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2
Adds ASSERT expression [AS description] as a statement, wired through
the statement visitors, deparser and table-name finder. ASSERT stays a
non-reserved keyword.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2
Adds EXPORT DATA [WITH CONNECTION connection] OPTIONS (option_list) AS
query. Options are kept as an expression list, and the exported query is
reachable from TablesNamesFinder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2
Adds LOAD DATA {INTO | OVERWRITE} table [(column_definitions)]
[OPTIONS (option_list)] FROM FILES (file_options)
[WITH PARTITION COLUMNS] [WITH CONNECTION connection]. LOAD becomes a
non-reserved keyword so that a statement never starts with a bare
identifier, which would make statement starts ambiguous elsewhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2
BigQuery snapshots and clones a table with
CREATE [SNAPSHOT] TABLE target CLONE source [FOR SYSTEM_TIME AS OF ...].
The clone source is kept on CreateTable and rendered by both toString()
and CreateTableDeParser. SNAPSHOT itself already parsed as a create
option.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2
@manticore-projects

Copy link
Copy Markdown
Contributor

Run './gradlew spotlessApply' to fix all violations.

@hayssams
hayssams force-pushed the feature/bigquery-syntax branch from 6d597a2 to eecd758 Compare September 15, 2026 14:13
@hayssams

Copy link
Copy Markdown
Contributor Author

@manticore-projects
Done!
It now works
Thanks

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