Skip to content

Commit 8f1573a

Browse files
aasthabharillpratickchokhani
authored andcommitted
Live and Bulk Retry: Data conversion errors when NULL is passed in non-textual datatype
Alternative implementation: instead of manually checking in each toDatatype function, we could add this check to containsValue function that is used by all datatypes Reason for rejection: a user can pass "NULL" string purposely to a string column - this should not be converted to null by my logic. containsValue function is called in method toString and thus this conversion cannot be added to the common function and needs to be added for each datatype seperately wherever relevant. This would not change the existing behaviour for string data type. It would only change NULL handling for other datatypes
1 parent c0090de commit 8f1573a

File tree

2 files changed

+50
-0
lines changed

2 files changed

+50
-0
lines changed

v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/convertors/ChangeEventTypeConvertor.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ public static Boolean toBoolean(JsonNode changeEvent, String key, boolean requir
6262
*/
6363
JsonNode node = changeEvent.get(key);
6464
if (node.isTextual()) {
65+
if (node.asText().equalsIgnoreCase("NULL")) {
66+
return null;
67+
}
6568
return BooleanUtils.toBoolean(node.asText());
6669
}
6770
return Boolean.valueOf(node.asBoolean());
@@ -80,6 +83,9 @@ public static Long toLong(JsonNode changeEvent, String key, boolean requiredFiel
8083
try {
8184
JsonNode node = changeEvent.get(key);
8285
if (node.isTextual()) {
86+
if (node.asText().equalsIgnoreCase("NULL")) {
87+
return null;
88+
}
8389
return Long.valueOf(node.asText());
8490
}
8591
return node.asLong();
@@ -98,6 +104,9 @@ public static Double toDouble(JsonNode changeEvent, String key, boolean required
98104
try {
99105
JsonNode node = changeEvent.get(key);
100106
if (node.isTextual()) {
107+
if (node.asText().equalsIgnoreCase("NULL")) {
108+
return null;
109+
}
101110
return Double.valueOf(node.asText());
102111
}
103112
return Double.valueOf(node.asDouble());
@@ -115,6 +124,9 @@ public static Float toFloat(JsonNode changeEvent, String key, boolean requiredFi
115124
try {
116125
JsonNode node = changeEvent.get(key);
117126
if (node.isTextual()) {
127+
if (node.asText().equalsIgnoreCase("NULL")) {
128+
return null;
129+
}
118130
return Float.valueOf(node.asText());
119131
}
120132
return new Float(node.asDouble());
@@ -155,6 +167,9 @@ public static ByteArray toByteArray(JsonNode changeEvent, String key, boolean re
155167
// For data with Spanner type as BYTES, Datastream returns a hex encoded string. We need to
156168
// decode it before returning to ensure data correctness.
157169
String s = node.asText();
170+
if (s.equalsIgnoreCase("NULL")) {
171+
return null;
172+
}
158173
// Make an odd length hex string even by appending a 0 in the beginning.
159174
if (s.length() % 2 == 1) {
160175
s = "0" + s;
@@ -179,6 +194,9 @@ public static Timestamp toTimestamp(JsonNode changeEvent, String key, boolean re
179194
}
180195
try {
181196
String timeString = changeEvent.get(key).asText();
197+
if (timeString.equalsIgnoreCase("NULL")) {
198+
return null;
199+
}
182200
Instant instant = parseTimestamp(timeString);
183201
return Timestamp.ofTimeSecondsAndNanos(instant.getEpochSecond(), instant.getNano());
184202
} catch (Exception e) {
@@ -195,6 +213,9 @@ public static Date toDate(JsonNode changeEvent, String key, boolean requiredFiel
195213
}
196214
try {
197215
String dateString = changeEvent.get(key).asText();
216+
if (dateString.equalsIgnoreCase("NULL")) {
217+
return null;
218+
}
198219
return Date.fromJavaUtilDate(parseLenientDate(dateString));
199220
} catch (Exception e) {
200221
throw new ChangeEventConvertorException("Unable to convert field " + key + " to Date", e);
@@ -227,6 +248,9 @@ public static BigDecimal toNumericBigDecimal(
227248
if (value == null) {
228249
return null;
229250
}
251+
if (value.equalsIgnoreCase("NULL")) {
252+
return null;
253+
}
230254
if (NumberUtils.isCreatable(value) || NumberUtils.isParsable(value) || isNumeric(value)) {
231255
return new BigDecimal(value).setScale(9, RoundingMode.HALF_UP);
232256
}

v2/spanner-common/src/test/java/com/google/cloud/teleport/v2/spanner/migrations/convertors/ChangeEventTypeConvertorTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,4 +746,30 @@ public void cannotConvertNonExistentRequiredFieldToDate() throws Exception {
746746
ChangeEventTypeConvertor.toDate(ce, "field1", /* requiredField= */ true),
747747
Date.parseDate("2020-12-30"));
748748
}
749+
750+
@Test
751+
public void canConvertNullStrings() throws Exception {
752+
JSONObject changeEvent = new JSONObject();
753+
changeEvent.put("long_field", "NULL");
754+
changeEvent.put("double_field", "null");
755+
changeEvent.put("float_field", "Null");
756+
changeEvent.put("numeric_field", "NULL");
757+
changeEvent.put("boolean_field", "NULL");
758+
changeEvent.put("byte_field", "NULL");
759+
changeEvent.put("timestamp_field", "NULL");
760+
changeEvent.put("date_field", "NULL");
761+
changeEvent.put("string_field", "NULL"); // this should not be converted to null
762+
763+
JsonNode ce = getJsonNode(changeEvent.toString());
764+
765+
assertNull(ChangeEventTypeConvertor.toLong(ce, "long_field", true));
766+
assertNull(ChangeEventTypeConvertor.toDouble(ce, "double_field", true));
767+
assertNull(ChangeEventTypeConvertor.toFloat(ce, "float_field", true));
768+
assertNull(ChangeEventTypeConvertor.toNumericBigDecimal(ce, "numeric_field", true));
769+
assertNull(ChangeEventTypeConvertor.toBoolean(ce, "boolean_field", true));
770+
assertNull(ChangeEventTypeConvertor.toByteArray(ce, "byte_field", true));
771+
assertNull(ChangeEventTypeConvertor.toTimestamp(ce, "timestamp_field", true));
772+
assertNull(ChangeEventTypeConvertor.toDate(ce, "date_field", true));
773+
assertEquals("NULL", ChangeEventTypeConvertor.toString(ce, "string_field", true));
774+
}
749775
}

0 commit comments

Comments
 (0)