Skip to content

Commit c70cf0a

Browse files
hayssamsclaude
andcommitted
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
1 parent 163ff40 commit c70cf0a

9 files changed

Lines changed: 200 additions & 0 deletions

File tree

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

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

9798
/**
9899
* Derives a {@link StatementFeatures} verdict from a statement tree.
@@ -528,6 +529,13 @@ public <S> Void visit(AssertStatement assertStatement, S context) {
528529
return null;
529530
}
530531

532+
@Override
533+
public <S> Void visit(ExportDataStatement exportDataStatement, S context) {
534+
analysis.claimTopLevel();
535+
analysis.certain(StmtFeature.READS_DATA, StmtFeature.MODIFIES_DATA);
536+
return null;
537+
}
538+
531539
@Override
532540
public <S> Void visit(PurgeStatement purgeStatement, S context) {
533541
analysis.claimTopLevel();

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
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;
7172

7273
public interface StatementVisitor<T> {
7374

@@ -377,6 +378,12 @@ default void visit(AssertStatement assertStatement) {
377378
this.visit(assertStatement, null);
378379
}
379380

381+
<S> T visit(ExportDataStatement exportDataStatement, S context);
382+
383+
default void visit(ExportDataStatement exportDataStatement) {
384+
this.visit(exportDataStatement, null);
385+
}
386+
380387
<S> T visit(PurgeStatement purgeStatement, S context);
381388

382389
default void visit(PurgeStatement purgeStatement) {

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

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

9090
import java.util.List;
9191
import net.sf.jsqlparser.util.TableDefinitionTraversal;
92+
import net.sf.jsqlparser.statement.export.ExportDataStatement;
9293

9394
@SuppressWarnings({"PMD.UncommentedEmptyMethodBody"})
9495
public class StatementVisitorAdapter<T> implements StatementVisitor<T> {
@@ -623,6 +624,11 @@ public <S> T visit(AssertStatement assertStatement, S context) {
623624
return null;
624625
}
625626

627+
@Override
628+
public <S> T visit(ExportDataStatement exportDataStatement, S context) {
629+
return null;
630+
}
631+
626632
@Override
627633
public <S> T visit(PurgeStatement purgeStatement, S context) {
628634
return null;
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+
}

src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@
210210
import net.sf.jsqlparser.statement.update.UpdateSet;
211211
import net.sf.jsqlparser.statement.upsert.Upsert;
212212
import net.sf.jsqlparser.statement.AssertStatement;
213+
import net.sf.jsqlparser.statement.export.ExportDataStatement;
213214

214215

215216
/**
@@ -2476,6 +2477,17 @@ public void visit(AssertStatement assertStatement) {
24762477
StatementVisitor.super.visit(assertStatement);
24772478
}
24782479

2480+
@Override
2481+
public <S> Void visit(ExportDataStatement exportDataStatement, S context) {
2482+
exportDataStatement.getSelect().accept((SelectVisitor<?>) this, context);
2483+
return null;
2484+
}
2485+
2486+
@Override
2487+
public void visit(ExportDataStatement exportDataStatement) {
2488+
StatementVisitor.super.visit(exportDataStatement);
2489+
}
2490+
24792491
@Override
24802492
public <S> Void visit(PurgeStatement purgeStatement, S context) {
24812493
if (purgeStatement.getPurgeObjectType() == PurgeObjectType.TABLE) {

src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
import net.sf.jsqlparser.statement.update.Update;
9999
import net.sf.jsqlparser.statement.upsert.Upsert;
100100
import net.sf.jsqlparser.statement.AssertStatement;
101+
import net.sf.jsqlparser.statement.export.ExportDataStatement;
101102

102103
public class StatementDeParser extends AbstractDeParser<Statement>
103104
implements StatementVisitor<StringBuilder> {
@@ -559,6 +560,12 @@ public <S> StringBuilder visit(AssertStatement assertStatement, S context) {
559560
return builder;
560561
}
561562

563+
@Override
564+
public <S> StringBuilder visit(ExportDataStatement exportDataStatement, S context) {
565+
exportDataStatement.appendTo(builder);
566+
return builder;
567+
}
568+
562569
@Override
563570
public <S> StringBuilder visit(PurgeStatement purgeStatement, S context) {
564571
purgeStatement.appendTo(builder);

src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
import net.sf.jsqlparser.util.validation.ValidationCapability;
101101
import net.sf.jsqlparser.util.validation.metadata.NamedObject;
102102
import net.sf.jsqlparser.statement.AssertStatement;
103+
import net.sf.jsqlparser.statement.export.ExportDataStatement;
103104

104105
/**
105106
* @author gitmotte
@@ -484,6 +485,12 @@ public <S> Void visit(AssertStatement assertStatement, S context) {
484485
return null;
485486
}
486487

488+
@Override
489+
public <S> Void visit(ExportDataStatement exportDataStatement, S context) {
490+
// TODO: not yet implemented
491+
return null;
492+
}
493+
487494
@Override
488495
public <S> Void visit(PurgeStatement purgeStatement, S context) {
489496
// TODO: not yet implemented
@@ -704,6 +711,10 @@ public void visit(AssertStatement assertStatement) {
704711
visit(assertStatement, null);
705712
}
706713

714+
public void visit(ExportDataStatement exportDataStatement) {
715+
visit(exportDataStatement, null);
716+
}
717+
707718
public void visit(PurgeStatement purgeStatement) {
708719
visit(purgeStatement, null);
709720
}

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2915,6 +2915,8 @@ Statement SingleStatement() :
29152915
|
29162916
LOOKAHEAD({ getToken(1).kind == K_ASSERT }) stm = AssertStatement()
29172917
|
2918+
LOOKAHEAD({ getToken(1).kind == K_EXPORT && getToken(2).kind == K_DATA }) stm = ExportDataStatement()
2919+
|
29182920
stm = PurgeStatement()
29192921
|
29202922
stm = SessionStatement()
@@ -4316,6 +4318,32 @@ RenameTableStatement RenameTableStatement(): {
43164318
}
43174319
}
43184320

4321+
/**
4322+
* BigQuery's EXPORT DATA [WITH CONNECTION connection] OPTIONS (option_list) AS query.
4323+
*/
4324+
ExportDataStatement ExportDataStatement() #ExportDataStatement:
4325+
{
4326+
ExportDataStatement exportDataStatement = new ExportDataStatement();
4327+
ExpressionList options;
4328+
ObjectNames connectionName;
4329+
Select select;
4330+
}
4331+
{
4332+
<K_EXPORT> <K_DATA>
4333+
[
4334+
LOOKAHEAD({ getToken(1).kind == K_WITH && "CONNECTION".equalsIgnoreCase(getToken(2).image) })
4335+
<K_WITH> <S_IDENTIFIER> connectionName=RelObjectNames()
4336+
{ exportDataStatement.setConnectionName(String.join(".", connectionName.getNames())); }
4337+
]
4338+
[
4339+
LOOKAHEAD({ isKeywordAhead("OPTIONS") })
4340+
<S_IDENTIFIER> "(" options=ExpressionList() ")"
4341+
{ exportDataStatement.setOptions(options); }
4342+
]
4343+
<K_AS> select=Select() { exportDataStatement.setSelect(select); }
4344+
{ return exportDataStatement; }
4345+
}
4346+
43194347
/**
43204348
* BigQuery's ASSERT expression [AS description] debugging statement.
43214349
*/

