Skip to content

Commit 953b921

Browse files
committed
Add docker-compose.yml and fix Bulk Insert Issues
1 parent d410d9c commit 953b921

4 files changed

Lines changed: 91 additions & 143 deletions

File tree

JSqlServerBulkInsert/src/main/java/de/bytefish/jsqlserverbulkinsert/SqlServerBulkInsert.java

Lines changed: 70 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
// Copyright (c) Philipp Wagner and Victor Lee. All rights reserved.
21
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
32

43
package de.bytefish.jsqlserverbulkinsert;
54

6-
import com.microsoft.sqlserver.jdbc.ISQLServerBulkRecord;
7-
import com.microsoft.sqlserver.jdbc.SQLServerBulkCopy;
8-
import com.microsoft.sqlserver.jdbc.SQLServerBulkCopyOptions;
9-
import com.microsoft.sqlserver.jdbc.SQLServerConnection;
10-
import com.microsoft.sqlserver.jdbc.SQLServerException;
5+
import com.microsoft.sqlserver.jdbc.*;
116

127
import java.sql.Connection;
8+
import java.sql.Timestamp;
139
import java.sql.Types;
1410
import java.time.*;
15-
import java.time.format.DateTimeFormatter;
1611
import java.util.*;
1712
import java.util.function.*;
1813

@@ -99,15 +94,15 @@ public <TEntity> ColumnBinding<TEntity> from(Function<TEntity, TValue> extractor
9994
}
10095

10196
public static class SqlServerShortType extends SqlServerType<Short> {
102-
public SqlServerShortType(int jdbcType) { super(jdbcType); }
97+
public SqlServerShortType(int jdbcType, int p) { super(jdbcType, p, 0); }
10398
public <TEntity> ColumnBinding<TEntity> primitive(ToShortFunction<TEntity> extractor) {
10499
return new ColumnBinding<>(jdbcType, precision, scale, entity -> extractor.applyAsShort(entity));
105100
}
106101
}
107102

