17
17
package io .cdap .delta .plugin .common ;
18
18
19
19
import io .cdap .cdap .api .data .format .StructuredRecord ;
20
+ import io .cdap .cdap .api .data .format .UnexpectedFormatException ;
20
21
import io .cdap .cdap .api .data .schema .Schema ;
21
22
import io .cdap .delta .api .SourceColumn ;
22
23
import io .debezium .jdbc .JdbcValueConverters ;
37
38
import java .math .BigDecimal ;
38
39
import java .time .Instant ;
39
40
import java .time .LocalDate ;
41
+ import java .time .LocalDateTime ;
40
42
import java .time .LocalTime ;
41
43
import java .time .ZoneOffset ;
42
44
import java .time .ZonedDateTime ;
45
+ import java .time .format .DateTimeParseException ;
43
46
import java .util .ArrayList ;
44
47
import java .util .List ;
45
48
import java .util .Map ;
@@ -157,6 +160,7 @@ public static StructuredRecord convert(Struct struct) {
157
160
for (Schema .Field field : schema .getFields ()) {
158
161
String fieldName = field .getName ();
159
162
Field debeziumField = struct .schema ().field (fieldName );
163
+ String debeziumSchemaName = debeziumField .schema ().name ();
160
164
Object val = convert (debeziumField .schema (), struct .get (fieldName ));
161
165
Schema fieldSchema = field .getSchema ();
162
166
fieldSchema = fieldSchema .isNullable () ? fieldSchema .getNonNullable () : fieldSchema ;
@@ -168,6 +172,34 @@ public static StructuredRecord convert(Struct struct) {
168
172
case DATE :
169
173
builder .setDate (fieldName , LocalDate .ofEpochDay ((int ) val ));
170
174
break ;
175
+ case DATETIME :
176
+ long value = (long ) val ;
177
+ try {
178
+ LocalDateTime localDateTime ;
179
+ if (NanoTimestamp .SCHEMA_NAME .equals (debeziumSchemaName )) {
180
+ // DATETIME2(7) from SQL Server is represented as io.debezium.time.NanoTimestamp
181
+ // which is the number of nanoseconds past the epoch, and does not include timezone information.
182
+ localDateTime = LocalDateTime .ofInstant (Instant .ofEpochSecond (0L , value ), ZoneOffset .UTC );
183
+ } else if (MicroTimestamp .SCHEMA_NAME .equals (debeziumSchemaName )) {
184
+ // DATETIME2(4), DATETIME2(5), DATETIME2(6) from SQL Server and mysql are represented as
185
+ // io.debezium.time.MicroTimestamp, which is the number of microseconds past the epoch, and does
186
+ // not include timezone information.
187
+ localDateTime
188
+ = LocalDateTime .ofInstant (Instant .ofEpochSecond (0L , TimeUnit .MICROSECONDS .toNanos (value )),
189
+ ZoneOffset .UTC );
190
+ } else {
191
+ // DATETIME, SMALLDATETIME, DATETIME2(0), DATETIME2(1), DATETIME2(2), DATETIME2(3) from SQL Server
192
+ // and mysql are represented as io.debezium.time.Timestamp, which is the number of milliseconds
193
+ // past the epoch, and does not include timezone information
194
+ localDateTime = LocalDateTime .ofInstant (Instant .ofEpochMilli (value ), ZoneOffset .UTC );
195
+ }
196
+ builder .setDateTime (fieldName , localDateTime );
197
+ } catch (DateTimeParseException exception ) {
198
+ throw new UnexpectedFormatException (
199
+ String .format ("Field '%s' of type '%s' with value '%s' is not in ISO-8601 format." ,
200
+ field .getName (), debeziumSchemaName , val ), exception );
201
+ }
202
+ break ;
171
203
case TIMESTAMP_MILLIS :
172
204
builder .setTimestamp (fieldName , getZonedDateTime ((long ) val , TimeUnit .MILLISECONDS ));
173
205
break ;
@@ -178,7 +210,17 @@ public static StructuredRecord convert(Struct struct) {
178
210
builder .setTime (fieldName , LocalTime .ofNanoOfDay (TimeUnit .MILLISECONDS .toNanos ((int ) val )));
179
211
break ;
180
212
case TIME_MICROS :
181
- builder .setTime (fieldName , LocalTime .ofNanoOfDay (TimeUnit .MICROSECONDS .toNanos ((long ) val )));
213
+ LocalTime localTime ;
214
+ if (NanoTime .SCHEMA_NAME .equals (debeziumSchemaName )) {
215
+ // TIME(7) from SQL server represents the number of nanoseconds past midnight, and does not include
216
+ // timezone information.
217
+ localTime = LocalTime .ofNanoOfDay ((long ) val );
218
+ } else {
219
+ // TIME(4), TIME(5), TIME(6) which represents the number of microseconds past midnight,
220
+ // and does not include timezone information.
221
+ localTime = LocalTime .ofNanoOfDay (TimeUnit .MICROSECONDS .toNanos ((long ) val ));
222
+ }
223
+ builder .setTime (fieldName , localTime );
182
224
break ;
183
225
case DECIMAL :
184
226
builder .setDecimal (fieldName , (BigDecimal ) val );
@@ -278,10 +320,9 @@ public static Schema convert(org.apache.kafka.connect.data.Schema schema) {
278
320
NanoTime .SCHEMA_NAME .equals (schema .name ())) {
279
321
converted = Schema .of (Schema .LogicalType .TIME_MICROS );
280
322
} else if (MicroTimestamp .SCHEMA_NAME .equals (schema .name ()) ||
281
- NanoTimestamp .SCHEMA_NAME .equals (schema .name ())) {
282
- converted = Schema .of (Schema .LogicalType .TIMESTAMP_MICROS );
283
- } else if (Timestamp .SCHEMA_NAME .equals (schema .name ())) {
284
- converted = Schema .of (Schema .LogicalType .TIMESTAMP_MILLIS );
323
+ NanoTimestamp .SCHEMA_NAME .equals (schema .name ()) ||
324
+ Timestamp .SCHEMA_NAME .equals (schema .name ())) {
325
+ converted = Schema .of (Schema .LogicalType .DATETIME );
285
326
} else {
286
327
converted = Schema .of (Schema .Type .LONG );
287
328
}
0 commit comments