Skip to content

Commit 13aee6c

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

9 files changed

Lines changed: 298 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
@@ -94,6 +94,7 @@
9494
import java.util.Set;
9595
import java.util.function.Predicate;
9696
import net.sf.jsqlparser.statement.export.ExportDataStatement;
97+
import net.sf.jsqlparser.statement.load.LoadDataStatement;
9798

9899
/**
99100
* Derives a {@link StatementFeatures} verdict from a statement tree.
@@ -536,6 +537,13 @@ public <S> Void visit(ExportDataStatement exportDataStatement, S context) {
536537
return null;
537538
}
538539

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+
539547
@Override
540548
public <S> Void visit(PurgeStatement purgeStatement, S context) {
541549
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
@@ -69,6 +69,7 @@
6969
import net.sf.jsqlparser.statement.update.Update;
7070
import net.sf.jsqlparser.statement.upsert.Upsert;
7171
import net.sf.jsqlparser.statement.export.ExportDataStatement;
72+
import net.sf.jsqlparser.statement.load.LoadDataStatement;
7273

7374
public interface StatementVisitor<T> {
7475

@@ -384,6 +385,12 @@ default void visit(ExportDataStatement exportDataStatement) {
384385
this.visit(exportDataStatement, null);
385386
}
386387

388+
<S> T visit(LoadDataStatement loadDataStatement, S context);
389+
390+
default void visit(LoadDataStatement loadDataStatement) {
391+
this.visit(loadDataStatement, null);
392+
}
393+
387394
<S> T visit(PurgeStatement purgeStatement, S context);
388395

389396
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
@@ -90,6 +90,7 @@
9090
import java.util.List;
9191
import net.sf.jsqlparser.util.TableDefinitionTraversal;
9292
import net.sf.jsqlparser.statement.export.ExportDataStatement;
93+
import net.sf.jsqlparser.statement.load.LoadDataStatement;
9394

9495
@SuppressWarnings({"PMD.UncommentedEmptyMethodBody"})
9596
public class StatementVisitorAdapter<T> implements StatementVisitor<T> {
@@ -629,6 +630,11 @@ public <S> T visit(ExportDataStatement exportDataStatement, S context) {
629630
return null;
630631
}
631632

633+
@Override
634+
public <S> T visit(LoadDataStatement loadDataStatement, S context) {
635+
return null;
636+
}
637+
632638
@Override
633639
public <S> T visit(PurgeStatement purgeStatement, S context) {
634640
return null;
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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.load;
11+
12+
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
13+
import net.sf.jsqlparser.schema.Table;
14+
import net.sf.jsqlparser.statement.Statement;
15+
import net.sf.jsqlparser.statement.StatementVisitor;
16+
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
17+
import net.sf.jsqlparser.statement.select.PlainSelect;
18+
19+
import java.util.List;
20+
21+
/**
22+
* BigQuery's {@code LOAD DATA {INTO | OVERWRITE} table [(column_definitions)] [OPTIONS
23+
* (option_list)] FROM FILES (file_options) [WITH PARTITION COLUMNS] [WITH CONNECTION connection]}.
24+
*
25+
* @see <a href=
26+
* "https://cloud.google.com/bigquery/docs/reference/standard-sql/load-statements">Load
27+
* statements</a>
28+
*/
29+
public class LoadDataStatement implements Statement {
30+
private boolean overwrite;
31+
private Table table;
32+
private List<ColumnDefinition> columnDefinitions;
33+
private ExpressionList<?> options;
34+
private ExpressionList<?> fileOptions;
35+
private boolean withPartitionColumns;
36+
private String connectionName;
37+
38+
public boolean isOverwrite() {
39+
return overwrite;
40+
}
41+
42+
public void setOverwrite(boolean overwrite) {
43+
this.overwrite = overwrite;
44+
}
45+
46+
public LoadDataStatement withOverwrite(boolean overwrite) {
47+
setOverwrite(overwrite);
48+
return this;
49+
}
50+
51+
public Table getTable() {
52+
return table;
53+
}
54+
55+
public void setTable(Table table) {
56+
this.table = table;
57+
}
58+
59+
public LoadDataStatement withTable(Table table) {
60+
setTable(table);
61+
return this;
62+
}
63+
64+
public List<ColumnDefinition> getColumnDefinitions() {
65+
return columnDefinitions;
66+
}
67+
68+
public void setColumnDefinitions(List<ColumnDefinition> columnDefinitions) {
69+
this.columnDefinitions = columnDefinitions;
70+
}
71+
72+
public LoadDataStatement withColumnDefinitions(List<ColumnDefinition> columnDefinitions) {
73+
setColumnDefinitions(columnDefinitions);
74+
return this;
75+
}
76+
77+
public ExpressionList<?> getOptions() {
78+
return options;
79+
}
80+
81+
public void setOptions(ExpressionList<?> options) {
82+
this.options = options;
83+
}
84+
85+
public LoadDataStatement withOptions(ExpressionList<?> options) {
86+
setOptions(options);
87+
return this;
88+
}
89+
90+
public ExpressionList<?> getFileOptions() {
91+
return fileOptions;
92+
}
93+
94+
public void setFileOptions(ExpressionList<?> fileOptions) {
95+
this.fileOptions = fileOptions;
96+
}
97+
98+
public LoadDataStatement withFileOptions(ExpressionList<?> fileOptions) {
99+
setFileOptions(fileOptions);
100+
return this;
101+
}
102+
103+
public boolean isWithPartitionColumns() {
104+
return withPartitionColumns;
105+
}
106+
107+
public void setWithPartitionColumns(boolean withPartitionColumns) {
108+
this.withPartitionColumns = withPartitionColumns;
109+
}
110+
111+
public LoadDataStatement withWithPartitionColumns(boolean withPartitionColumns) {
112+
setWithPartitionColumns(withPartitionColumns);
113+
return this;
114+
}
115+
116+
public String getConnectionName() {
117+
return connectionName;
118+
}
119+
120+
public void setConnectionName(String connectionName) {
121+
this.connectionName = connectionName;
122+
}
123+
124+
public LoadDataStatement withConnectionName(String connectionName) {
125+
setConnectionName(connectionName);
126+
return this;
127+
}
128+
129+
public StringBuilder appendTo(StringBuilder builder) {
130+
builder.append("LOAD DATA ").append(overwrite ? "OVERWRITE " : "INTO ").append(table);
131+
if (columnDefinitions != null && !columnDefinitions.isEmpty()) {
132+
builder.append(" ").append(PlainSelect.getStringList(columnDefinitions, true, true));
133+
}
134+
if (options != null) {
135+
builder.append(" OPTIONS (").append(options).append(")");
136+
}
137+
builder.append(" FROM FILES (").append(fileOptions).append(")");
138+
if (withPartitionColumns) {
139+
builder.append(" WITH PARTITION COLUMNS");
140+
}
141+
if (connectionName != null) {
142+
builder.append(" WITH CONNECTION ").append(connectionName);
143+
}
144+
return builder;
145+
}
146+
147+
@Override
148+
public String toString() {
149+
return appendTo(new StringBuilder()).toString();
150+
}
151+
152+
@Override
153+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
154+
return statementVisitor.visit(this, context);
155+
}
156+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@
211211
import net.sf.jsqlparser.statement.upsert.Upsert;
212212
import net.sf.jsqlparser.statement.AssertStatement;
213213
import net.sf.jsqlparser.statement.export.ExportDataStatement;
214+
import net.sf.jsqlparser.statement.load.LoadDataStatement;
214215

