diff --git a/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/IdentityPartitionDTO.java b/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/IdentityPartitionDTO.java new file mode 100644 index 00000000000..5e98c475b5a --- /dev/null +++ b/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/IdentityPartitionDTO.java @@ -0,0 +1,108 @@ +/* + * Copyright 2024 Datastrato Pvt Ltd. + * This software is licensed under the Apache License version 2. + */ +package com.datastrato.gravitino.dto.rel.partitions; + +import com.datastrato.gravitino.dto.rel.expressions.LiteralDTO; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +public class IdentityPartitionDTO implements PartitionDTO { + + private final String name; + private final String[][] fieldNames; + private final LiteralDTO[] values; + private final Map properties; + + public static Builder builder() { + return new Builder(); + } + + private IdentityPartitionDTO() { + this(null, null, null, null); + } + + private IdentityPartitionDTO( + String name, String[][] fieldNames, LiteralDTO[] values, Map properties) { + this.name = name; + this.fieldNames = fieldNames; + this.values = values; + this.properties = properties; + } + + @Override + public String name() { + return name; + } + + public String[][] fieldNames() { + return fieldNames; + } + + public LiteralDTO[] values() { + return values; + } + + @Override + public Map properties() { + return properties; + } + + @Override + public Type type() { + return Type.IDENTITY; + } + + public static class Builder { + private String name; + private String[][] fieldNames; + private LiteralDTO[] values; + private Map properties; + + public Builder withName(String name) { + this.name = name; + return this; + } + + public Builder withFieldNames(String[][] fieldNames) { + this.fieldNames = fieldNames; + return this; + } + + public Builder withValues(LiteralDTO[] values) { + this.values = values; + return this; + } + + public Builder withProperties(Map properties) { + this.properties = properties; + return this; + } + + public IdentityPartitionDTO build() { + return new IdentityPartitionDTO(name, fieldNames, values, properties); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IdentityPartitionDTO that = (IdentityPartitionDTO) o; + return Objects.equals(name, that.name) + && Arrays.deepEquals(fieldNames, that.fieldNames) + && Arrays.equals(values, that.values) + && Objects.equals(properties, that.properties); + } + + @Override + public int hashCode() { + return Objects.hash(name, Arrays.deepHashCode(fieldNames), Arrays.hashCode(values), properties); + } +} diff --git a/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/ListPartitionDTO.java b/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/ListPartitionDTO.java new file mode 100644 index 00000000000..a5f4ae28cb6 --- /dev/null +++ b/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/ListPartitionDTO.java @@ -0,0 +1,96 @@ +/* + * Copyright 2024 Datastrato Pvt Ltd. + * This software is licensed under the Apache License version 2. + */ +package com.datastrato.gravitino.dto.rel.partitions; + +import com.datastrato.gravitino.dto.rel.expressions.LiteralDTO; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +public class ListPartitionDTO implements PartitionDTO { + + private final String name; + private final LiteralDTO[][] lists; + private final Map properties; + + public static Builder builder() { + return new Builder(); + } + + private ListPartitionDTO() { + this(null, null, null); + } + + private ListPartitionDTO(String name, LiteralDTO[][] lists, Map properties) { + this.name = name; + this.lists = lists; + this.properties = properties; + } + + @Override + public String name() { + return name; + } + + public LiteralDTO[][] lists() { + return lists; + } + + @Override + public Map properties() { + return properties; + } + + @Override + public Type type() { + return Type.LIST; + } + + public static class Builder { + private String name; + private LiteralDTO[][] lists; + private Map properties; + + public Builder withName(String name) { + this.name = name; + return this; + } + + public Builder withLists(LiteralDTO[][] lists) { + this.lists = lists; + return this; + } + + public Builder withProperties(Map properties) { + this.properties = properties; + return this; + } + + public ListPartitionDTO build() { + return new ListPartitionDTO(name, lists, properties); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ListPartitionDTO that = (ListPartitionDTO) o; + return Objects.equals(name, that.name) + && Arrays.deepEquals(lists, that.lists) + && Objects.equals(properties, that.properties); + } + + @Override + public int hashCode() { + int result = Objects.hash(name, properties); + result = 31 * result + Arrays.deepHashCode(lists); + return result; + } +} diff --git a/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/PartitionDTO.java b/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/PartitionDTO.java new file mode 100644 index 00000000000..dbde45990ad --- /dev/null +++ b/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/PartitionDTO.java @@ -0,0 +1,23 @@ +/* + * Copyright 2024 Datastrato Pvt Ltd. + * This software is licensed under the Apache License version 2. + */ +package com.datastrato.gravitino.dto.rel.partitions; + +import com.datastrato.gravitino.json.JsonUtils; +import com.datastrato.gravitino.rel.partitions.Partition; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +@JsonSerialize(using = JsonUtils.PartitionDTOSerializer.class) +@JsonDeserialize(using = JsonUtils.PartitionDTODeserializer.class) +public interface PartitionDTO extends Partition { + + Type type(); + + enum Type { + RANGE, + LIST, + IDENTITY + } +} diff --git a/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/RangePartitionDTO.java b/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/RangePartitionDTO.java new file mode 100644 index 00000000000..54b569d2fc9 --- /dev/null +++ b/common/src/main/java/com/datastrato/gravitino/dto/rel/partitions/RangePartitionDTO.java @@ -0,0 +1,107 @@ +/* + * Copyright 2024 Datastrato Pvt Ltd. + * This software is licensed under the Apache License version 2. + */ +package com.datastrato.gravitino.dto.rel.partitions; + +import com.datastrato.gravitino.dto.rel.expressions.LiteralDTO; +import java.util.Map; +import java.util.Objects; + +public class RangePartitionDTO implements PartitionDTO { + + private final String name; + private final Map properties; + private final LiteralDTO upper; + private final LiteralDTO lower; + + public static Builder builder() { + return new Builder(); + } + + private RangePartitionDTO() { + this(null, null, null, null); + } + + private RangePartitionDTO( + String name, Map properties, LiteralDTO upper, LiteralDTO lower) { + this.name = name; + this.properties = properties; + this.upper = upper; + this.lower = lower; + } + + @Override + public String name() { + return name; + } + + public LiteralDTO upper() { + return upper; + } + + public LiteralDTO lower() { + return lower; + } + + @Override + public Map properties() { + return properties; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RangePartitionDTO that = (RangePartitionDTO) o; + return Objects.equals(name, that.name) + && Objects.equals(properties, that.properties) + && Objects.equals(upper, that.upper) + && Objects.equals(lower, that.lower); + } + + @Override + public int hashCode() { + return Objects.hash(name, properties, upper, lower); + } + + @Override + public Type type() { + return Type.RANGE; + } + + public static class Builder { + private String name; + private Map properties; + private LiteralDTO upper; + private LiteralDTO lower; + + public Builder withName(String name) { + this.name = name; + return this; + } + + public Builder withProperties(Map properties) { + this.properties = properties; + return this; + } + + public Builder withUpper(LiteralDTO upper) { + this.upper = upper; + return this; + } + + public Builder withLower(LiteralDTO lower) { + this.lower = lower; + return this; + } + + public RangePartitionDTO build() { + return new RangePartitionDTO(name, properties, upper, lower); + } + } +} diff --git a/common/src/main/java/com/datastrato/gravitino/json/JsonUtils.java b/common/src/main/java/com/datastrato/gravitino/json/JsonUtils.java index b4afc2dd6ec..024f61b8f77 100644 --- a/common/src/main/java/com/datastrato/gravitino/json/JsonUtils.java +++ b/common/src/main/java/com/datastrato/gravitino/json/JsonUtils.java @@ -23,6 +23,10 @@ import com.datastrato.gravitino.dto.rel.partitioning.RangePartitioningDTO; import com.datastrato.gravitino.dto.rel.partitioning.TruncatePartitioningDTO; import com.datastrato.gravitino.dto.rel.partitioning.YearPartitioningDTO; +import com.datastrato.gravitino.dto.rel.partitions.IdentityPartitionDTO; +import com.datastrato.gravitino.dto.rel.partitions.ListPartitionDTO; +import com.datastrato.gravitino.dto.rel.partitions.PartitionDTO; +import com.datastrato.gravitino.dto.rel.partitions.RangePartitionDTO; import com.datastrato.gravitino.rel.Column; import com.datastrato.gravitino.rel.TableChange; import com.datastrato.gravitino.rel.expressions.Expression; @@ -54,6 +58,7 @@ import java.io.IOException; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.slf4j.Logger; @@ -97,6 +102,13 @@ public class JsonUtils { private static final String MAP_KEY_TYPE = "keyType"; private static final String MAP_VALUE_TYPE = "valueType"; private static final String MAP_VALUE_NULLABLE = "valueContainsNull"; + private static final String PARTITION_TYPE = "type"; + private static final String PARTITION_NAME = "name"; + private static final String PARTITION_PROPERTIES = "properties"; + private static final String IDENTITY_PARTITION_VALUES = "values"; + private static final String LIST_PARTITION_LISTS = "lists"; + private static final String RANGE_PARTITION_UPPER = "upper"; + private static final String RANGE_PARTITION_LOWER = "lower"; private static final ImmutableMap TYPES = Maps.uniqueIndex( ImmutableList.of( @@ -286,8 +298,7 @@ public static FunctionArg readFunctionArg(JsonNode node) { "Cannot parse literal arg from missing literal value: %s", node); Type dataType = readDataType(node.get(DATA_TYPE)); - JsonNode jsonNode = node.get(LITERAL_VALUE); - String value = jsonNode.isNull() ? null : jsonNode.asText(); + String value = getStringOrNull(LITERAL_VALUE, node); return new LiteralDTO.Builder().withDataType(dataType).withValue(value).build(); case FIELD: Preconditions.checkArgument( @@ -358,6 +369,29 @@ public static String getString(String property, JsonNode node) { return convertToString(property, pNode); } + private static String getStringOrNull(String property, JsonNode node) { + if (!node.has(property) || node.get(property).isNull()) { + return null; + } + + return getString(property, node); + } + + private static Map getStringMapOrNull(String property, JsonNode node) { + if (!node.has(property) || node.get(property).isNull()) { + return null; + } + + JsonNode propertiesNode = node.get(property); + Iterator> fieldsIterator = propertiesNode.fields(); + Map properties = Maps.newHashMap(); + while (fieldsIterator.hasNext()) { + Map.Entry field = fieldsIterator.next(); + properties.put(field.getKey(), field.getValue().asText()); + } + return properties; + } + private static String convertToString(String property, JsonNode pNode) { Preconditions.checkArgument( pNode != null && !pNode.isNull() && pNode.isTextual(), @@ -933,4 +967,133 @@ public Expression deserialize(JsonParser p, DeserializationContext ctxt) throws return readFunctionArg(node); } } + + public static class PartitionDTOSerializer extends JsonSerializer { + @Override + public void serialize(PartitionDTO value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeStartObject(); + gen.writeStringField(PARTITION_TYPE, value.type().name().toLowerCase()); + gen.writeStringField(PARTITION_NAME, value.name()); + switch (value.type()) { + case IDENTITY: + IdentityPartitionDTO identityPartitionDTO = (IdentityPartitionDTO) value; + gen.writeFieldName(FIELD_NAMES); + gen.writeObject(identityPartitionDTO.fieldNames()); + gen.writeArrayFieldStart(IDENTITY_PARTITION_VALUES); + for (LiteralDTO literal : identityPartitionDTO.values()) { + writeFunctionArg(literal, gen); + } + gen.writeEndArray(); + break; + case LIST: + ListPartitionDTO listPartitionDTO = (ListPartitionDTO) value; + gen.writeArrayFieldStart(LIST_PARTITION_LISTS); + for (LiteralDTO[] literals : listPartitionDTO.lists()) { + gen.writeStartArray(); + for (LiteralDTO literal : literals) { + writeFunctionArg(literal, gen); + } + gen.writeEndArray(); + } + gen.writeEndArray(); + break; + case RANGE: + RangePartitionDTO rangePartitionDTO = (RangePartitionDTO) value; + gen.writeFieldName(RANGE_PARTITION_UPPER); + writeFunctionArg(rangePartitionDTO.upper(), gen); + gen.writeFieldName(RANGE_PARTITION_LOWER); + writeFunctionArg(rangePartitionDTO.lower(), gen); + break; + default: + throw new IOException("Unknown partition type: " + value.type()); + } + gen.writeObjectField(PARTITION_PROPERTIES, value.properties()); + gen.writeEndObject(); + } + } + + public static class PartitionDTODeserializer extends JsonDeserializer { + @Override + public PartitionDTO deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.getCodec().readTree(p); + Preconditions.checkArgument( + node != null && !node.isNull() && node.isObject(), + "Partition must be a valid JSON object, but found: %s", + node); + Preconditions.checkArgument( + node.has(PARTITION_TYPE), "Partition must have a type field, but found: %s", node); + String type = getString(PARTITION_TYPE, node); + switch (PartitionDTO.Type.valueOf(type.toUpperCase())) { + case IDENTITY: + Preconditions.checkArgument( + node.has(FIELD_NAMES) && node.get(FIELD_NAMES).isArray(), + "Identity partition must have array of fieldNames, but found: %s", + node); + Preconditions.checkArgument( + node.has(IDENTITY_PARTITION_VALUES) && node.get(IDENTITY_PARTITION_VALUES).isArray(), + "Identity partition must have array of values, but found: %s", + node); + + List fieldNames = Lists.newArrayList(); + node.get(FIELD_NAMES).forEach(field -> fieldNames.add(getStringArray((ArrayNode) field))); + List values = Lists.newArrayList(); + node.get(IDENTITY_PARTITION_VALUES) + .forEach(value -> values.add((LiteralDTO) readFunctionArg(value))); + return IdentityPartitionDTO.builder() + .withName(getStringOrNull(PARTITION_NAME, node)) + .withFieldNames(fieldNames.toArray(new String[0][0])) + .withValues(values.toArray(new LiteralDTO[0])) + .withProperties(getStringMapOrNull(PARTITION_PROPERTIES, node)) + .build(); + + case LIST: + Preconditions.checkArgument( + node.has(PARTITION_NAME), "List partition must have name, but found: %s", node); + Preconditions.checkArgument( + node.has(LIST_PARTITION_LISTS) && node.get(LIST_PARTITION_LISTS).isArray(), + "List partition must have array of lists, but found: %s", + node); + + List lists = Lists.newArrayList(); + node.get(LIST_PARTITION_LISTS) + .forEach( + list -> { + List literals = Lists.newArrayList(); + list.forEach(literal -> literals.add((LiteralDTO) readFunctionArg(literal))); + lists.add(literals.toArray(new LiteralDTO[0])); + }); + + return ListPartitionDTO.builder() + .withName(getStringOrNull(PARTITION_NAME, node)) + .withLists(lists.toArray(new LiteralDTO[0][0])) + .withProperties(getStringMapOrNull(PARTITION_PROPERTIES, node)) + .build(); + + case RANGE: + Preconditions.checkArgument( + node.has(PARTITION_NAME), "Range partition must have name, but found: %s", node); + Preconditions.checkArgument( + node.has(RANGE_PARTITION_UPPER), + "Range partition must have upper, but found: %s", + node); + Preconditions.checkArgument( + node.has(RANGE_PARTITION_LOWER), + "Range partition must have lower, but found: %s", + node); + + LiteralDTO upper = (LiteralDTO) readFunctionArg(node.get(RANGE_PARTITION_UPPER)); + LiteralDTO lower = (LiteralDTO) readFunctionArg(node.get(RANGE_PARTITION_LOWER)); + return RangePartitionDTO.builder() + .withName(getStringOrNull(PARTITION_NAME, node)) + .withUpper(upper) + .withLower(lower) + .withProperties(getStringMapOrNull(PARTITION_PROPERTIES, node)) + .build(); + + default: + throw new IOException("Unknown partition type: " + type); + } + } + } } diff --git a/common/src/test/java/com/datastrato/gravitino/json/TestJsonUtils.java b/common/src/test/java/com/datastrato/gravitino/json/TestJsonUtils.java index 4e3657c499d..f371a85ddbd 100644 --- a/common/src/test/java/com/datastrato/gravitino/json/TestJsonUtils.java +++ b/common/src/test/java/com/datastrato/gravitino/json/TestJsonUtils.java @@ -8,8 +8,14 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import com.datastrato.gravitino.dto.rel.expressions.LiteralDTO; +import com.datastrato.gravitino.dto.rel.partitions.IdentityPartitionDTO; +import com.datastrato.gravitino.dto.rel.partitions.ListPartitionDTO; +import com.datastrato.gravitino.dto.rel.partitions.PartitionDTO; +import com.datastrato.gravitino.dto.rel.partitions.RangePartitionDTO; import com.datastrato.gravitino.rel.types.Type; import com.datastrato.gravitino.rel.types.Types; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; @@ -160,4 +166,227 @@ void testGetLong() throws Exception { Long result = JsonUtils.getLong("property", nodeNormal); assertEquals(1L, result); } + + @Test + void testPartitionDTOSerde() throws JsonProcessingException { + String[] field1 = {"dt"}; + String[] field2 = {"country"}; + LiteralDTO literal1 = + new LiteralDTO.Builder().withDataType(Types.DateType.get()).withValue("2008-08-08").build(); + LiteralDTO literal2 = + new LiteralDTO.Builder().withDataType(Types.StringType.get()).withValue("us").build(); + PartitionDTO partition = + IdentityPartitionDTO.builder() + .withFieldNames(new String[][] {field1, field2}) + .withValues(new LiteralDTO[] {literal1, literal2}) + .build(); + String jsonValue = JsonUtils.objectMapper().writeValueAsString(partition); + + String expected = + "{\n" + + " \"type\": \"identity\",\n" + + " \"fieldNames\": [\n" + + " [\n" + + " \"dt\"\n" + + " ],\n" + + " [\n" + + " \"country\"\n" + + " ]\n" + + " ],\n" + + " \"values\": [\n" + + " {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"date\",\n" + + " \"value\": \"2008-08-08\"\n" + + " },\n" + + " {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"string\",\n" + + " \"value\": \"us\"\n" + + " }\n" + + " ]\n" + + "}"; + + Assertions.assertEquals( + objectMapper.readValue(expected, IdentityPartitionDTO.class), + objectMapper.readValue(jsonValue, IdentityPartitionDTO.class)); + + partition = + RangePartitionDTO.builder() + .withName("p0") + .withUpper( + new LiteralDTO.Builder() + .withDataType(Types.NullType.get()) + .withValue("null") + .build()) + .withLower( + new LiteralDTO.Builder() + .withDataType(Types.IntegerType.get()) + .withValue("6") + .build()) + .build(); + jsonValue = JsonUtils.objectMapper().writeValueAsString(partition); + expected = + "{\n" + + " \"type\": \"range\",\n" + + " \"name\": \"p0\",\n" + + " \"upper\": {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"null\",\n" + + " \"value\": \"null\"\n" + + " },\n" + + " \"lower\": {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"integer\",\n" + + " \"value\": \"6\"\n" + + " }\n" + + "}"; + Assertions.assertEquals( + objectMapper.readValue(expected, RangePartitionDTO.class), + objectMapper.readValue(jsonValue, RangePartitionDTO.class)); + + partition = + ListPartitionDTO.builder() + .withName("p202204_California") + .withLists( + new LiteralDTO[][] { + { + new LiteralDTO.Builder() + .withDataType(Types.DateType.get()) + .withValue("2022-04-01") + .build(), + new LiteralDTO.Builder() + .withDataType(Types.StringType.get()) + .withValue("Los Angeles") + .build() + }, + { + new LiteralDTO.Builder() + .withDataType(Types.DateType.get()) + .withValue("2022-04-01") + .build(), + new LiteralDTO.Builder() + .withDataType(Types.StringType.get()) + .withValue("San Francisco") + .build() + } + }) + .build(); + jsonValue = JsonUtils.objectMapper().writeValueAsString(partition); + expected = + "{\n" + + " \"type\": \"list\",\n" + + " \"name\": \"p202204_California\",\n" + + " \"lists\": [\n" + + " [\n" + + " {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"date\",\n" + + " \"value\": \"2022-04-01\"\n" + + " },\n" + + " {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"string\",\n" + + " \"value\": \"Los Angeles\"\n" + + " }\n" + + " ],\n" + + " [\n" + + " {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"date\",\n" + + " \"value\": \"2022-04-01\"\n" + + " },\n" + + " {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"string\",\n" + + " \"value\": \"San Francisco\"\n" + + " }\n" + + " ]\n" + + " ]\n" + + "}"; + Assertions.assertEquals( + objectMapper.readValue(expected, ListPartitionDTO.class), + objectMapper.readValue(jsonValue, ListPartitionDTO.class)); + } + + @Test + void testPartitionDTOSerdeException() { + String illegalJson1 = + "{\n" + + " \"type\": \"identity\",\n" + + " \"fieldNames\": [\n" + + " [\n" + + " \"dt\"\n" + + " ],\n" + + " [\n" + + " \"country\"\n" + + " ]\n" + + " ]\n" + + "}"; + IllegalArgumentException exception = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> objectMapper.readValue(illegalJson1, PartitionDTO.class)); + Assertions.assertTrue( + exception.getMessage().contains("Identity partition must have array of values"), + exception.getMessage()); + + String illegalJson2 = + "{\n" + + " \"type\": \"list\",\n" + + " \"lists\": [\n" + + " [\n" + + " {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"date\",\n" + + " \"value\": \"2022-04-01\"\n" + + " },\n" + + " {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"string\",\n" + + " \"value\": \"Los Angeles\"\n" + + " }\n" + + " ],\n" + + " [\n" + + " {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"date\",\n" + + " \"value\": \"2022-04-01\"\n" + + " },\n" + + " {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"string\",\n" + + " \"value\": \"San Francisco\"\n" + + " }\n" + + " ]\n" + + " ]\n" + + "}"; + exception = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> objectMapper.readValue(illegalJson2, PartitionDTO.class)); + Assertions.assertTrue( + exception.getMessage().contains("List partition must have name"), exception.getMessage()); + + String illegalJson3 = + "{\n" + + " \"type\": \"range\",\n" + + " \"upper\": {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"null\",\n" + + " \"value\": \"null\"\n" + + " },\n" + + " \"lower\": {\n" + + " \"type\": \"literal\",\n" + + " \"dataType\": \"integer\",\n" + + " \"value\": \"6\"\n" + + " }\n" + + "}"; + exception = + Assertions.assertThrows( + IllegalArgumentException.class, + () -> objectMapper.readValue(illegalJson3, PartitionDTO.class)); + Assertions.assertTrue( + exception.getMessage().contains("Range partition must have name"), exception.getMessage()); + } }