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
43package 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
127import java .sql .Connection ;
8+ import java .sql .Timestamp ;
139import java .sql .Types ;
1410import java .time .*;
15- import java .time .format .DateTimeFormatter ;
1611import java .util .*;
1712import 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