Skip to content

Commit 86c0ac2

Browse files
committed
Support SQL Server INSERT BULK declarations
1 parent eddb1fb commit 86c0ac2

13 files changed

Lines changed: 476 additions & 1 deletion

File tree

‎src/main/java/net/sf/jsqlparser/parser/feature/Feature.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ public enum Feature {
453453
*
454454
* @see Execute
455455
*/
456-
execute, executeExec, executeCall, executeExecute,
456+
execute, executeExec, executeCall, executeExecute, insertBulk,
457457

458458
/**
459459
* SQL "EXECUTE" statement is allowed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import net.sf.jsqlparser.statement.grant.Grant;
5858
import net.sf.jsqlparser.statement.imprt.Import;
5959
import net.sf.jsqlparser.statement.insert.Insert;
60+
import net.sf.jsqlparser.statement.insert.InsertBulk;
6061
import net.sf.jsqlparser.statement.lock.LockStatement;
6162
import net.sf.jsqlparser.statement.merge.Merge;
6263
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
@@ -387,6 +388,13 @@ public <S> Void visit(Insert insert, S context) {
387388
return super.visit(insert, context);
388389
}
389390

391+
@Override
392+
public <S> Void visit(InsertBulk statement, S context) {
393+
analysis.claimTopLevel();
394+
analysis.certain(StmtFeature.MODIFIES_DATA);
395+
return super.visit(statement, context);
396+
}
397+
390398
@Override
391399
public <S> Void visit(Update update, S context) {
392400
boolean topLevel = analysis.claimTopLevel();

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import net.sf.jsqlparser.statement.grant.Grant;
5353
import net.sf.jsqlparser.statement.imprt.Import;
5454
import net.sf.jsqlparser.statement.insert.Insert;
55+
import net.sf.jsqlparser.statement.insert.InsertBulk;
5556
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
5657
import net.sf.jsqlparser.statement.lock.LockStatement;
5758
import net.sf.jsqlparser.statement.merge.Merge;
@@ -66,6 +67,14 @@
6667

6768
public interface StatementVisitor<T> {
6869

70+
default <S> T visit(InsertBulk statement, S context) {
71+
return null;
72+
}
73+
74+
default void visit(InsertBulk statement) {
75+
this.visit(statement, null);
76+
}
77+
6978
<S> T visit(Analyze analyze, S context);
7079

7180
default void visit(Analyze analyze) {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import net.sf.jsqlparser.statement.grant.Grant;
6060
import net.sf.jsqlparser.statement.imprt.Import;
6161
import net.sf.jsqlparser.statement.insert.Insert;
62+
import net.sf.jsqlparser.statement.insert.InsertBulk;
6263
import net.sf.jsqlparser.statement.insert.InsertConflictAction;
6364
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
6465
import net.sf.jsqlparser.statement.lock.LockStatement;
@@ -311,6 +312,13 @@ private <S> T visitReturningClause(ReturningClause returningClause, S context) {
311312
return null;
312313
}
313314

315+
@Override
316+
public <S> T visit(InsertBulk statement, S context) {
317+
fromItemVisitor.visitFromItem(statement.getTable(), context);
318+
statement.visitExpressions(expression -> expression.accept(expressionVisitor, context));
319+
return null;
320+
}
321+
314322
@Override
315323
public <S> T visit(ParenthesedInsert insert, S context) {
316324
insert.getInsert().accept(this, context);
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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.insert;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.function.Consumer;
15+
import net.sf.jsqlparser.expression.Expression;
16+
import net.sf.jsqlparser.schema.Table;
17+
import net.sf.jsqlparser.statement.Statement;
18+
import net.sf.jsqlparser.statement.StatementVisitor;
19+
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
20+
import net.sf.jsqlparser.statement.select.OrderByElement;
21+
22+
/** SQL Server's SQL declaration preceding a separate bulk-load data stream. */
23+
public class InsertBulk implements Statement {
24+
private Table table;
25+
private List<ColumnDefinition> columns = new ArrayList<>();
26+
private List<Option> options = new ArrayList<>();
27+
28+
public Table getTable() {
29+
return table;
30+
}
31+
32+
public void setTable(Table table) {
33+
this.table = table;
34+
}
35+
36+
public List<ColumnDefinition> getColumns() {
37+
return columns;
38+
}
39+
40+
public void setColumns(List<ColumnDefinition> columns) {
41+
this.columns = columns;
42+
}
43+
44+
public List<Option> getOptions() {
45+
return options;
46+
}
47+
48+
public void setOptions(List<Option> options) {
49+
this.options = options;
50+
}
51+
52+
public void visitExpressions(Consumer<Expression> visitor) {
53+
for (Option option : options) {
54+
if (option.getValue() != null) {
55+
visitor.accept(option.getValue());
56+
}
57+
for (OrderByElement order : option.getOrderByElements()) {
58+
visitor.accept(order.getExpression());
59+
}
60+
}
61+
}
62+
63+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressions,
64+
Consumer<OrderByElement> ordering) {
65+
builder.append("INSERT BULK ").append(table).append(" (");
66+
for (int i = 0; i < columns.size(); i++) {
67+
if (i > 0) {
68+
builder.append(", ");
69+
}
70+
builder.append(columns.get(i));
71+
}
72+
builder.append(')');
73+
if (!options.isEmpty()) {
74+
builder.append(" WITH (");
75+
for (int i = 0; i < options.size(); i++) {
76+
if (i > 0) {
77+
builder.append(", ");
78+
}
79+
options.get(i).appendTo(builder, expressions, ordering);
80+
}
81+
builder.append(')');
82+
}
83+
return builder;
84+
}
85+
86+
@Override
87+
public <T, S> T accept(StatementVisitor<T> visitor, S context) {
88+
return visitor.visit(this, context);
89+
}
90+
91+
@Override
92+
public String toString() {
93+
StringBuilder builder = new StringBuilder();
94+
return appendTo(builder, value -> builder.append(value), order -> builder.append(order))
95+
.toString();
96+
}
97+
98+
public static class Option {
99+
public enum Kind {
100+
CHECK_CONSTRAINTS, FIRE_TRIGGERS, KEEP_NULLS, TABLOCK, ALLOW_ENCRYPTED_VALUE_MODIFICATIONS, ROWS_PER_BATCH, ORDER
101+
}
102+
103+
private Kind kind;
104+
private Expression value;
105+
private List<OrderByElement> orderByElements = new ArrayList<>();
106+
107+
public Option(Kind kind) {
108+
this.kind = kind;
109+
}
110+
111+
public Kind getKind() {
112+
return kind;
113+
}
114+
115+
public void setKind(Kind kind) {
116+
this.kind = kind;
117+
}
118+
119+
public Expression getValue() {
120+
return value;
121+
}
122+
123+
public void setValue(Expression value) {
124+
this.value = value;
125+
}
126+
127+
public List<OrderByElement> getOrderByElements() {
128+
return orderByElements;
129+
}
130+
131+
public void setOrderByElements(List<OrderByElement> orderByElements) {
132+
this.orderByElements = orderByElements;
133+
}
134+
135+
public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressions,
136+
Consumer<OrderByElement> ordering) {
137+
builder.append(kind);
138+
if (value != null) {
139+
builder.append(" = ");
140+
expressions.accept(value);
141+
}
142+
if (!orderByElements.isEmpty()) {
143+
builder.append(" (");
144+
for (int i = 0; i < orderByElements.size(); i++) {
145+
if (i > 0) {
146+
builder.append(", ");
147+
}
148+
ordering.accept(orderByElements.get(i));
149+
}
150+
builder.append(')');
151+
}
152+
return builder;
153+
}
154+
155+
@Override
156+
public String toString() {
157+
StringBuilder builder = new StringBuilder();
158+
return appendTo(builder, value -> builder.append(value), order -> builder.append(order))
159+
.toString();
160+
}
161+
}
162+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@
134134
import net.sf.jsqlparser.statement.grant.Grant;
135135
import net.sf.jsqlparser.statement.imprt.Import;
136136
import net.sf.jsqlparser.statement.insert.Insert;
137+
import net.sf.jsqlparser.statement.insert.InsertBulk;
137138
import net.sf.jsqlparser.statement.insert.InsertConflictAction;
138139
import net.sf.jsqlparser.statement.insert.InsertDuplicateAction;
139140
import net.sf.jsqlparser.statement.insert.OracleMultiInsertBranch;
@@ -1556,6 +1557,13 @@ public <S> Void visit(CreateIndex createIndex, S context) {
15561557
return null;
15571558
}
15581559

