26
26
import org .apache .seatunnel .api .table .type .SeaTunnelRow ;
27
27
import org .apache .seatunnel .api .table .type .SeaTunnelRowType ;
28
28
import org .apache .seatunnel .api .table .type .SqlType ;
29
+ import org .apache .seatunnel .common .exception .CommonErrorCodeDeprecated ;
30
+ import org .apache .seatunnel .common .utils .DateUtils ;
31
+ import org .apache .seatunnel .format .json .exception .SeaTunnelJsonFormatException ;
29
32
30
33
import java .io .IOException ;
31
34
import java .io .Serializable ;
37
40
import java .time .LocalTime ;
38
41
import java .time .ZoneOffset ;
39
42
import java .time .format .DateTimeFormatter ;
43
+ import java .time .temporal .TemporalAccessor ;
44
+ import java .time .temporal .TemporalQueries ;
40
45
import java .util .ArrayList ;
46
+ import java .util .HashMap ;
41
47
import java .util .Iterator ;
42
48
import java .util .LinkedHashMap ;
43
49
import java .util .List ;
44
50
import java .util .Map ;
51
+ import java .util .concurrent .TimeUnit ;
45
52
46
53
public class DebeziumRowConverter implements Serializable {
54
+ private static final String DECIMAL_SCALE_KEY = "scale" ;
55
+ private static final String DECIMAL_VALUE_KEY = "value" ;
47
56
57
+ private final Map <String , DateTimeFormatter > fieldFormatterMap = new HashMap <>();
48
58
private final SeaTunnelRowType rowType ;
49
59
50
60
public DebeziumRowConverter (SeaTunnelRowType rowType ) {
51
61
this .rowType = rowType ;
52
62
}
53
63
54
- public SeaTunnelRow serializeValue (JsonNode node ) {
55
- return (SeaTunnelRow ) getValue (rowType , node );
64
+ public SeaTunnelRow parse (JsonNode node ) throws IOException {
65
+ return (SeaTunnelRow ) getValue (null , rowType , node );
56
66
}
57
67
58
- private Object getValue (SeaTunnelDataType <?> dataType , JsonNode value ) {
68
+ private Object getValue (String fieldName , SeaTunnelDataType <?> dataType , JsonNode value )
69
+ throws IOException {
59
70
SqlType sqlType = dataType .getSqlType ();
60
71
if (value == null ) {
61
72
return null ;
62
73
}
63
74
switch (sqlType ) {
64
75
case BOOLEAN :
65
- return value .booleanValue ();
76
+ return value .asBoolean ();
66
77
case TINYINT :
67
- return (byte ) value .intValue ();
78
+ return (byte ) value .asInt ();
68
79
case SMALLINT :
69
- return (short ) value .intValue ();
80
+ return (short ) value .asInt ();
70
81
case INT :
71
- return value .intValue ();
82
+ return value .asInt ();
72
83
case BIGINT :
73
- return value .longValue ();
84
+ return value .asLong ();
74
85
case FLOAT :
75
86
return value .floatValue ();
76
87
case DOUBLE :
@@ -88,42 +99,100 @@ private Object getValue(SeaTunnelDataType<?> dataType, JsonNode value) {
88
99
throw new RuntimeException ("Invalid bytes for Decimal field" , e );
89
100
}
90
101
}
102
+ if (value .has (DECIMAL_SCALE_KEY )) {
103
+ return new BigDecimal (
104
+ new BigInteger (value .get (DECIMAL_VALUE_KEY ).binaryValue ()),
105
+ value .get (DECIMAL_SCALE_KEY ).intValue ());
106
+ }
107
+ return new BigDecimal (value .asText ());
91
108
case STRING :
92
- return value .textValue ();
109
+ return value .asText ();
93
110
case BYTES :
94
111
try {
95
112
return value .binaryValue ();
96
113
} catch (IOException e ) {
97
114
throw new RuntimeException ("Invalid bytes field" , e );
98
115
}
99
116
case DATE :
100
- try {
101
- int d = Integer .parseInt (value .toString ());
102
- return LocalDate .ofEpochDay (d );
103
- } catch (NumberFormatException e ) {
104
- return LocalDate .parse (
105
- value .textValue (), DateTimeFormatter .ofPattern ("yyyy-MM-dd" ));
117
+ String dateStr = value .asText ();
118
+ if (value .canConvertToLong ()) {
119
+ return LocalDate .ofEpochDay (Long .parseLong (dateStr ));
120
+ }
121
+ DateTimeFormatter dateFormatter = fieldFormatterMap .get (fieldName );
122
+ if (dateFormatter == null ) {
123
+ dateFormatter = DateUtils .matchDateFormatter (dateStr );
124
+ fieldFormatterMap .put (fieldName , dateFormatter );
125
+ }
126
+ if (dateFormatter == null ) {
127
+ throw new SeaTunnelJsonFormatException (
128
+ CommonErrorCodeDeprecated .UNSUPPORTED_DATA_TYPE ,
129
+ String .format (
130
+ "SeaTunnel can not parse this date format [%s] of field [%s]" ,
131
+ dateStr , fieldName ));
106
132
}
133
+ return dateFormatter .parse (dateStr ).query (TemporalQueries .localDate ());
107
134
case TIME :
108
- try {
109
- long t = Long .parseLong (value .toString ());
110
- return LocalTime .ofNanoOfDay (t * 1000L );
111
- } catch (NumberFormatException e ) {
112
- return LocalTime .parse (value .textValue ());
135
+ String timeStr = value .asText ();
136
+ if (value .canConvertToLong ()) {
137
+ long time = Long .parseLong (timeStr );
138
+ if (timeStr .length () == 8 ) {
139
+ time = TimeUnit .SECONDS .toMicros (time );
140
+ } else if (timeStr .length () == 11 ) {
141
+ time = TimeUnit .MILLISECONDS .toMicros (time );
142
+ }
143
+ return LocalTime .ofNanoOfDay (time );
144
+ }
145
+
146
+ DateTimeFormatter timeFormatter = fieldFormatterMap .get (fieldName );
147
+ if (timeFormatter == null ) {
148
+ timeFormatter = DateUtils .matchDateFormatter (timeStr );
149
+ fieldFormatterMap .put (fieldName , timeFormatter );
113
150
}
151
+ if (timeFormatter == null ) {
152
+ throw new SeaTunnelJsonFormatException (
153
+ CommonErrorCodeDeprecated .UNSUPPORTED_DATA_TYPE ,
154
+ String .format (
155
+ "SeaTunnel can not parse this date format [%s] of field [%s]" ,
156
+ timeStr , fieldName ));
157
+ }
158
+
159
+ TemporalAccessor parsedTime = timeFormatter .parse (timeStr );
160
+ return parsedTime .query (TemporalQueries .localTime ());
114
161
case TIMESTAMP :
115
- try {
162
+ String timestampStr = value .asText ();
163
+ if (value .canConvertToLong ()) {
116
164
long timestamp = Long .parseLong (value .toString ());
165
+ if (timestampStr .length () == 10 ) {
166
+ timestamp = TimeUnit .SECONDS .toMillis (timestamp );
167
+ } else if (timestampStr .length () == 19 ) {
168
+ timestamp = TimeUnit .NANOSECONDS .toMillis (timestamp );
169
+ } else if (timestampStr .length () == 16 ) {
170
+ timestamp = TimeUnit .MICROSECONDS .toMillis (timestamp );
171
+ }
117
172
return LocalDateTime .ofInstant (Instant .ofEpochMilli (timestamp ), ZoneOffset .UTC );
118
- } catch (NumberFormatException e ) {
119
- return LocalDateTime .parse (
120
- value .textValue (),
121
- DateTimeFormatter .ofPattern ("yyyy-MM-dd'T'HH:mm:ss'Z'" ));
122
173
}
174
+
175
+ DateTimeFormatter timestampFormatter = fieldFormatterMap .get (fieldName );
176
+ if (timestampFormatter == null ) {
177
+ timestampFormatter = DateUtils .matchDateFormatter (timestampStr );
178
+ fieldFormatterMap .put (fieldName , timestampFormatter );
179
+ }
180
+ if (timestampFormatter == null ) {
181
+ throw new SeaTunnelJsonFormatException (
182
+ CommonErrorCodeDeprecated .UNSUPPORTED_DATA_TYPE ,
183
+ String .format (
184
+ "SeaTunnel can not parse this date format [%s] of field [%s]" ,
185
+ timestampStr , fieldName ));
186
+ }
187
+
188
+ TemporalAccessor parsedTimestamp = timestampFormatter .parse (timestampStr );
189
+ LocalTime localTime = parsedTimestamp .query (TemporalQueries .localTime ());
190
+ LocalDate localDate = parsedTimestamp .query (TemporalQueries .localDate ());
191
+ return LocalDateTime .of (localDate , localTime );
123
192
case ARRAY :
124
193
List <Object > arrayValue = new ArrayList <>();
125
194
for (JsonNode o : value ) {
126
- arrayValue .add (getValue (((ArrayType ) dataType ).getElementType (), o ));
195
+ arrayValue .add (getValue (fieldName , ((ArrayType ) dataType ).getElementType (), o ));
127
196
}
128
197
return arrayValue ;
129
198
case MAP :
@@ -132,7 +201,7 @@ private Object getValue(SeaTunnelDataType<?> dataType, JsonNode value) {
132
201
Map .Entry <String , JsonNode > entry = it .next ();
133
202
mapValue .put (
134
203
entry .getKey (),
135
- getValue (((MapType ) dataType ).getValueType (), entry .getValue ()));
204
+ getValue (null , ((MapType ) dataType ).getValueType (), entry .getValue ()));
136
205
}
137
206
return mapValue ;
138
207
case ROW :
@@ -141,7 +210,10 @@ private Object getValue(SeaTunnelDataType<?> dataType, JsonNode value) {
141
210
for (int i = 0 ; i < rowType .getTotalFields (); i ++) {
142
211
row .setField (
143
212
i ,
144
- getValue (rowType .getFieldType (i ), value .get (rowType .getFieldName (i ))));
213
+ getValue (
214
+ rowType .getFieldName (i ),
215
+ rowType .getFieldType (i ),
216
+ value .get (rowType .getFieldName (i ))));
145
217
}
146
218
return row ;
147
219
default :
0 commit comments