215216

216217
/**
@@ -2488,6 +2489,17 @@ public void visit(ExportDataStatement exportDataStatement) {
24882489
StatementVisitor.super.visit(exportDataStatement);
24892490
}
24902491

2492+
@Override
2493+
public <S> Void visit(LoadDataStatement loadDataStatement, S context) {
2494+
loadDataStatement.getTable().accept(this, context);
2495+
return null;
2496+
}
2497+
2498+
@Override
2499+
public void visit(LoadDataStatement loadDataStatement) {
2500+
StatementVisitor.super.visit(loadDataStatement);
2501+
}
2502+
24912503
@Override
24922504
public <S> Void visit(PurgeStatement purgeStatement, S context) {
24932505
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
@@ -99,6 +99,7 @@
9999
import net.sf.jsqlparser.statement.upsert.Upsert;
100100
import net.sf.jsqlparser.statement.AssertStatement;
101101
import net.sf.jsqlparser.statement.export.ExportDataStatement;
102+
import net.sf.jsqlparser.statement.load.LoadDataStatement;
102103

103104
public class StatementDeParser extends AbstractDeParser<Statement>
104105
implements StatementVisitor<StringBuilder> {
@@ -566,6 +567,12 @@ public <S> StringBuilder visit(ExportDataStatement exportDataStatement, S contex
566567
return builder;
567568
}
568569

570+
@Override
571+
public <S> StringBuilder visit(LoadDataStatement loadDataStatement, S context) {
572+
loadDataStatement.appendTo(builder);
573+
return builder;
574+
}
575+
569576
@Override
570577
public <S> StringBuilder visit(PurgeStatement purgeStatement, S context) {
571578
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
@@ -101,6 +101,7 @@
101101
import net.sf.jsqlparser.util.validation.metadata.NamedObject;
102102
import net.sf.jsqlparser.statement.AssertStatement;
103103
import net.sf.jsqlparser.statement.export.ExportDataStatement;
104+
import net.sf.jsqlparser.statement.load.LoadDataStatement;
104105

105106
/**
106107
* @author gitmotte
@@ -491,6 +492,12 @@ public <S> Void visit(ExportDataStatement exportDataStatement, S context) {
491492
return null;
492493
}
493494

495+
@Override
496+
public <S> Void visit(LoadDataStatement loadDataStatement, S context) {
497+
// TODO: not yet implemented
498+
return null;
499+
}
500+
494501
@Override
495502
public <S> Void visit(PurgeStatement purgeStatement, S context) {
496503
// TODO: not yet implemented
@@ -715,6 +722,10 @@ public void visit(ExportDataStatement exportDataStatement) {
715722
visit(exportDataStatement, null);
716723
}
717724

725+
public void visit(LoadDataStatement loadDataStatement) {
726+
visit(loadDataStatement, null);
727+
}
728+
718729
public void visit(PurgeStatement purgeStatement) {
719730
visit(purgeStatement, null);
720731
}

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import net.sf.jsqlparser.statement.create.view.*;
7272
import net.sf.jsqlparser.statement.delete.*;
7373
import net.sf.jsqlparser.statement.drop.*;
7474
import net.sf.jsqlparser.statement.insert.*;
75+
import net.sf.jsqlparser.statement.load.*;
7576
import net.sf.jsqlparser.statement.execute.*;
7677
import net.sf.jsqlparser.statement.piped.*;
7778
import net.sf.jsqlparser.statement.select.*;
@@ -2101,6 +2102,7 @@ String NonReservedWord() :
21012102
| tk=<K_LINEAR:"LINEAR">
21022103
| tk=<K_LINES:"LINES">
21032104
| tk=<K_LIST:"LIST">
2105+
| tk=<K_LOAD: "LOAD">
21042106
| tk=<K_LOCAL:"LOCAL">
21052107
| tk=<K_LOCK:"LOCK">
21062108
| tk=<K_LOCKED:"LOCKED">
@@ -2917,6 +2919,8 @@ Statement SingleStatement() :
29172919
|
29182920
LOOKAHEAD({ getToken(1).kind == K_EXPORT && getToken(2).kind == K_DATA }) stm = ExportDataStatement()
29192921
|
2922+
LOOKAHEAD({ getToken(1).kind == K_LOAD && getToken(2).kind == K_DATA }) stm = LoadDataStatement()
2923+
|
29202924
stm = PurgeStatement()
29212925
|
29222926
stm = SessionStatement()
@@ -4318,6 +4322,48 @@ RenameTableStatement RenameTableStatement(): {
43184322
}
43194323
}
43204324

4325+
/**
4326+
* BigQuery's LOAD DATA {INTO | OVERWRITE} table [(column_definitions)] [OPTIONS (option_list)]
4327+
* FROM FILES (file_options) [WITH PARTITION COLUMNS] [WITH CONNECTION connection].
4328+
*/
4329+
LoadDataStatement LoadDataStatement() #LoadDataStatement:
4330+
{
4331+
LoadDataStatement loadDataStatement = new LoadDataStatement();
4332+
List<ColumnDefinition> columnDefinitions = new ArrayList<ColumnDefinition>();
4333+
ColumnDefinition columnDefinition;
4334+
ExpressionList options;
4335+
ObjectNames connectionName;
4336+
Table table;
4337+
}
4338+
{
4339+
<K_LOAD> <K_DATA>
4340+
( <K_INTO> | <K_OVERWRITE> { loadDataStatement.setOverwrite(true); } )
4341+
table=Table() { loadDataStatement.setTable(table); }
4342+
[
4343+
LOOKAHEAD(2) "("
4344+
columnDefinition=ColumnDefinition() { columnDefinitions.add(columnDefinition); }
4345+
( "," columnDefinition=ColumnDefinition() { columnDefinitions.add(columnDefinition); } )*
4346+
")" { loadDataStatement.setColumnDefinitions(columnDefinitions); }
4347+
]
4348+
[
4349+
LOOKAHEAD({ isKeywordAhead("OPTIONS") })
4350+
<S_IDENTIFIER> "(" options=ExpressionList() ")" { loadDataStatement.setOptions(options); }
4351+
]
4352+
<K_FROM>
4353+
LOOKAHEAD({ isKeywordAhead("FILES") }) <S_IDENTIFIER>
4354+
"(" options=ExpressionList() ")" { loadDataStatement.setFileOptions(options); }
4355+
[
4356+
LOOKAHEAD(2) <K_WITH> <K_PARTITION> <K_COLUMNS>
4357+
{ loadDataStatement.setWithPartitionColumns(true); }
4358+
]
4359+
[
4360+
LOOKAHEAD({ getToken(1).kind == K_WITH && "CONNECTION".equalsIgnoreCase(getToken(2).image) })
4361+
<K_WITH> <S_IDENTIFIER> connectionName=RelObjectNames()
4362+
{ loadDataStatement.setConnectionName(String.join(".", connectionName.getNames())); }
4363+
]
4364+
{ return loadDataStatement; }
4365+
}
4366+
43214367
/**
43224368
* BigQuery's EXPORT DATA [WITH CONNECTION connection] OPTIONS (option_list) AS query.
43234369
*/

0 commit comments

Comments
 (0)