108103
public static class SqlServerIntType extends SqlServerType<Integer> {
109104
public SqlServerIntType(int jdbcType) {
110-
super(jdbcType);
105+
super(jdbcType, 10, 0);
111106
}
112107

113108
public <TEntity> ColumnBinding<TEntity> primitive(ToIntFunction<TEntity> extractor) {
@@ -117,15 +112,14 @@ public <TEntity> ColumnBinding<TEntity> primitive(ToIntFunction<TEntity> extract
117112

118113
public static class SqlServerLongType extends SqlServerType<Long> {
119114
public SqlServerLongType(int jdbcType) {
120-
super(jdbcType);
115+
super(jdbcType, 19, 0);
121116
}
122117

123118
public <TEntity> ColumnBinding<TEntity> primitive(ToLongFunction<TEntity> extractor) {
124119
return new ColumnBinding<>(jdbcType, precision, scale, entity -> extractor.applyAsLong(entity));
125120
}
126121
}
127122

128-
129123
public static class SqlServerFloatType extends SqlServerType<Float> {
130124
public SqlServerFloatType(int jdbcType) { super(jdbcType); }
131125
public <TEntity> ColumnBinding<TEntity> primitive(ToFloatFunction<TEntity> extractor) {
@@ -134,7 +128,7 @@ public <TEntity> ColumnBinding<TEntity> primitive(ToFloatFunction<TEntity> extra
134128
}
135129

136130
public static class SqlServerDoubleType extends SqlServerType<Double> {
137-
public SqlServerDoubleType(int jdbcType) { super(jdbcType); }
131+
public SqlServerDoubleType(int jdbcType) { super(jdbcType, 53, 0); }
138132
public <TEntity> ColumnBinding<TEntity> primitive(ToDoubleFunction<TEntity> extractor) {
139133
return new ColumnBinding<>(jdbcType, precision, scale, entity -> extractor.applyAsDouble(entity));
140134
}
@@ -153,10 +147,9 @@ public SqlServerBigDecimalType decimal(int precision, int scale) {
153147
}
154148
}
155149

156-
157150
public static class SqlServerStringType extends SqlServerType<String> {
158-
public SqlServerStringType(int jdbcType) {
159-
super(jdbcType);
151+
public SqlServerStringType(int jdbcType, int p) {
152+
super(jdbcType, p, 0);
160153
}
161154

162155
private SqlServerStringType(int jdbcType, int precision, int scale) {
@@ -169,8 +162,8 @@ public SqlServerStringType max() {
169162
}
170163

171164
public static class SqlServerBinaryType extends SqlServerType<byte[]> {
172-
public SqlServerBinaryType(int jdbcType) {
173-
super(jdbcType);
165+
public SqlServerBinaryType(int jdbcType, int p) {
166+
super(jdbcType, p, 0);
174167
}
175168

176169
private SqlServerBinaryType(int jdbcType, int precision, int scale) {
@@ -183,24 +176,24 @@ public SqlServerBinaryType max() {
183176
}
184177

185178
public static class SqlServerDateType extends SqlServerType<LocalDate> {
186-
public SqlServerDateType(int jdbcType) { super(jdbcType); }
179+
public SqlServerDateType(int jdbcType) { super(jdbcType, 10, 0); }
187180

188181
public <TEntity> ColumnBinding<TEntity> localDate(Function<TEntity, LocalDate> extractor) {
189182
return from(extractor);
190183
}
191184
}
192185

193186
public static class SqlServerTimeType extends SqlServerType<LocalTime> {
194-
public SqlServerTimeType(int jdbcType) { super(jdbcType); }
187+
public SqlServerTimeType(int jdbcType) { super(jdbcType, 16, 7); }
195188

196189
public <TEntity> ColumnBinding<TEntity> localTime(Function<TEntity, LocalTime> extractor) {
197190
return from(extractor);
198191
}
199192
}
200193

201194
public static class SqlServerDateTimeType extends SqlServerType<LocalDateTime> {
202-
public SqlServerDateTimeType(int jdbcType) {
203-
super(jdbcType);
195+
public SqlServerDateTimeType(int jdbcType, int p, int s) {
196+
super(jdbcType, p, s);
204197
}
205198

206199
public <TEntity> ColumnBinding<TEntity> localDateTime(Function<TEntity, LocalDateTime> extractor) {
@@ -210,24 +203,29 @@ public <TEntity> ColumnBinding<TEntity> localDateTime(Function<TEntity, LocalDat
210203
public <TEntity> ColumnBinding<TEntity> instant(Function<TEntity, Instant> extractor) {
211204
return new ColumnBinding<>(jdbcType, precision, scale, e -> {
212205
Instant val = extractor.apply(e);
206+
213207
return val == null ? null : LocalDateTime.ofInstant(val, ZoneOffset.UTC);
214208
});
215209
}
216210
}
217211

218212
public static class SqlServerDateTimeOffsetType extends SqlServerType<OffsetDateTime> {
219-
public SqlServerDateTimeOffsetType(int jdbcType) {
220-
super(jdbcType);
221-
}
222-
213+
public SqlServerDateTimeOffsetType(int jdbcType, int precision, int scale) { super(jdbcType, precision, scale); }
223214
public <TEntity> ColumnBinding<TEntity> offsetDateTime(Function<TEntity, OffsetDateTime> extractor) {
224-
return from(extractor);
215+
return new ColumnBinding<>(jdbcType, precision, scale, e -> {
216+
OffsetDateTime odt = extractor.apply(e);
217+
if (odt == null) {
218+
return null;
219+
}
220+
221+
return microsoft.sql.DateTimeOffset.valueOf(Timestamp.from(odt.toInstant()), odt.getOffset().getTotalSeconds() / 60);
222+
});
225223
}
226224

227225
public <TEntity> ColumnBinding<TEntity> instant(Function<TEntity, Instant> extractor) {
228-
return new ColumnBinding<>(jdbcType, precision, scale, e -> {
229-
Instant val = extractor.apply(e);
230-
return val == null ? null : val.atOffset(ZoneOffset.UTC);
226+
return offsetDateTime(e -> {
227+
Instant i = extractor.apply(e);
228+
return i == null ? null : i.atOffset(ZoneOffset.UTC);
231229
});
232230
}
233231
}
@@ -240,52 +238,49 @@ public <TEntity> ColumnBinding<TEntity> primitive(Predicate<TEntity> extractor)
240238
}
241239

242240
public static class SqlServerTypes {
241+
243242
private SqlServerTypes() {
244243
}
245244

246245
public static final int MAX = -1;
247246

248247
// Primitives & Numbers
249248
public static final SqlServerBooleanType BIT = new SqlServerBooleanType(Types.BIT);
250-
public static final SqlServerShortType TINYINT = new SqlServerShortType(Types.TINYINT);
251-
public static final SqlServerShortType SMALLINT = new SqlServerShortType(Types.SMALLINT);
249+
public static final SqlServerShortType TINYINT = new SqlServerShortType(Types.TINYINT, 3);
250+
public static final SqlServerShortType SMALLINT = new SqlServerShortType(Types.SMALLINT, 5);
252251
public static final SqlServerIntType INT = new SqlServerIntType(Types.INTEGER);
253252
public static final SqlServerLongType BIGINT = new SqlServerLongType(Types.BIGINT);
254253
public static final SqlServerFloatType REAL = new SqlServerFloatType(Types.REAL);
255-
public static final SqlServerDoubleType FLOAT = new SqlServerDoubleType(Types.FLOAT); // SQL Server FLOAT is Double
254+
public static final SqlServerDoubleType FLOAT = new SqlServerDoubleType(Types.DOUBLE);
256255

257256
// Decimals & Money
258-
public static final SqlServerBigDecimalType NUMERIC = new SqlServerBigDecimalType(Types.NUMERIC);
259-
public static final SqlServerBigDecimalType DECIMAL = new SqlServerBigDecimalType(Types.DECIMAL);
260-
public static final SqlServerBigDecimalType MONEY = new SqlServerBigDecimalType(Types.DECIMAL);
261-
public static final SqlServerBigDecimalType SMALLMONEY = new SqlServerBigDecimalType(Types.DECIMAL);
257+
public static final SqlServerBigDecimalType NUMERIC = new SqlServerBigDecimalType(Types.NUMERIC, 38, 10);
258+
public static final SqlServerBigDecimalType DECIMAL = new SqlServerBigDecimalType(Types.DECIMAL, 38, 10);
259+
public static final SqlServerBigDecimalType MONEY = new SqlServerBigDecimalType(Types.DECIMAL, 19, 4);
260+
public static final SqlServerBigDecimalType SMALLMONEY = new SqlServerBigDecimalType(Types.DECIMAL, 10, 4);
262261

263262
// Strings
264-
public static final SqlServerStringType CHAR = new SqlServerStringType(Types.CHAR);
265-
public static final SqlServerStringType VARCHAR = new SqlServerStringType(Types.VARCHAR);
266-
public static final SqlServerStringType NCHAR = new SqlServerStringType(Types.NCHAR);
267-
public static final SqlServerStringType NVARCHAR = new SqlServerStringType(Types.NVARCHAR);
263+
public static final SqlServerStringType CHAR = new SqlServerStringType(Types.CHAR, 8000);
264+
public static final SqlServerStringType VARCHAR = new SqlServerStringType(Types.VARCHAR, 8000);
265+
public static final SqlServerStringType NCHAR = new SqlServerStringType(Types.NCHAR, 4000);
266+
public static final SqlServerStringType NVARCHAR = new SqlServerStringType(Types.NVARCHAR, 4000);
268267

269268
// Binaries
270-
public static final SqlServerBinaryType VARBINARY = new SqlServerBinaryType(Types.VARBINARY);
269+
public static final SqlServerBinaryType VARBINARY = new SqlServerBinaryType(Types.VARBINARY, 8000);
271270

272271
// Unique Identifier
273-
public static final SqlServerType<UUID> UNIQUEIDENTIFIER = new SqlServerType<>(microsoft.sql.Types.GUID);
272+
public static final SqlServerType<UUID> UNIQUEIDENTIFIER = new SqlServerType<>(microsoft.sql.Types.GUID, 36, 0);
274273

275274
// Dates & Times
276275
public static final SqlServerDateType DATE = new SqlServerDateType(Types.DATE);
277276
public static final SqlServerTimeType TIME = new SqlServerTimeType(Types.TIME);
278-
public static final SqlServerDateTimeType DATETIME = new SqlServerDateTimeType(Types.TIMESTAMP);
279-
public static final SqlServerDateTimeType DATETIME2 = new SqlServerDateTimeType(Types.TIMESTAMP);
280-
public static final SqlServerDateTimeType SMALLDATETIME = new SqlServerDateTimeType(Types.TIMESTAMP);
281-
public static final SqlServerDateTimeOffsetType DATETIMEOFFSET = new SqlServerDateTimeOffsetType(microsoft.sql.Types.DATETIMEOFFSET);
277+
public static final SqlServerDateTimeType DATETIME = new SqlServerDateTimeType(Types.TIMESTAMP, 23, 3);
278+
public static final SqlServerDateTimeType DATETIME2 = new SqlServerDateTimeType(Types.TIMESTAMP, 27, 7);
279+
public static final SqlServerDateTimeType SMALLDATETIME = new SqlServerDateTimeType(Types.TIMESTAMP, 16, 0);
280+
public static final SqlServerDateTimeOffsetType DATETIMEOFFSET = new SqlServerDateTimeOffsetType(microsoft.sql.Types.DATETIMEOFFSET, 34, 7);
282281
}
283282

284-
// =========================================================================
285-
// 5. THE MAPPER
286-
// =========================================================================
287-
288-
public static class SqlServerMapper<TEntity> {
283+
public static class SqlServerMapper<TEntity> {
289284

290285
static class MappedColumn<T> {
291286
final String columnName;
@@ -316,7 +311,7 @@ List<MappedColumn<TEntity>> getColumns() {
316311
}
317312
}
318313

319-
public static class SqlServerBulkRecordAdapter<TEntity> implements ISQLServerBulkRecord {
314+
static class SqlServerBulkDataAdapter<TEntity> implements ISQLServerBulkData {
320315
private final List<SqlServerMapper.MappedColumn<TEntity>> columns;
321316
private final Iterator<TEntity> iterator;
322317
private final Set<Integer> ordinals;
@@ -326,44 +321,27 @@ public static class SqlServerBulkRecordAdapter<TEntity> implements ISQLServerBul
326321
private TEntity current;
327322
private long counter = 0;
328323

329-
SqlServerBulkRecordAdapter(SqlServerMapper<TEntity> mapper, Iterator<TEntity> iterator, int notifyAfter, BulkProgressListener listener) {
324+
SqlServerBulkDataAdapter(SqlServerMapper<TEntity> mapper, Iterator<TEntity> iterator, int notifyAfter, BulkProgressListener listener) {
330325
this.columns = mapper.getColumns();
331326
this.iterator = iterator;
332327
this.notifyAfter = notifyAfter;
333328
this.progressListener = listener;
334-
this.ordinals = new HashSet<>();
335-
for (int i = 1; i <= columns.size(); i++) ordinals.add(i);
336-
}
337-
338-
@Override
339-
public Set<Integer> getColumnOrdinals() {
340-
return ordinals;
341-
}
329+
this.ordinals = new LinkedHashSet<>();
342330

343-
@Override
344-
public String getColumnName(int c) {
345-
return columns.get(c - 1).columnName;
346-
}
331+
for (int i = 1; i <= columns.size(); i++) ordinals.add(i);
347332

348-
@Override
349-
public int getColumnType(int c) {
350-
return columns.get(c - 1).binding.getJdbcType();
351333
}
352334

353-
@Override
354-
public int getPrecision(int c) {
355-
return columns.get(c - 1).binding.getPrecision();
356-
}
335+
@Override public Set<Integer> getColumnOrdinals() { return ordinals; }
336+
@Override public String getColumnName(int c) {
337+
String name = columns.get(c - 1).columnName;
357338

358-
@Override
359-
public int getScale(int c) {
360-
return columns.get(c - 1).binding.getScale();
339+
return name.startsWith("[") && name.endsWith("]") ? name : "[" + name + "]";
361340
}
362341

363-
@Override
364-
public boolean isAutoIncrement(int c) {
365-
return false;
366-
}
342+
@Override public int getColumnType(int c) { return columns.get(c - 1).binding.getJdbcType(); }
343+
@Override public int getPrecision(int c) { return columns.get(c - 1).binding.getPrecision(); }
344+
@Override public int getScale(int c) { return columns.get(c - 1).binding.getScale(); }
367345

368346
@Override
369347
public Object[] getRowData() throws SQLServerException {
@@ -374,41 +352,6 @@ public Object[] getRowData() throws SQLServerException {
374352
return data;
375353
}
376354

377-
@Override
378-
public void addColumnMetadata(int positionInSource, String name, int jdbcType, int precision, int scale, DateTimeFormatter dateTimeFormatter) {
379-
// Doesn't need to be implemented
380-
}
381-
382-
@Override
383-
public void addColumnMetadata(int positionInSource, String name, int jdbcType, int precision, int scale) {
384-
// Doesn't need to be implemented
385-
}
386-
387-
@Override
388-
public void setTimestampWithTimezoneFormat(String s) {
389-
390-
}
391-
392-
@Override
393-
public void setTimestampWithTimezoneFormat(DateTimeFormatter dateTimeFormatter) {
394-
395-
}
396-
397-
@Override
398-
public void setTimeWithTimezoneFormat(String s) {
399-
400-
}
401-
402-
@Override
403-
public void setTimeWithTimezoneFormat(DateTimeFormatter dateTimeFormatter) {
404-
405-
}
406-
407-
@Override
408-
public DateTimeFormatter getColumnDateTimeFormatter(int i) {
409-
return null;
410-
}
411-
412355
@Override
413356
public boolean next() throws SQLServerException {
414357
if (iterator.hasNext()) {
@@ -422,9 +365,7 @@ public boolean next() throws SQLServerException {
422365
return false;
423366
}
424367

425-
public long getRowsProcessed() {
426-
return counter;
427-
}
368+
public long getRowsProcessed() { return counter; }
428369
}
429370

430371
public static class SqlServerBulkWriter<TEntity> {
@@ -435,8 +376,7 @@ public static class SqlServerBulkWriter<TEntity> {
435376
private boolean checkConstraints = true;
436377
private int notifyAfter = 0;
437378
private BulkProgressListener progressListener = null;
438-
private BulkErrorHandler errorHandler = e -> {
439-
};
379+
private BulkErrorHandler errorHandler = e -> {};
440380

441381
public SqlServerBulkWriter(SqlServerMapper<TEntity> mapper) {
442382
this.mapper = mapper;
@@ -478,16 +418,24 @@ public BulkInsertResult saveAll(Connection connection, String schemaName, String
478418
options.setCheckConstraints(checkConstraints);
479419

480420
bulkCopy.setBulkCopyOptions(options);
481-
bulkCopy.setDestinationTableName("[" + schemaName + "].[" + tableName + "]");
421+
422+
String safeSchema = schemaName.startsWith("[") && schemaName.endsWith("]") ? schemaName : "[" + schemaName + "]";
423+
String safeTable = tableName.startsWith("[") && tableName.endsWith("]") ? tableName : "[" + tableName + "]";
424+
bulkCopy.setDestinationTableName(safeSchema + "." + safeTable);
482425

483426
for (var column : mapper.getColumns()) {
484-
bulkCopy.addColumnMapping(column.columnName, column.columnName);
427+
String safeName = column.columnName.startsWith("[") && column.columnName.endsWith("]")
428+
? column.columnName
429+
: "[" + column.columnName + "]";
430+
431+
bulkCopy.addColumnMapping(safeName, column.columnName);
485432
}
486433

487-
SqlServerBulkRecordAdapter<TEntity> adapter = new SqlServerBulkRecordAdapter<>(
434+
SqlServerBulkDataAdapter<TEntity> adapter = new SqlServerBulkDataAdapter<>(
488435
mapper, entities.iterator(), notifyAfter, progressListener);
489436

490437
bulkCopy.writeToServer(adapter);
438+
491439
return BulkInsertResult.ok(adapter.getRowsProcessed());
492440
}
493441
} catch (Exception e) {

0 commit comments

Comments
 (0)