1560+
@Override
1561+
public <S> Void visit(InsertBulk statement, S context) {
1562+
visit(statement.getTable(), context);
1563+
statement.visitExpressions(value -> value.accept(this, context));
1564+
return null;
1565+
}
1566+
15591567
@Override
15601568
public void visit(CreateIndex createIndex) {
15611569
StatementVisitor.super.visit(createIndex);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import net.sf.jsqlparser.statement.grant.Grant;
7878
import net.sf.jsqlparser.statement.imprt.Import;
7979
import net.sf.jsqlparser.statement.insert.Insert;
80+
import net.sf.jsqlparser.statement.insert.InsertBulk;
8081
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
8182
import net.sf.jsqlparser.statement.lock.LockStatement;
8283
import net.sf.jsqlparser.statement.merge.Merge;
@@ -146,6 +147,13 @@ public <S> StringBuilder visit(CreateIndex createIndex, S context) {
146147
return builder;
147148
}
148149

150+
@Override
151+
public <S> StringBuilder visit(InsertBulk statement, S context) {
152+
OrderByDeParser order = new OrderByDeParser(expressionDeParser, builder);
153+
return statement.appendTo(builder, value -> value.accept(expressionDeParser, context),
154+
element -> order.deParseElement(element, context));
155+
}
156+
149157
@Override
150158
public <S> StringBuilder visit(CreateTable createTable, S context) {
151159
CreateTableDeParser createTableDeParser = new CreateTableDeParser(this, builder);

‎src/main/java/net/sf/jsqlparser/util/validation/feature/SqlServerVersion.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public enum SqlServerVersion implements Version {
6060

6161
// https://docs.microsoft.com/en-us/sql/t-sql/statements/insert-transact-sql?view=sql-server-ver15
6262
Feature.insert,
63+
Feature.insertBulk,
6364
Feature.insertValues,
6465
Feature.insertFromSelect, // https://docs.microsoft.com/en-us/sql/t-sql/queries/update-transact-sql?view=sql-server-ver15
6566
Feature.update,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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.util.validation.validator;
11+
12+
import java.util.stream.Collectors;
13+
import net.sf.jsqlparser.parser.feature.Feature;
14+
import net.sf.jsqlparser.statement.insert.InsertBulk;
15+
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
16+
import net.sf.jsqlparser.util.validation.ValidationCapability;
17+
import net.sf.jsqlparser.util.validation.metadata.NamedObject;
18+
19+
public class InsertBulkValidator extends AbstractValidator<InsertBulk> {
20+
@Override
21+
public void validate(InsertBulk statement) {
22+
validateFeature(Feature.insertBulk);
23+
validateOptionalFromItem(statement.getTable());
24+
for (ValidationCapability capability : getCapabilities()) {
25+
validateOptionalColumnNames(capability, statement.getColumns().stream()
26+
.map(ColumnDefinition::getColumnName).collect(Collectors.toList()),
27+
NamedObject.table);
28+
}
29+
statement.visitExpressions(this::validateOptionalExpression);
30+
}
31+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
import net.sf.jsqlparser.statement.grant.Grant;
7979
import net.sf.jsqlparser.statement.imprt.Import;
8080
import net.sf.jsqlparser.statement.insert.Insert;
81+
import net.sf.jsqlparser.statement.insert.InsertBulk;
8182
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
8283
import net.sf.jsqlparser.statement.lock.LockStatement;
8384
import net.sf.jsqlparser.statement.merge.Merge;
@@ -228,6 +229,12 @@ public <S> Void visit(Execute execute, S context) {
228229
return null;
229230
}
230231

232+
@Override
233+
public <S> Void visit(InsertBulk statement, S context) {
234+
getValidator(InsertBulkValidator.class).validate(statement);
235+
return null;
236+
}
237+
231238
@Override
232239
public <S> Void visit(SetStatement set, S context) {
233240
getValidator(SetStatementValidator.class).validate(set);

0 commit comments

Comments
 (0)