This is an automated email from the ASF dual-hosted git repository.
jackie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 0cd233b6db6 Fix complex field ser de (#17685)
0cd233b6db6 is described below
commit 0cd233b6db6d87d2eb3efa6901f3744a89143767
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Wed Feb 25 15:04:27 2026 -0800
Fix complex field ser de (#17685)
---
.../java/org/apache/pinot/spi/data/FieldSpec.java | 53 +++-
.../pinot/spi/data/SchemaSerializationTest.java | 353 +++++++++++++++------
2 files changed, 299 insertions(+), 107 deletions(-)
diff --git a/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java
b/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java
index 5e8ef58666b..a67682aec9c 100644
--- a/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java
+++ b/pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java
@@ -25,10 +25,12 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.OptBoolean;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Timestamp;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -336,13 +338,10 @@ public abstract class FieldSpec implements
Comparable<FieldSpec>, Serializable {
return getStringValue(_defaultNullValue);
}
- /**
- * Helper method to return the String value for the given object.
- * This is required as not all data types have a toString() (e.g. byte[]).
- *
- * @param value Value for which String value needs to be returned
- * @return String value for the object.
- */
+ /// Returns the [String] representation of the given object.
+ /// The input value could be:
+ /// - Default null value stored in [FieldSpec]
+ /// - Value from the records (post transform)
public static String getStringValue(Object value) {
if (value instanceof BigDecimal) {
return ((BigDecimal) value).toPlainString();
@@ -350,10 +349,32 @@ public abstract class FieldSpec implements
Comparable<FieldSpec>, Serializable {
if (value instanceof byte[]) {
return BytesUtils.toHexString((byte[]) value);
}
+ if (value instanceof List || value instanceof Map) {
+ try {
+ return JsonUtils.objectToString(value);
+ } catch (Exception e) {
+ throw new RuntimeException("Caught exception serializing value: " +
value, e);
+ }
+ }
return value.toString();
}
- // Required by JSON de-serializer. DO NOT REMOVE.
+ @JsonProperty
+ private void setDefaultNullValue(@Nullable JsonNode defaultNullValue) {
+ if (defaultNullValue != null && !defaultNullValue.isNull()) {
+ if (defaultNullValue.isValueNode()) {
+ _stringDefaultNullValue = defaultNullValue.asText();
+ } else {
+ // For ARRAY and OBJECT
+ _stringDefaultNullValue = defaultNullValue.toString();
+ }
+ }
+ if (_dataType != null) {
+ _defaultNullValue = getDefaultNullValue(getFieldType(), _dataType,
_stringDefaultNullValue);
+ }
+ }
+
+ @JsonIgnore
public void setDefaultNullValue(@Nullable Object defaultNullValue) {
if (defaultNullValue != null) {
_stringDefaultNullValue = getStringValue(defaultNullValue);
@@ -535,10 +556,8 @@ public abstract class FieldSpec implements
Comparable<FieldSpec>, Serializable {
jsonNode.put(key, BytesUtils.toHexString((byte[])
_defaultNullValue));
break;
case MAP:
- jsonNode.put(key, JsonUtils.objectToJsonNode(_defaultNullValue));
- break;
case LIST:
- jsonNode.put(key, JsonUtils.objectToJsonNode(_defaultNullValue));
+ jsonNode.set(key, JsonUtils.objectToJsonNode(_defaultNullValue));
break;
default:
throw new IllegalStateException("Unsupported data type: " + this);
@@ -568,7 +587,7 @@ public abstract class FieldSpec implements
Comparable<FieldSpec>, Serializable {
&& Objects.equals(_maxLength, that._maxLength)
&& Objects.equals(_maxLengthExceedStrategy,
that._maxLengthExceedStrategy)
&& _allowTrailingZeros == that._allowTrailingZeros
- &&
getStringValue(_defaultNullValue).equals(getStringValue(that._defaultNullValue))
+ && _dataType.equals(_defaultNullValue, that._defaultNullValue)
&& Objects.equals(_transformFunction, that._transformFunction)
&& Objects.equals(_virtualColumnProvider, that._virtualColumnProvider);
}
@@ -576,7 +595,7 @@ public abstract class FieldSpec implements
Comparable<FieldSpec>, Serializable {
@Override
public int hashCode() {
return Objects.hash(_name, _dataType, _singleValueField, _notNull,
_maxLength, _maxLengthExceedStrategy,
- _allowTrailingZeros, getStringValue(_defaultNullValue),
_transformFunction, _virtualColumnProvider);
+ _allowTrailingZeros, _dataType.hashCode(_defaultNullValue),
_transformFunction, _virtualColumnProvider);
}
/**
@@ -720,6 +739,14 @@ public abstract class FieldSpec implements
Comparable<FieldSpec>, Serializable {
}
}
+ public boolean equals(Object value1, Object value2) {
+ return this == BYTES ? Arrays.equals((byte[]) value1, (byte[]) value2) :
value1.equals(value2);
+ }
+
+ public int hashCode(Object value) {
+ return this == BYTES ? Arrays.hashCode((byte[]) value) :
value.hashCode();
+ }
+
/**
* Compares the given values of the data type.
*
diff --git
a/pinot-spi/src/test/java/org/apache/pinot/spi/data/SchemaSerializationTest.java
b/pinot-spi/src/test/java/org/apache/pinot/spi/data/SchemaSerializationTest.java
index f6a8b7ce535..3acb8cdc551 100644
---
a/pinot-spi/src/test/java/org/apache/pinot/spi/data/SchemaSerializationTest.java
+++
b/pinot-spi/src/test/java/org/apache/pinot/spi/data/SchemaSerializationTest.java
@@ -20,12 +20,19 @@ package org.apache.pinot.spi.data;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.util.List;
import java.util.Map;
+import org.apache.pinot.spi.data.FieldSpec.DataType;
import org.apache.pinot.spi.utils.JsonUtils;
-import org.testng.Assert;
import org.testng.annotations.Test;
import org.testng.collections.Lists;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
/**
* Unit tests for Schema serialization with @JsonValue annotation.
@@ -43,22 +50,22 @@ public class SchemaSerializationTest {
throws Exception {
final Schema schema = new Schema.SchemaBuilder()
.setSchemaName("testSchema")
- .addSingleValueDimension("dim1", FieldSpec.DataType.STRING)
- .addMetric("metric1", FieldSpec.DataType.LONG)
+ .addSingleValueDimension("dim1", DataType.STRING)
+ .addMetric("metric1", DataType.LONG)
.build();
// Serialize using Jackson (which should use @JsonValue -> toJsonObject())
final String jsonString = JsonUtils.objectToString(schema);
// Verify that defaultNullValueString is NOT present in the output
- Assert.assertFalse(jsonString.contains("defaultNullValueString"),
+ assertFalse(jsonString.contains("defaultNullValueString"),
"defaultNullValueString should not be present in serialized output");
// Verify it can be deserialized back
final Schema deserializedSchema = Schema.fromString(jsonString);
- Assert.assertEquals(deserializedSchema.getSchemaName(), "testSchema");
- Assert.assertNotNull(deserializedSchema.getDimensionSpec("dim1"));
- Assert.assertNotNull(deserializedSchema.getMetricSpec("metric1"));
+ assertEquals(deserializedSchema.getSchemaName(), "testSchema");
+ assertNotNull(deserializedSchema.getDimensionSpec("dim1"));
+ assertNotNull(deserializedSchema.getMetricSpec("metric1"));
}
/**
@@ -69,8 +76,8 @@ public class SchemaSerializationTest {
throws Exception {
final Schema schema = new Schema.SchemaBuilder()
.setSchemaName("testSchema")
- .addSingleValueDimension("dim1", FieldSpec.DataType.STRING) // default
null value = "null"
- .addMetric("metric1", FieldSpec.DataType.DOUBLE) // default null value
= 0.0
+ .addSingleValueDimension("dim1", DataType.STRING) // default null
value = "null"
+ .addMetric("metric1", DataType.DOUBLE) // default null value = 0.0
.build();
final String jsonString = JsonUtils.objectToString(schema);
@@ -78,24 +85,24 @@ public class SchemaSerializationTest {
// Check dimension field spec - should not have defaultNullValue since
"null" is the default
final JsonNode dimSpecs = jsonNode.get("dimensionFieldSpecs");
- Assert.assertNotNull(dimSpecs);
- Assert.assertEquals(dimSpecs.size(), 1);
+ assertNotNull(dimSpecs);
+ assertEquals(dimSpecs.size(), 1);
final JsonNode dimSpec = dimSpecs.get(0);
- Assert.assertFalse(dimSpec.has("defaultNullValue"),
+ assertFalse(dimSpec.has("defaultNullValue"),
"defaultNullValue should not be present for STRING dimension with
default value");
- Assert.assertFalse(dimSpec.has("notNull"),
+ assertFalse(dimSpec.has("notNull"),
"notNull should not be present when false (default)");
- Assert.assertFalse(dimSpec.has("singleValueField"),
+ assertFalse(dimSpec.has("singleValueField"),
"singleValueField should not be present when true (default)");
- Assert.assertFalse(dimSpec.has("allowTrailingZeros"),
+ assertFalse(dimSpec.has("allowTrailingZeros"),
"allowTrailingZeros should not be present when false (default)");
// Check metric field spec - should not have defaultNullValue since 0.0 is
the default
final JsonNode metricSpecs = jsonNode.get("metricFieldSpecs");
- Assert.assertNotNull(metricSpecs);
- Assert.assertEquals(metricSpecs.size(), 1);
+ assertNotNull(metricSpecs);
+ assertEquals(metricSpecs.size(), 1);
final JsonNode metricSpec = metricSpecs.get(0);
- Assert.assertFalse(metricSpec.has("defaultNullValue"),
+ assertFalse(metricSpec.has("defaultNullValue"),
"defaultNullValue should not be present for DOUBLE metric with default
value");
}
@@ -107,9 +114,9 @@ public class SchemaSerializationTest {
throws Exception {
final Schema schema = new Schema.SchemaBuilder()
.setSchemaName("testSchema")
- .addSingleValueDimension("dim1", FieldSpec.DataType.STRING,
"custom_default")
- .addMetric("metric1", FieldSpec.DataType.DOUBLE, 99.9)
- .addMultiValueDimension("mvDim", FieldSpec.DataType.INT)
+ .addSingleValueDimension("dim1", DataType.STRING, "custom_default")
+ .addMetric("metric1", DataType.DOUBLE, 99.9)
+ .addMultiValueDimension("mvDim", DataType.INT)
.build();
final String jsonString = JsonUtils.objectToString(schema);
@@ -128,24 +135,24 @@ public class SchemaSerializationTest {
}
}
- Assert.assertNotNull(dim1);
- Assert.assertTrue(dim1.has("defaultNullValue"),
+ assertNotNull(dim1);
+ assertTrue(dim1.has("defaultNullValue"),
"defaultNullValue should be present for non-default value");
- Assert.assertEquals(dim1.get("defaultNullValue").asText(),
"custom_default");
+ assertEquals(dim1.get("defaultNullValue").asText(), "custom_default");
// Check multi-value dimension has singleValueField: false
- Assert.assertNotNull(mvDim);
- Assert.assertTrue(mvDim.has("singleValueField"),
+ assertNotNull(mvDim);
+ assertTrue(mvDim.has("singleValueField"),
"singleValueField should be present when false (non-default)");
- Assert.assertFalse(mvDim.get("singleValueField").asBoolean());
+ assertFalse(mvDim.get("singleValueField").asBoolean());
// Check metric with custom default
final JsonNode metricSpecs = jsonNode.get("metricFieldSpecs");
- Assert.assertNotNull(metricSpecs);
+ assertNotNull(metricSpecs);
final JsonNode metric1 = metricSpecs.get(0);
- Assert.assertTrue(metric1.has("defaultNullValue"),
+ assertTrue(metric1.has("defaultNullValue"),
"defaultNullValue should be present for non-default value");
- Assert.assertEquals(metric1.get("defaultNullValue").asDouble(), 99.9);
+ assertEquals(metric1.get("defaultNullValue").asDouble(), 99.9);
}
/**
@@ -156,25 +163,25 @@ public class SchemaSerializationTest {
throws Exception {
final Schema schema = new Schema.SchemaBuilder()
.setSchemaName("testSchema")
- .addSingleValueDimension("dim1", FieldSpec.DataType.STRING)
+ .addSingleValueDimension("dim1", DataType.STRING)
.build();
final String jsonString = JsonUtils.objectToString(schema);
final JsonNode jsonNode = JsonUtils.stringToJsonNode(jsonString);
// Should have dimensionFieldSpecs
- Assert.assertTrue(jsonNode.has("dimensionFieldSpecs"));
+ assertTrue(jsonNode.has("dimensionFieldSpecs"));
// Should NOT have empty metricFieldSpecs, dateTimeFieldSpecs,
complexFieldSpecs
- Assert.assertFalse(jsonNode.has("metricFieldSpecs"),
+ assertFalse(jsonNode.has("metricFieldSpecs"),
"Empty metricFieldSpecs should be omitted");
- Assert.assertFalse(jsonNode.has("dateTimeFieldSpecs"),
+ assertFalse(jsonNode.has("dateTimeFieldSpecs"),
"Empty dateTimeFieldSpecs should be omitted");
- Assert.assertFalse(jsonNode.has("complexFieldSpecs"),
+ assertFalse(jsonNode.has("complexFieldSpecs"),
"Empty complexFieldSpecs should be omitted");
- Assert.assertFalse(jsonNode.has("timeFieldSpec"),
+ assertFalse(jsonNode.has("timeFieldSpec"),
"Null timeFieldSpec should be omitted");
- Assert.assertFalse(jsonNode.has("primaryKeyColumns"),
+ assertFalse(jsonNode.has("primaryKeyColumns"),
"Empty primaryKeyColumns should be omitted");
}
@@ -187,28 +194,28 @@ public class SchemaSerializationTest {
// Test with default value (false)
final Schema schemaWithDefault = new Schema.SchemaBuilder()
.setSchemaName("testSchema")
- .addSingleValueDimension("dim1", FieldSpec.DataType.STRING)
+ .addSingleValueDimension("dim1", DataType.STRING)
.build();
final String jsonStringDefault =
JsonUtils.objectToString(schemaWithDefault);
final JsonNode jsonNodeDefault =
JsonUtils.stringToJsonNode(jsonStringDefault);
- Assert.assertTrue(jsonNodeDefault.has("enableColumnBasedNullHandling"),
+ assertTrue(jsonNodeDefault.has("enableColumnBasedNullHandling"),
"enableColumnBasedNullHandling should always be present");
-
Assert.assertFalse(jsonNodeDefault.get("enableColumnBasedNullHandling").asBoolean());
+
assertFalse(jsonNodeDefault.get("enableColumnBasedNullHandling").asBoolean());
// Test with non-default value (true)
final Schema schemaWithEnabled = new Schema.SchemaBuilder()
.setSchemaName("testSchema")
.setEnableColumnBasedNullHandling(true)
- .addSingleValueDimension("dim1", FieldSpec.DataType.STRING)
+ .addSingleValueDimension("dim1", DataType.STRING)
.build();
final String jsonStringEnabled =
JsonUtils.objectToString(schemaWithEnabled);
final JsonNode jsonNodeEnabled =
JsonUtils.stringToJsonNode(jsonStringEnabled);
- Assert.assertTrue(jsonNodeEnabled.has("enableColumnBasedNullHandling"));
-
Assert.assertTrue(jsonNodeEnabled.get("enableColumnBasedNullHandling").asBoolean());
+ assertTrue(jsonNodeEnabled.has("enableColumnBasedNullHandling"));
+
assertTrue(jsonNodeEnabled.get("enableColumnBasedNullHandling").asBoolean());
}
/**
@@ -217,7 +224,7 @@ public class SchemaSerializationTest {
@Test
public void testJsonValueSerializationWithComplexFieldSpecMap()
throws Exception {
- final ComplexFieldSpec mapField = new ComplexFieldSpec("mapField",
FieldSpec.DataType.MAP, true, Map.of());
+ final ComplexFieldSpec mapField = new ComplexFieldSpec("mapField",
DataType.MAP, true, Map.of());
final Schema schema = new Schema.SchemaBuilder()
.setSchemaName("testSchema")
@@ -228,23 +235,23 @@ public class SchemaSerializationTest {
final JsonNode jsonNode = JsonUtils.stringToJsonNode(jsonString);
// Should have complexFieldSpecs
- Assert.assertTrue(jsonNode.has("complexFieldSpecs"));
+ assertTrue(jsonNode.has("complexFieldSpecs"));
final JsonNode complexSpecs = jsonNode.get("complexFieldSpecs");
- Assert.assertEquals(complexSpecs.size(), 1);
+ assertEquals(complexSpecs.size(), 1);
final JsonNode mapSpec = complexSpecs.get(0);
- Assert.assertEquals(mapSpec.get("name").asText(), "mapField");
- Assert.assertEquals(mapSpec.get("dataType").asText(), "MAP");
- Assert.assertEquals(mapSpec.get("fieldType").asText(), "COMPLEX");
+ assertEquals(mapSpec.get("name").asText(), "mapField");
+ assertEquals(mapSpec.get("dataType").asText(), "MAP");
+ assertEquals(mapSpec.get("fieldType").asText(), "COMPLEX");
// defaultNullValue should be omitted since empty Map is the default
- Assert.assertFalse(mapSpec.has("defaultNullValue"),
+ assertFalse(mapSpec.has("defaultNullValue"),
"Empty Map default should not be serialized");
// Verify round-trip
final Schema deserializedSchema = Schema.fromString(jsonString);
- Assert.assertNotNull(deserializedSchema.getFieldSpecFor("mapField"));
-
Assert.assertEquals(deserializedSchema.getFieldSpecFor("mapField").getDataType(),
FieldSpec.DataType.MAP);
+ assertNotNull(deserializedSchema.getFieldSpecFor("mapField"));
+ assertEquals(deserializedSchema.getFieldSpecFor("mapField").getDataType(),
DataType.MAP);
}
/**
@@ -253,7 +260,7 @@ public class SchemaSerializationTest {
@Test
public void testJsonValueSerializationWithComplexFieldSpecList()
throws Exception {
- final ComplexFieldSpec listField = new ComplexFieldSpec("listField",
FieldSpec.DataType.LIST, true, Map.of());
+ final ComplexFieldSpec listField = new ComplexFieldSpec("listField",
DataType.LIST, true, Map.of());
final Schema schema = new Schema.SchemaBuilder()
.setSchemaName("testSchema")
@@ -264,18 +271,18 @@ public class SchemaSerializationTest {
final JsonNode jsonNode = JsonUtils.stringToJsonNode(jsonString);
// Should have complexFieldSpecs
- Assert.assertTrue(jsonNode.has("complexFieldSpecs"));
+ assertTrue(jsonNode.has("complexFieldSpecs"));
final JsonNode complexSpecs = jsonNode.get("complexFieldSpecs");
- Assert.assertEquals(complexSpecs.size(), 1);
+ assertEquals(complexSpecs.size(), 1);
final JsonNode listSpec = complexSpecs.get(0);
- Assert.assertEquals(listSpec.get("name").asText(), "listField");
- Assert.assertEquals(listSpec.get("dataType").asText(), "LIST");
+ assertEquals(listSpec.get("name").asText(), "listField");
+ assertEquals(listSpec.get("dataType").asText(), "LIST");
// Verify round-trip
final Schema deserializedSchema = Schema.fromString(jsonString);
- Assert.assertNotNull(deserializedSchema.getFieldSpecFor("listField"));
-
Assert.assertEquals(deserializedSchema.getFieldSpecFor("listField").getDataType(),
FieldSpec.DataType.LIST);
+ assertNotNull(deserializedSchema.getFieldSpecFor("listField"));
+
assertEquals(deserializedSchema.getFieldSpecFor("listField").getDataType(),
DataType.LIST);
}
/**
@@ -286,24 +293,24 @@ public class SchemaSerializationTest {
throws Exception {
final Schema schema = new Schema.SchemaBuilder()
.setSchemaName("testSchema")
- .addDateTime("timestamp", FieldSpec.DataType.LONG,
"1:MILLISECONDS:EPOCH", "1:MILLISECONDS")
+ .addDateTime("timestamp", DataType.LONG, "1:MILLISECONDS:EPOCH",
"1:MILLISECONDS")
.build();
final String jsonString = JsonUtils.objectToString(schema);
final JsonNode jsonNode = JsonUtils.stringToJsonNode(jsonString);
- Assert.assertTrue(jsonNode.has("dateTimeFieldSpecs"));
+ assertTrue(jsonNode.has("dateTimeFieldSpecs"));
final JsonNode dateTimeSpecs = jsonNode.get("dateTimeFieldSpecs");
- Assert.assertEquals(dateTimeSpecs.size(), 1);
+ assertEquals(dateTimeSpecs.size(), 1);
final JsonNode dtSpec = dateTimeSpecs.get(0);
- Assert.assertEquals(dtSpec.get("name").asText(), "timestamp");
- Assert.assertEquals(dtSpec.get("dataType").asText(), "LONG");
- Assert.assertEquals(dtSpec.get("format").asText(), "1:MILLISECONDS:EPOCH");
- Assert.assertEquals(dtSpec.get("granularity").asText(), "1:MILLISECONDS");
+ assertEquals(dtSpec.get("name").asText(), "timestamp");
+ assertEquals(dtSpec.get("dataType").asText(), "LONG");
+ assertEquals(dtSpec.get("format").asText(), "1:MILLISECONDS:EPOCH");
+ assertEquals(dtSpec.get("granularity").asText(), "1:MILLISECONDS");
// defaultNullValue should be omitted since Long.MIN_VALUE is the default
for DATE_TIME LONG
- Assert.assertFalse(dtSpec.has("defaultNullValue"),
+ assertFalse(dtSpec.has("defaultNullValue"),
"Default null value should not be serialized for DATE_TIME LONG");
}
@@ -316,11 +323,11 @@ public class SchemaSerializationTest {
final Schema schema = new Schema.SchemaBuilder()
.setSchemaName("testSchema")
.setEnableColumnBasedNullHandling(true)
- .addSingleValueDimension("dim1", FieldSpec.DataType.STRING)
- .addSingleValueDimension("dim2", FieldSpec.DataType.INT, 42)
- .addMultiValueDimension("mvDim", FieldSpec.DataType.DOUBLE)
- .addMetric("metric1", FieldSpec.DataType.LONG)
- .addDateTime("ts", FieldSpec.DataType.LONG, "1:HOURS:EPOCH", "1:HOURS")
+ .addSingleValueDimension("dim1", DataType.STRING)
+ .addSingleValueDimension("dim2", DataType.INT, 42)
+ .addMultiValueDimension("mvDim", DataType.DOUBLE)
+ .addMetric("metric1", DataType.LONG)
+ .addDateTime("ts", DataType.LONG, "1:HOURS:EPOCH", "1:HOURS")
.setPrimaryKeyColumns(Lists.newArrayList("dim1"))
.build();
@@ -332,7 +339,7 @@ public class SchemaSerializationTest {
final JsonNode toJsonObjectNode = schema.toJsonObject();
// They should be equal
- Assert.assertEquals(jacksonNode, toJsonObjectNode,
+ assertEquals(jacksonNode, toJsonObjectNode,
"Jackson serialization should match toJsonObject() output");
}
@@ -345,13 +352,13 @@ public class SchemaSerializationTest {
final Schema originalSchema = new Schema.SchemaBuilder()
.setSchemaName("complexTestSchema")
.setEnableColumnBasedNullHandling(true)
- .addSingleValueDimension("stringDim", FieldSpec.DataType.STRING)
- .addSingleValueDimension("intDimWithDefault", FieldSpec.DataType.INT,
100)
- .addMultiValueDimension("mvStringDim", FieldSpec.DataType.STRING,
"default")
- .addMetric("longMetric", FieldSpec.DataType.LONG)
- .addMetric("doubleMetricWithDefault", FieldSpec.DataType.DOUBLE, 3.14)
- .addDateTime("eventTime", FieldSpec.DataType.LONG,
"1:MILLISECONDS:EPOCH", "1:MILLISECONDS")
- .addDateTime("dayTime", FieldSpec.DataType.STRING,
"1:DAYS:SIMPLE_DATE_FORMAT:yyyy-MM-dd", "1:DAYS")
+ .addSingleValueDimension("stringDim", DataType.STRING)
+ .addSingleValueDimension("intDimWithDefault", DataType.INT, 100)
+ .addMultiValueDimension("mvStringDim", DataType.STRING, "default")
+ .addMetric("longMetric", DataType.LONG)
+ .addMetric("doubleMetricWithDefault", DataType.DOUBLE, 3.14)
+ .addDateTime("eventTime", DataType.LONG, "1:MILLISECONDS:EPOCH",
"1:MILLISECONDS")
+ .addDateTime("dayTime", DataType.STRING,
"1:DAYS:SIMPLE_DATE_FORMAT:yyyy-MM-dd", "1:DAYS")
.setPrimaryKeyColumns(Lists.newArrayList("stringDim", "eventTime"))
.build();
@@ -362,24 +369,24 @@ public class SchemaSerializationTest {
final Schema deserializedSchema = Schema.fromString(jsonString);
// Verify all fields
- Assert.assertEquals(deserializedSchema.getSchemaName(),
"complexTestSchema");
- Assert.assertTrue(deserializedSchema.isEnableColumnBasedNullHandling());
+ assertEquals(deserializedSchema.getSchemaName(), "complexTestSchema");
+ assertTrue(deserializedSchema.isEnableColumnBasedNullHandling());
// Verify dimensions
- Assert.assertNotNull(deserializedSchema.getDimensionSpec("stringDim"));
-
Assert.assertEquals(deserializedSchema.getDimensionSpec("intDimWithDefault").getDefaultNullValue(),
100);
-
Assert.assertFalse(deserializedSchema.getDimensionSpec("mvStringDim").isSingleValueField());
+ assertNotNull(deserializedSchema.getDimensionSpec("stringDim"));
+
assertEquals(deserializedSchema.getDimensionSpec("intDimWithDefault").getDefaultNullValue(),
100);
+
assertFalse(deserializedSchema.getDimensionSpec("mvStringDim").isSingleValueField());
// Verify metrics
- Assert.assertNotNull(deserializedSchema.getMetricSpec("longMetric"));
-
Assert.assertEquals(deserializedSchema.getMetricSpec("doubleMetricWithDefault").getDefaultNullValue(),
3.14);
+ assertNotNull(deserializedSchema.getMetricSpec("longMetric"));
+
assertEquals(deserializedSchema.getMetricSpec("doubleMetricWithDefault").getDefaultNullValue(),
3.14);
// Verify date time
- Assert.assertNotNull(deserializedSchema.getDateTimeSpec("eventTime"));
-
Assert.assertEquals(deserializedSchema.getDateTimeSpec("eventTime").getFormat(),
"1:MILLISECONDS:EPOCH");
+ assertNotNull(deserializedSchema.getDateTimeSpec("eventTime"));
+ assertEquals(deserializedSchema.getDateTimeSpec("eventTime").getFormat(),
"1:MILLISECONDS:EPOCH");
// Verify primary keys
- Assert.assertEquals(deserializedSchema.getPrimaryKeyColumns(),
Lists.newArrayList("stringDim", "eventTime"));
+ assertEquals(deserializedSchema.getPrimaryKeyColumns(),
Lists.newArrayList("stringDim", "eventTime"));
}
/**
@@ -391,8 +398,8 @@ public class SchemaSerializationTest {
throws Exception {
final Schema schema = new Schema.SchemaBuilder()
.setSchemaName("testSchema")
- .addSingleValueDimension("dim1", FieldSpec.DataType.STRING)
- .addMetric("metric1", FieldSpec.DataType.LONG)
+ .addSingleValueDimension("dim1", DataType.STRING)
+ .addMetric("metric1", DataType.LONG)
.build();
// Use a fresh ObjectMapper (simulates different Jackson configurations)
@@ -400,11 +407,169 @@ public class SchemaSerializationTest {
final String freshMapperJson = freshMapper.writeValueAsString(schema);
// Should still produce toJsonObject() format
- Assert.assertFalse(freshMapperJson.contains("defaultNullValueString"),
+ assertFalse(freshMapperJson.contains("defaultNullValueString"),
"Fresh ObjectMapper should also use @JsonValue and omit
defaultNullValueString");
// Should be deserializable
final Schema deserializedSchema = Schema.fromString(freshMapperJson);
- Assert.assertEquals(deserializedSchema.getSchemaName(), "testSchema");
+ assertEquals(deserializedSchema.getSchemaName(), "testSchema");
+ }
+
+ @Test
+ public void testComplexFieldDefaultNullValue()
+ throws Exception {
+ // Test LIST
+ ComplexFieldSpec listFieldSpec = new ComplexFieldSpec("list",
DataType.LIST, true, Map.of());
+
+ // Test no defaultNullValue
+ Object defaultNullValue = listFieldSpec.getDefaultNullValue();
+ assertTrue(defaultNullValue instanceof List);
+ assertEquals(defaultNullValue, List.of());
+ ObjectNode jsonObject = listFieldSpec.toJsonObject();
+ assertFalse(jsonObject.has("defaultNullValue"));
+ ComplexFieldSpec deserialized = JsonUtils.jsonNodeToObject(jsonObject,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+ String serialized = jsonObject.toString();
+ assertFalse(serialized.contains("defaultNullValue"));
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+
+ // Test null defaultNullValue
+ serialized = "{"
+ + "\"name\":\"list\","
+ + "\"dataType\":\"LIST\","
+ + "\"fieldType\":\"COMPLEX\","
+ + "\"defaultNullValue\":null,"
+ + "\"childFieldSpecs\":{}"
+ + "}";
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+
+ // Test numeric
+ listFieldSpec.setDefaultNullValue(List.of(1, 2, 3));
+ defaultNullValue = listFieldSpec.getDefaultNullValue();
+ assertTrue(defaultNullValue instanceof List);
+ assertEquals(defaultNullValue, List.of(1, 2, 3));
+ jsonObject = listFieldSpec.toJsonObject();
+ JsonNode defaultNullValueNode = jsonObject.get("defaultNullValue");
+ assertTrue(defaultNullValueNode.isArray());
+ assertEquals(defaultNullValueNode.toString(), "[1,2,3]");
+ deserialized = JsonUtils.jsonNodeToObject(jsonObject,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+ serialized = jsonObject.toString();
+ assertTrue(serialized.contains("\"defaultNullValue\":[1,2,3]"));
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+ // Test compatibility with serialized JSON ARRAY
+ serialized = "{"
+ + "\"name\":\"list\","
+ + "\"dataType\":\"LIST\","
+ + "\"fieldType\":\"COMPLEX\","
+ + "\"defaultNullValue\":\"[1,2,3]\","
+ + "\"childFieldSpecs\":{}"
+ + "}";
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+
+ // Test text
+ listFieldSpec.setDefaultNullValue(List.of("a", "b", "c"));
+ defaultNullValue = listFieldSpec.getDefaultNullValue();
+ assertTrue(defaultNullValue instanceof List);
+ assertEquals(defaultNullValue, List.of("a", "b", "c"));
+ jsonObject = listFieldSpec.toJsonObject();
+ defaultNullValueNode = jsonObject.get("defaultNullValue");
+ assertTrue(defaultNullValueNode.isArray());
+ assertEquals(defaultNullValueNode.toString(), "[\"a\",\"b\",\"c\"]");
+ deserialized = JsonUtils.jsonNodeToObject(jsonObject,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+ serialized = jsonObject.toString();
+
assertTrue(serialized.contains("\"defaultNullValue\":[\"a\",\"b\",\"c\"]"));
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+ // Test compatibility with serialized JSON ARRAY
+ serialized = "{"
+ + "\"name\":\"list\","
+ + "\"dataType\":\"LIST\","
+ + "\"fieldType\":\"COMPLEX\","
+ + "\"defaultNullValue\":\"[\\\"a\\\",\\\"b\\\",\\\"c\\\"]\","
+ + "\"childFieldSpecs\":{}"
+ + "}";
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+
+ // Test MAP
+ ComplexFieldSpec mapFieldSpec = new ComplexFieldSpec("map", DataType.MAP,
true, Map.of());
+
+ // Test no defaultNullValue
+ defaultNullValue = mapFieldSpec.getDefaultNullValue();
+ assertTrue(defaultNullValue instanceof Map);
+ assertEquals(defaultNullValue, Map.of());
+ jsonObject = mapFieldSpec.toJsonObject();
+ assertFalse(jsonObject.has("defaultNullValue"));
+ deserialized = JsonUtils.jsonNodeToObject(jsonObject,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+ serialized = jsonObject.toString();
+ assertFalse(serialized.contains("defaultNullValue"));
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+
+ // Test null defaultNullValue
+ serialized = "{"
+ + "\"name\":\"map\","
+ + "\"dataType\":\"MAP\","
+ + "\"fieldType\":\"COMPLEX\","
+ + "\"defaultNullValue\":null,"
+ + "\"childFieldSpecs\":{}"
+ + "}";
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+
+ // Test numeric
+ mapFieldSpec.setDefaultNullValue(Map.of("a", 1, "b", 2));
+ defaultNullValue = mapFieldSpec.getDefaultNullValue();
+ assertTrue(defaultNullValue instanceof Map);
+ assertEquals(defaultNullValue, Map.of("a", 1, "b", 2));
+ jsonObject = mapFieldSpec.toJsonObject();
+ defaultNullValueNode = jsonObject.get("defaultNullValue");
+ assertTrue(defaultNullValueNode.isObject());
+ deserialized = JsonUtils.jsonNodeToObject(jsonObject,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+ serialized = jsonObject.toString();
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+ // Test compatibility with serialized JSON OBJECT
+ serialized = "{"
+ + "\"name\":\"map\","
+ + "\"dataType\":\"MAP\","
+ + "\"fieldType\":\"COMPLEX\","
+ + "\"defaultNullValue\":\"{\\\"a\\\":1,\\\"b\\\":2}\","
+ + "\"childFieldSpecs\":{}"
+ + "}";
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+
+ // Test text
+ mapFieldSpec.setDefaultNullValue(Map.of("key", "a", "value", "b"));
+ defaultNullValue = mapFieldSpec.getDefaultNullValue();
+ assertTrue(defaultNullValue instanceof Map);
+ assertEquals(defaultNullValue, Map.of("key", "a", "value", "b"));
+ jsonObject = mapFieldSpec.toJsonObject();
+ defaultNullValueNode = jsonObject.get("defaultNullValue");
+ assertTrue(defaultNullValueNode.isObject());
+ deserialized = JsonUtils.jsonNodeToObject(jsonObject,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+ serialized = jsonObject.toString();
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
+ // Test compatibility with serialized JSON OBJECT
+ serialized = "{"
+ + "\"name\":\"map\","
+ + "\"dataType\":\"MAP\","
+ + "\"fieldType\":\"COMPLEX\","
+ +
"\"defaultNullValue\":\"{\\\"key\\\":\\\"a\\\",\\\"value\\\":\\\"b\\\"}\","
+ + "\"childFieldSpecs\":{}"
+ + "}";
+ deserialized = JsonUtils.stringToObject(serialized,
ComplexFieldSpec.class);
+ assertEquals(deserialized.getDefaultNullValue(), defaultNullValue);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]