Skip to content

Commit a0de41e

Browse files
hayssamsclaude
andauthored
Support BigQuery UNNEST WITH OFFSET, array OFFSET(), PARTITION BY expression, EXPORT/LOAD DATA, ASSERT and table snapshots (#2642)
* Support BigQuery array subscript OFFSET(n) 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 * Support BigQuery UNNEST ... WITH OFFSET after the alias 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 * Support ORDER BY after IGNORE NULLS in aggregate functions 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 * Support BigQuery PARTITION BY expression in CREATE TABLE 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 * Support BigQuery ASSERT statement 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 * Support BigQuery EXPORT DATA statement 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 * Support BigQuery LOAD DATA statement 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 * Support CREATE [SNAPSHOT] TABLE ... CLONE 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 --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 0e9c21a commit a0de41e

15 files changed

Lines changed: 943 additions & 7 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
import net.sf.jsqlparser.expression.Expression;
13+
import net.sf.jsqlparser.expression.StringValue;
14+
15+
/**
16+
* BigQuery's {@code ASSERT expression [AS description]}, which fails the script when the expression
17+
* does not evaluate to {@code TRUE}.
18+
*
19+
* @see <a href=
20+
* "https://cloud.google.com/bigquery/docs/reference/standard-sql/debugging-statements">Debugging
21+
* statements</a>
22+
*/
23+
public class AssertStatement implements Statement {
24+
private Expression expression;
25+
private StringValue description;
26+
27+
public AssertStatement() {}
28+
29+
public AssertStatement(Expression expression) {
30+
this.expression = expression;
31+
}
32+
33+
public AssertStatement(Expression expression, StringValue description) {
34+
this.expression = expression;
35+
this.description = description;
36+
}
37+
38+
public Expression getExpression() {
39+
return expression;
40+
}
41+
42+
public void setExpression(Expression expression) {
43+
this.expression = expression;
44+
}
45+
46+
public AssertStatement withExpression(Expression expression) {
47+
setExpression(expression);
48+
return this;
49+
}
50+
51+
public StringValue getDescription() {
52+
return description;
53+
}
54+
55+
public void setDescription(StringValue description) {
56+
this.description = description;
57+
}
58+
59+
public AssertStatement withDescription(StringValue description) {
60+
setDescription(description);
61+
return this;
62+
}
63+
64+
public StringBuilder appendTo(StringBuilder builder) {
65+
builder.append("ASSERT ").append(expression);
66+
if (description != null) {
67+
builder.append(" AS ").append(description);
68+
}
69+
return builder;
70+
}
71+
72+
@Override
73+
public String toString() {
74+
return appendTo(new StringBuilder()).toString();
75+
}
76+
77+
@Override
78+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
79+
return statementVisitor.visit(this, context);
80+
}
81+
}

‎src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@
9393
import java.util.Locale;
9494
import java.util.Set;
9595
import java.util.function.Predicate;
96+
import net.sf.jsqlparser.statement.export.ExportDataStatement;
97+
import net.sf.jsqlparser.statement.load.LoadDataStatement;
9698

9799
/**
98100
* Derives a {@link StatementFeatures} verdict from a statement tree.
@@ -521,6 +523,27 @@ public <S> Void visit(Drop drop, S context) {
521523
return null;
522524
}
523525

526+
@Override
527+
public <S> Void visit(AssertStatement assertStatement, S context) {
528+
analysis.claimTopLevel();
529+
analysis.certain(StmtFeature.READS_DATA);
530+
return null;
531+
}
532+
533+
@Override
534+
public <S> Void visit(ExportDataStatement exportDataStatement, S context) {
535+
analysis.claimTopLevel();
536+
analysis.certain(StmtFeature.READS_DATA, StmtFeature.MODIFIES_DATA);
537+
return null;
538+
}
539+
540+
@Override
541+
public <S> Void visit(LoadDataStatement loadDataStatement, S context) {
542+
analysis.claimTopLevel();
543+
analysis.certain(StmtFeature.MODIFIES_DATA);
544+
return null;
545+
}
546+
524547
@Override
525548
public <S> Void visit(PurgeStatement purgeStatement, S context) {
526549
analysis.claimTopLevel();

‎src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
import net.sf.jsqlparser.statement.update.ParenthesedUpdate;
6969
import net.sf.jsqlparser.statement.update.Update;
7070
import net.sf.jsqlparser.statement.upsert.Upsert;
71+
import net.sf.jsqlparser.statement.export.ExportDataStatement;
72+
import net.sf.jsqlparser.statement.load.LoadDataStatement;
7173

7274
public interface StatementVisitor<T> {
7375

@@ -371,6 +373,24 @@ default void visit(RenameTableStatement renameTableStatement) {
371373
this.visit(renameTableStatement, null);
372374
}
373375

376+
<S> T visit(AssertStatement assertStatement, S context);
377+
378+
default void visit(AssertStatement assertStatement) {
379+
this.visit(assertStatement, null);
380+
}
381+
382+
<S> T visit(ExportDataStatement exportDataStatement, S context);
383+
384+
default void visit(ExportDataStatement exportDataStatement) {
385+
this.visit(exportDataStatement, null);
386+
}
387+
388+
<S> T visit(LoadDataStatement loadDataStatement, S context);
389+
390+
default void visit(LoadDataStatement loadDataStatement) {
391+
this.visit(loadDataStatement, null);
392+
}
393+
374394
<S> T visit(PurgeStatement purgeStatement, S context);
375395

376396
default void visit(PurgeStatement purgeStatement) {

‎src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@
8989

9090
import java.util.List;
9191
import net.sf.jsqlparser.util.TableDefinitionTraversal;
92+
import net.sf.jsqlparser.statement.export.ExportDataStatement;
93+
import net.sf.jsqlparser.statement.load.LoadDataStatement;
9294

9395
@SuppressWarnings({"PMD.UncommentedEmptyMethodBody"})
9496
public class StatementVisitorAdapter<T> implements StatementVisitor<T> {
@@ -616,6 +618,21 @@ public <S> T visit(RenameTableStatement renameTableStatement, S context) {
616618
return null;
617619
}
618620

621+
@Override
622+
public <S> T visit(AssertStatement assertStatement, S context) {
623+
return null;
624+
}
625+
626+
@Override
627+
public <S> T visit(ExportDataStatement exportDataStatement, S context) {
628+
return null;
629+
}
630+
631+
@Override
632+
public <S> T visit(LoadDataStatement loadDataStatement, S context) {
633+
return null;
634+
}
635+
619636
@Override
620637
public <S> T visit(PurgeStatement purgeStatement, S context) {
621638
return null;

‎src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class CreateTable implements Statement {
3636
private List<TableElement> tableElements;
3737
private Select select;
3838
private Table likeTable;
39+
private Table cloneTable;
3940
private ColDataType ofType;
4041
private boolean selectParenthesis;
4142
private boolean ifNotExists = false;
@@ -236,6 +237,22 @@ public void setLikeTable(Table likeTable, boolean parenthesis) {
236237
this.selectParenthesis = parenthesis;
237238
}
238239

240+
/**
241+
* BigQuery's {@code CREATE [SNAPSHOT] TABLE target CLONE source [FOR SYSTEM_TIME AS OF ...]}.
242+
*/
243+
public Table getCloneTable() {
244+
return cloneTable;
245+
}
246+
247+
public void setCloneTable(Table cloneTable) {
248+
this.cloneTable = cloneTable;
249+
}
250+
251+
public CreateTable withCloneTable(Table cloneTable) {
252+
setCloneTable(cloneTable);
253+
return this;
254+
}
255+
239256
public boolean isIfNotExists() {
240257
return ifNotExists;
241258
}
@@ -330,6 +347,9 @@ private void appendCreateClause(StringBuilder b) {
330347
if (partitionOf != null) {
331348
b.append(" PARTITION OF ").append(partitionOf);
332349
}
350+
if (cloneTable != null) {
351+
b.append(" CLONE ").append(cloneTable);
352+
}
333353
}
334354

