Skip to content

Commit cd406cd

Browse files
committed
Support SQL Server INSERT BULK declarations
1 parent 35f84d5 commit cd406cd

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
@@ -457,7 +457,7 @@ public enum Feature {
457457
*/
458458
oracleBlock,
459459

460-
execute, executeExec, executeCall, executeExecute,
460+
execute, executeExec, executeCall, executeExecute, insertBulk,
461461

462462
/**
463463
* 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
@@ -61,6 +61,7 @@
6161
import net.sf.jsqlparser.statement.grant.Grant;
6262
import net.sf.jsqlparser.statement.imprt.Import;
6363
import net.sf.jsqlparser.statement.insert.Insert;
64+
import net.sf.jsqlparser.statement.insert.InsertBulk;
6465
import net.sf.jsqlparser.statement.lock.LockStatement;
6566
import net.sf.jsqlparser.statement.merge.Merge;
6667
import net.sf.jsqlparser.statement.refresh.RefreshMaterializedViewStatement;
@@ -392,6 +393,13 @@ public <S> Void visit(Insert insert, S context) {
392393
return super.visit(insert, context);
393394
}
394395

396+
@Override
397+
public <S> Void visit(InsertBulk statement, S context) {
398+
analysis.claimTopLevel();
399+
analysis.certain(StmtFeature.MODIFIES_DATA);
400+
return super.visit(statement, context);
401+
}
402+
395403
@Override
396404
public <S> Void visit(Update update, S context) {
397405
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
@@ -56,6 +56,7 @@
5656
import net.sf.jsqlparser.statement.grant.Grant;
5757
import net.sf.jsqlparser.statement.imprt.Import;
5858
import net.sf.jsqlparser.statement.insert.Insert;
59+
import net.sf.jsqlparser.statement.insert.InsertBulk;
5960
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
6061
import net.sf.jsqlparser.statement.lock.LockStatement;
6162
import net.sf.jsqlparser.statement.merge.Merge;
@@ -70,6 +71,14 @@
7071

7172
public interface StatementVisitor<T> {
7273

74+
default <S> T visit(InsertBulk statement, S context) {
75+
return null;
76+
}
77+
78+
default void visit(InsertBulk statement) {
79+
this.visit(statement, null);
80+
}
81+
7382
<S> T visit(Analyze analyze, S context);
7483

7584
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
@@ -62,6 +62,7 @@
6262
import net.sf.jsqlparser.statement.grant.Grant;
6363
import net.sf.jsqlparser.statement.imprt.Import;
6464
import net.sf.jsqlparser.statement.insert.Insert;
65+
import net.sf.jsqlparser.statement.insert.InsertBulk;
6566
import net.sf.jsqlparser.statement.insert.InsertConflictAction;
6667
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
6768
import net.sf.jsqlparser.statement.lock.LockStatement;
@@ -314,6 +315,13 @@ private <S> T visitReturningClause(ReturningClause returningClause, S context) {
314315
return null;
315316
}
316317

318+
@Override
319+
public <S> T visit(InsertBulk statement, S context) {
320+
fromItemVisitor.visitFromItem(statement.getTable(), context);
321+
statement.visitExpressions(expression -> expression.accept(expressionVisitor, context));
322+
return null;
323+
}
324+
317325
@Override
318326
public <S> T visit(ParenthesedInsert insert, S context) {
319327
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
@@ -138,6 +138,7 @@
138138
import net.sf.jsqlparser.statement.grant.Grant;
139139
import net.sf.jsqlparser.statement.imprt.Import;
140140
import net.sf.jsqlparser.statement.insert.Insert;
141+
import net.sf.jsqlparser.statement.insert.InsertBulk;
141142
import net.sf.jsqlparser.statement.insert.InsertConflictAction;
142143
import net.sf.jsqlparser.statement.insert.InsertDuplicateAction;
143144
import net.sf.jsqlparser.statement.insert.OracleMultiInsertBranch;
@@ -1562,6 +1563,13 @@ public <S> Void visit(CreateIndex createIndex, S context) {
15621563
return null;
15631564
}
15641565

1566+
@Override
1567+
public <S> Void visit(InsertBulk statement, S context) {
1568+
visit(statement.getTable(), context);
1569+
statement.visitExpressions(value -> value.accept(this, context));
1570+
return null;
1571+
}
1572+
15651573
@Override
15661574
public void visit(CreateIndex createIndex) {
15671575
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
@@ -81,6 +81,7 @@
8181
import net.sf.jsqlparser.statement.grant.Grant;
8282
import net.sf.jsqlparser.statement.imprt.Import;
8383
import net.sf.jsqlparser.statement.insert.Insert;
84+
import net.sf.jsqlparser.statement.insert.InsertBulk;
8485
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
8586
import net.sf.jsqlparser.statement.lock.LockStatement;
8687
import net.sf.jsqlparser.statement.merge.Merge;
@@ -151,6 +152,13 @@ public <S> StringBuilder visit(CreateIndex createIndex, S context) {
151152
return builder;
152153
}
153154

155+
@Override
156+
public <S> StringBuilder visit(InsertBulk statement, S context) {
157+
OrderByDeParser order = new OrderByDeParser(expressionDeParser, builder);
158+
return statement.appendTo(builder, value -> value.accept(expressionDeParser, context),
159+
element -> order.deParseElement(element, context));
160+
}
161+
154162
@Override
155163
public <S> StringBuilder visit(CreateTable createTable, S context) {
156164
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
@@ -82,6 +82,7 @@
8282
import net.sf.jsqlparser.statement.grant.Grant;
8383
import net.sf.jsqlparser.statement.imprt.Import;
8484
import net.sf.jsqlparser.statement.insert.Insert;
85+
import net.sf.jsqlparser.statement.insert.InsertBulk;
8586
import net.sf.jsqlparser.statement.insert.ParenthesedInsert;
8687
import net.sf.jsqlparser.statement.lock.LockStatement;
8788
import net.sf.jsqlparser.statement.merge.Merge;
@@ -232,6 +233,12 @@ public <S> Void visit(Execute execute, S context) {
232233
return null;
233234
}
234235

236+
@Override
237+
public <S> Void visit(InsertBulk statement, S context) {
238+
getValidator(InsertBulkValidator.class).validate(statement);
239+
return null;
240+
}
241+
235242
@Override
236243
public <S> Void visit(SetStatement set, S context) {
237244
getValidator(SetStatementValidator.class).validate(set);

0 commit comments

Comments
 (0)