src/test/java/net/sf/jsqlparser/statement/select/BigQueryTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import net.sf.jsqlparser.expression.ArrayExpression;
1414
import net.sf.jsqlparser.statement.AssertStatement;
1515
import net.sf.jsqlparser.statement.create.table.CreateTable;
16+
import net.sf.jsqlparser.statement.export.ExportDataStatement;
1617
import net.sf.jsqlparser.statement.create.table.TablePartitioning;
1718
import net.sf.jsqlparser.test.TestUtils;
1819
import org.junit.jupiter.api.Assertions;
@@ -241,4 +242,34 @@ void testAssertRemainsUsableAsIdentifier() throws JSQLParserException {
241242
String sqlStr = "SELECT assert FROM t";
242243
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
243244
}
245+
246+
@Test
247+
void testExportData() throws JSQLParserException {
248+
String sqlStr = "EXPORT DATA OPTIONS (uri = 'gs://bucket/*.csv', format = 'CSV', "
249+
+ "overwrite = true) AS SELECT * FROM t";
250+
ExportDataStatement exportData =
251+
(ExportDataStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
252+
253+
Assertions.assertEquals(3, exportData.getOptions().size());
254+
Assertions.assertNull(exportData.getConnectionName());
255+
}
256+
257+
@Test
258+
void testExportDataWithConnection() throws JSQLParserException {
259+
String sqlStr = "EXPORT DATA WITH CONNECTION myproject.us.myconnection "
260+
+ "OPTIONS (uri = 'gs://bucket/*.csv') AS SELECT a FROM t";
261+
ExportDataStatement exportData =
262+
(ExportDataStatement) TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
263+
264+
Assertions.assertEquals("myproject.us.myconnection", exportData.getConnectionName());
265+
}
266+
267+
@Test
268+
void testExportDataTablesNamesFinder() throws JSQLParserException {
269+
String sqlStr = "EXPORT DATA OPTIONS (uri = 'gs://bucket/*.csv') AS SELECT * FROM ds.t";
270+
271+
Assertions.assertEquals(java.util.Collections.singletonList("ds.t"),
272+
new net.sf.jsqlparser.util.TablesNamesFinder<Void>()
273+
.getTableList(net.sf.jsqlparser.parser.CCJSqlParserUtil.parse(sqlStr)));
274+
}
244275
}

0 commit comments

Comments
 (0)