335355
private void appendColumnDefinitions(StringBuilder b) {

‎src/main/java/net/sf/jsqlparser/statement/create/table/TablePartitioning.java‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@
2727
public class TablePartitioning implements Serializable {
2828

2929
public enum Type {
30-
HASH, KEY, RANGE, LIST
30+
HASH, KEY, RANGE, LIST,
31+
/**
32+
* BigQuery partitions by a bare expression, without a partitioning method keyword:
33+
* {@code PARTITION BY DATE(ts)}.
34+
*/
35+
EXPRESSION
3136
}
3237

3338
private Type type;
@@ -285,6 +290,10 @@ public void appendTo(StringBuilder builder, Consumer<Expression> expressionPrint
285290
}
286291

287292
private void appendMethod(StringBuilder builder, Consumer<Expression> expressionPrinter) {
293+
if (type == Type.EXPRESSION) {
294+
expressionPrinter.accept(expression);
295+
return;
296+
}
288297
if (linear) {
289298
builder.append("LINEAR ");
290299
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.export;
11+
12+
import net.sf.jsqlparser.expression.Expression;
13+
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
14+
import net.sf.jsqlparser.statement.Statement;
15+
import net.sf.jsqlparser.statement.StatementVisitor;
16+
import net.sf.jsqlparser.statement.select.Select;
17+
18+
/**
19+
* BigQuery's {@code EXPORT DATA [WITH CONNECTION connection] OPTIONS (option_list) AS query}.
20+
*
21+
* @see <a href=
22+
* "https://cloud.google.com/bigquery/docs/reference/standard-sql/export-statements">Export
23+
* statements</a>
24+
*/
25+
public class ExportDataStatement implements Statement {
26+
private String connectionName;
27+
private ExpressionList<Expression> options;
28+
private Select select;
29+
30+
public String getConnectionName() {
31+
return connectionName;
32+
}
33+
34+
public void setConnectionName(String connectionName) {
35+
this.connectionName = connectionName;
36+
}
37+
38+
public ExportDataStatement withConnectionName(String connectionName) {
39+
setConnectionName(connectionName);
40+
return this;
41+
}
42+
43+
public ExpressionList<Expression> getOptions() {
44+
return options;
45+
}
46+
47+
public void setOptions(ExpressionList<Expression> options) {
48+
this.options = options;
49+
}
50+
51+
public ExportDataStatement withOptions(ExpressionList<Expression> options) {
52+
setOptions(options);
53+
return this;
54+
}
55+
56+
public Select getSelect() {
57+
return select;
58+
}
59+
60+
public void setSelect(Select select) {
61+
this.select = select;
62+
}
63+
64+
public ExportDataStatement withSelect(Select select) {
65+
setSelect(select);
66+
return this;
67+
}
68+
69+
public StringBuilder appendTo(StringBuilder builder) {
70+
builder.append("EXPORT DATA");
71+
if (connectionName != null) {
72+
builder.append(" WITH CONNECTION ").append(connectionName);
73+
}
74+
if (options != null) {
75+
builder.append(" OPTIONS (").append(options).append(")");
76+
}
77+
builder.append(" AS ").append(select);
78+
return builder;
79+
}
80+
81+
@Override
82+
public String toString() {
83+
return appendTo(new StringBuilder()).toString();
84+
}
85+
86+
@Override
87+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
88+
return statementVisitor.visit(this, context);
89+
}
90+
}

0 commit comments

Comments
 (0)