From 491746dc937bf4f24570a9dac194fe75352e278f Mon Sep 17 00:00:00 2001 From: Saisai Shao Date: Tue, 23 May 2023 10:07:39 +0800 Subject: [PATCH] [#16] feat(schema) add JSON serialize and deserialize support for schema (#20) ### What changes were proposed in this pull request? This PR adds JSON serde support for schema system. ### Why are the changes needed? The adds of JSON serde support will help to support REST API for Unified Catalog. Fix: #16 ### Does this PR introduce _any_ user-facing change? NA ### How was this patch tested? And new UTs. --- core/build.gradle.kts | 1 + .../unified_catalog/schema/AuditInfo.java | 2 + .../unified_catalog/schema/Column.java | 7 + .../unified_catalog/schema/Lakehouse.java | 2 + .../unified_catalog/schema/SchemaVersion.java | 25 + .../unified_catalog/schema/Table.java | 4 +- .../unified_catalog/schema/Tenant.java | 2 + .../schema/VirtualTableInfo.java | 8 + .../unified_catalog/schema/Zone.java | 2 + .../unified_catalog/schema/hasExtraInfo.java | 6 + .../schema/json/JsonUtils.java | 57 +++ .../schema/json/TestEntityJsonSerDe.java | 466 ++++++++++++++++++ gradle/libs.versions.toml | 1 + 13 files changed, 582 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/com/datastrato/unified_catalog/schema/json/JsonUtils.java create mode 100644 core/src/test/java/com/datastrato/unified_catalog/schema/json/TestEntityJsonSerDe.java diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 40af1d66416..e8a996c2537 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { implementation(libs.jackson.databind) implementation(libs.jackson.annotations) implementation(libs.jackson.datatype.jdk8) + implementation(libs.jackson.datatype.jsr310) implementation(libs.guava) compileOnly(libs.lombok) diff --git a/core/src/main/java/com/datastrato/unified_catalog/schema/AuditInfo.java b/core/src/main/java/com/datastrato/unified_catalog/schema/AuditInfo.java index 17c93b44eda..04c39d126d1 100644 --- a/core/src/main/java/com/datastrato/unified_catalog/schema/AuditInfo.java +++ b/core/src/main/java/com/datastrato/unified_catalog/schema/AuditInfo.java @@ -9,9 +9,11 @@ import javax.annotation.Nullable; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @EqualsAndHashCode +@ToString public final class AuditInfo implements Entity { public static final Field CREATOR = Field.required("creator", String.class, "The name of user who creates the entity"); diff --git a/core/src/main/java/com/datastrato/unified_catalog/schema/Column.java b/core/src/main/java/com/datastrato/unified_catalog/schema/Column.java index c9754c9aa16..72099c9bdd8 100644 --- a/core/src/main/java/com/datastrato/unified_catalog/schema/Column.java +++ b/core/src/main/java/com/datastrato/unified_catalog/schema/Column.java @@ -1,6 +1,9 @@ package com.datastrato.unified_catalog.schema; +import com.datastrato.unified_catalog.schema.json.JsonUtils; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.substrait.type.Type; import java.util.Collections; import java.util.HashMap; @@ -8,9 +11,11 @@ import javax.annotation.Nullable; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @EqualsAndHashCode +@ToString public final class Column implements Entity, Auditable { public static final Field ID = @@ -47,6 +52,8 @@ public final class Column implements Entity, Auditable { private String name; @JsonProperty("type") + @JsonSerialize(using = JsonUtils.TypeSerializer.class) + @JsonDeserialize(using = JsonUtils.TypeDeserializer.class) private Type type; @Nullable diff --git a/core/src/main/java/com/datastrato/unified_catalog/schema/Lakehouse.java b/core/src/main/java/com/datastrato/unified_catalog/schema/Lakehouse.java index 6b19dd5a63c..488de63fca8 100644 --- a/core/src/main/java/com/datastrato/unified_catalog/schema/Lakehouse.java +++ b/core/src/main/java/com/datastrato/unified_catalog/schema/Lakehouse.java @@ -7,9 +7,11 @@ import javax.annotation.Nullable; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @EqualsAndHashCode +@ToString public class Lakehouse implements Entity, Auditable { public static final Field ID = diff --git a/core/src/main/java/com/datastrato/unified_catalog/schema/SchemaVersion.java b/core/src/main/java/com/datastrato/unified_catalog/schema/SchemaVersion.java index 57f113a974c..94abd1bf7f0 100644 --- a/core/src/main/java/com/datastrato/unified_catalog/schema/SchemaVersion.java +++ b/core/src/main/java/com/datastrato/unified_catalog/schema/SchemaVersion.java @@ -1,11 +1,36 @@ package com.datastrato.unified_catalog.schema; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonFormat(shape = JsonFormat.Shape.OBJECT) public enum SchemaVersion { V_0_1(0, 1); + @JsonProperty("major_version") public final int majorVersion; + + @JsonProperty("minor_version") public final int minorVersion; + @JsonCreator + public static SchemaVersion forValues( + @JsonProperty("major_version") int majorVersion, + @JsonProperty("minor_version") int minorVersion) { + for (SchemaVersion schemaVersion : SchemaVersion.values()) { + if (schemaVersion.majorVersion == majorVersion + && schemaVersion.minorVersion == minorVersion) { + return schemaVersion; + } + } + + throw new IllegalArgumentException( + String.format( + "No schema version found for major version %d and minor version %d", + majorVersion, minorVersion)); + } + SchemaVersion(int majorVersion, int minorVersion) { this.majorVersion = majorVersion; this.minorVersion = minorVersion; diff --git a/core/src/main/java/com/datastrato/unified_catalog/schema/Table.java b/core/src/main/java/com/datastrato/unified_catalog/schema/Table.java index 0eec5e9fa6e..fb07964c8cd 100644 --- a/core/src/main/java/com/datastrato/unified_catalog/schema/Table.java +++ b/core/src/main/java/com/datastrato/unified_catalog/schema/Table.java @@ -5,11 +5,13 @@ import javax.annotation.Nullable; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @EqualsAndHashCode +@ToString public class Table implements Entity, Auditable, hasExtraInfo { - enum TableType { + public enum TableType { VIRTUAL("VIRTUAL"), VIEW("VIEW"), EXTERNAL("EXTERNAL"), diff --git a/core/src/main/java/com/datastrato/unified_catalog/schema/Tenant.java b/core/src/main/java/com/datastrato/unified_catalog/schema/Tenant.java index beb2d9b60e0..7e45d39543c 100644 --- a/core/src/main/java/com/datastrato/unified_catalog/schema/Tenant.java +++ b/core/src/main/java/com/datastrato/unified_catalog/schema/Tenant.java @@ -7,9 +7,11 @@ import javax.annotation.Nullable; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @EqualsAndHashCode +@ToString public final class Tenant implements Entity, Auditable { public static final Field ID = diff --git a/core/src/main/java/com/datastrato/unified_catalog/schema/VirtualTableInfo.java b/core/src/main/java/com/datastrato/unified_catalog/schema/VirtualTableInfo.java index 991f019b548..9dd8adf9278 100644 --- a/core/src/main/java/com/datastrato/unified_catalog/schema/VirtualTableInfo.java +++ b/core/src/main/java/com/datastrato/unified_catalog/schema/VirtualTableInfo.java @@ -6,9 +6,11 @@ import java.util.Map; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @EqualsAndHashCode +@ToString public final class VirtualTableInfo implements hasExtraInfo.ExtraInfo { public static final Field CONNECTION_ID = @@ -22,6 +24,12 @@ public final class VirtualTableInfo implements hasExtraInfo.ExtraInfo { @JsonProperty("identifier") private final List identifier; + // Only for Jackson deserialization + private VirtualTableInfo() { + this.connectionId = null; + this.identifier = null; + } + public VirtualTableInfo(Integer connectionId, List identifier) { this.connectionId = connectionId; this.identifier = identifier; diff --git a/core/src/main/java/com/datastrato/unified_catalog/schema/Zone.java b/core/src/main/java/com/datastrato/unified_catalog/schema/Zone.java index 4751e51de5e..5bbdb97b274 100644 --- a/core/src/main/java/com/datastrato/unified_catalog/schema/Zone.java +++ b/core/src/main/java/com/datastrato/unified_catalog/schema/Zone.java @@ -7,9 +7,11 @@ import javax.annotation.Nullable; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @EqualsAndHashCode +@ToString public class Zone implements Entity, Auditable { public static final Field ID = diff --git a/core/src/main/java/com/datastrato/unified_catalog/schema/hasExtraInfo.java b/core/src/main/java/com/datastrato/unified_catalog/schema/hasExtraInfo.java index 8abe75e02c6..05d7850facb 100644 --- a/core/src/main/java/com/datastrato/unified_catalog/schema/hasExtraInfo.java +++ b/core/src/main/java/com/datastrato/unified_catalog/schema/hasExtraInfo.java @@ -1,7 +1,13 @@ package com.datastrato.unified_catalog.schema; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + /** Interface for entities that have extra info. */ public interface hasExtraInfo { + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") + @JsonSubTypes({@JsonSubTypes.Type(value = VirtualTableInfo.class, name = "VIRTUAL")}) interface ExtraInfo extends Entity {} /** diff --git a/core/src/main/java/com/datastrato/unified_catalog/schema/json/JsonUtils.java b/core/src/main/java/com/datastrato/unified_catalog/schema/json/JsonUtils.java new file mode 100644 index 00000000000..f178c18f43b --- /dev/null +++ b/core/src/main/java/com/datastrato/unified_catalog/schema/json/JsonUtils.java @@ -0,0 +1,57 @@ +package com.datastrato.unified_catalog.schema.json; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.substrait.type.StringTypeVisitor; +import io.substrait.type.Type; +import io.substrait.type.parser.ParseToPojo; +import io.substrait.type.parser.TypeStringParser; +import java.io.IOException; + +public class JsonUtils { + private static ObjectMapper mapper = null; + + public static ObjectMapper objectMapper() { + if (mapper == null) { + synchronized (JsonUtils.class) { + if (mapper == null) { + mapper = + new ObjectMapper() + .registerModule(new JavaTimeModule()) + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + } + } + } + + return mapper; + } + + public static class TypeSerializer extends JsonSerializer { + private final StringTypeVisitor visitor = new StringTypeVisitor(); + + @Override + public void serialize(Type value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + try { + gen.writeString(value.accept(visitor)); + } catch (Exception e) { + throw new IOException("Unable to serialize type " + value, e); + } + } + } + + public static class TypeDeserializer extends JsonDeserializer { + + @Override + public Type deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String s = p.getValueAsString(); + try { + return TypeStringParser.parse(s, ParseToPojo::type); + } catch (Exception e) { + throw new IOException("Unable to parse string " + s.replace("\n", " \\n"), e); + } + } + } +} diff --git a/core/src/test/java/com/datastrato/unified_catalog/schema/json/TestEntityJsonSerDe.java b/core/src/test/java/com/datastrato/unified_catalog/schema/json/TestEntityJsonSerDe.java new file mode 100644 index 00000000000..05e8e58c2ae --- /dev/null +++ b/core/src/test/java/com/datastrato/unified_catalog/schema/json/TestEntityJsonSerDe.java @@ -0,0 +1,466 @@ +package com.datastrato.unified_catalog.schema.json; + +import com.datastrato.unified_catalog.schema.*; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.substrait.type.StringTypeVisitor; +import io.substrait.type.TypeCreator; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TestEntityJsonSerDe { + + private final String auditInfoJson = + "{\"creator\":%s,\"create_time\":%s," + + "\"last_modifier\":%s,\"last_modified_time\":%s," + + "\"last_access_user\":%s,\"last_access_time\":%s}"; + + private final String tenantJson = + "{\"id\":%d,\"name\":%s,\"comment\":%s," + + "\"audit_info\":%s,\"version\":{\"major_version\":%d,\"minor_version\":%d}}"; + + private final String lakehouseJson = + "{\"id\":%d,\"name\":%s,\"comment\":%s,\"properties\":%s,\"audit_info\":%s}"; + + private final String zoneJson = + "{\"id\":%d,\"lakehouse_id\":%d,\"name\":%s,\"comment\":%s," + + "\"properties\":%s,\"audit_info\":%s}"; + + private final String columnJson = + "{\"id\":%d,\"entity_id\":%d,\"entity_snapshot_id\":%d,\"name\":%s," + + "\"type\":%s,\"comment\":%s,\"position\":%d,\"audit_info\":%s}"; + + private final String tableJson = + "{\"id\":%d,\"zone_id\":%d,\"name\":%s,\"comment\":%s,\"type\":%s,\"snapshot_id\":%d," + + "\"properties\":%s,\"audit_info\":%s," + + "\"extra_info\":{\"type\":%s,\"connection_id\":%d,\"identifier\":%s}," + + "\"columns\":[%s]}"; + + private String withQuotes(String str) { + return "\"" + str + "\""; + } + + @Test + public void testAuditInfoJsonSerDe() throws Exception { + Instant now = Instant.now(); + String creator = "creator"; + String modifier = "modifier"; + String accessor = "accessor"; + + // Test with required fields + AuditInfo info = new AuditInfo.Builder().withCreator(creator).withCreateTime(now).build(); + + String serJson = JsonUtils.objectMapper().writeValueAsString(info); + String expectedJson = + String.format( + auditInfoJson, withQuotes(creator), withQuotes(now.toString()), null, null, null, null); + Assertions.assertEquals(expectedJson, serJson); + AuditInfo deserInfo = JsonUtils.objectMapper().readValue(serJson, AuditInfo.class); + Assertions.assertEquals(info, deserInfo); + + // Test with optional fields + AuditInfo info1 = + new AuditInfo.Builder() + .withCreator(creator) + .withCreateTime(now) + .withLastModifier(modifier) + .withLastModifiedTime(now) + .build(); + + String serJson1 = JsonUtils.objectMapper().writeValueAsString(info1); + String expectedJson1 = + String.format( + auditInfoJson, + withQuotes(creator), + withQuotes(now.toString()), + withQuotes(modifier), + withQuotes(now.toString()), + null, + null); + Assertions.assertEquals(expectedJson1, serJson1); + AuditInfo deserInfo1 = JsonUtils.objectMapper().readValue(serJson1, AuditInfo.class); + Assertions.assertEquals(info1, deserInfo1); + + AuditInfo info2 = + new AuditInfo.Builder() + .withCreator(creator) + .withCreateTime(now) + .withLastModifier(modifier) + .withLastModifiedTime(now) + .withLastAccessUser(accessor) + .withLastAccessTime(now) + .build(); + + String serJson2 = JsonUtils.objectMapper().writeValueAsString(info2); + String expectedJson2 = + String.format( + auditInfoJson, + withQuotes(creator), + withQuotes(now.toString()), + withQuotes(modifier), + withQuotes(now.toString()), + withQuotes(accessor), + withQuotes(now.toString())); + Assertions.assertEquals(expectedJson2, serJson2); + AuditInfo deserInfo2 = JsonUtils.objectMapper().readValue(serJson2, AuditInfo.class); + Assertions.assertEquals(info2, deserInfo2); + } + + @Test + public void testTenantSerDe() throws Exception { + Integer id = 1; + String name = "tenant"; + String comment = "comment"; + String creator = "creator"; + Instant now = Instant.now(); + AuditInfo info = new AuditInfo.Builder().withCreator(creator).withCreateTime(now).build(); + SchemaVersion version = SchemaVersion.V_0_1; + + // Test with required fields + Tenant tenant = + new Tenant.Builder() + .withId(id) + .withName(name) + .withComment(comment) + .withAuditInfo(info) + .withVersion(version) + .build(); + + String serJson = JsonUtils.objectMapper().writeValueAsString(tenant); + String expectedJson = + String.format( + tenantJson, + id, + withQuotes(name), + withQuotes(comment), + String.format( + auditInfoJson, + withQuotes(creator), + withQuotes(now.toString()), + null, + null, + null, + null), + version.getMajorVersion(), + version.getMinorVersion()); + Assertions.assertEquals(expectedJson, serJson); + Tenant deserTenant = JsonUtils.objectMapper().readValue(serJson, Tenant.class); + Assertions.assertEquals(tenant, deserTenant); + + // Test with optional fields + Tenant tenant1 = + new Tenant.Builder() + .withId(id) + .withName(name) + .withAuditInfo(info) + .withVersion(version) + .build(); + + String serJson1 = JsonUtils.objectMapper().writeValueAsString(tenant1); + String expectedJson1 = + String.format( + tenantJson, + id, + withQuotes(name), + null, + String.format( + auditInfoJson, + withQuotes(creator), + withQuotes(now.toString()), + null, + null, + null, + null), + version.getMajorVersion(), + version.getMinorVersion()); + Assertions.assertEquals(expectedJson1, serJson1); + Tenant deserTenant1 = JsonUtils.objectMapper().readValue(serJson1, Tenant.class); + Assertions.assertEquals(tenant1, deserTenant1); + } + + @Test + public void testLakehouseSerDe() throws Exception { + Long id = 1L; + String name = "lakehouse"; + String comment = "comment"; + Map properties = ImmutableMap.of("k1", "v1", "k2", "v2"); + String creator = "creator"; + Instant now = Instant.now(); + AuditInfo info = new AuditInfo.Builder().withCreator(creator).withCreateTime(now).build(); + + // Test with required fields + Lakehouse lakehouse = + new Lakehouse.Builder() + .withId(id) + .withName(name) + .withComment(comment) + .withProperties(properties) + .withAuditInfo(info) + .build(); + + String serJson = JsonUtils.objectMapper().writeValueAsString(lakehouse); + String expectedJson = + String.format( + lakehouseJson, + id, + withQuotes(name), + withQuotes(comment), + JsonUtils.objectMapper().writeValueAsString(properties), + String.format( + auditInfoJson, + withQuotes(creator), + withQuotes(now.toString()), + null, + null, + null, + null)); + Assertions.assertEquals(expectedJson, serJson); + Lakehouse deserLakehouse = JsonUtils.objectMapper().readValue(serJson, Lakehouse.class); + Assertions.assertEquals(lakehouse, deserLakehouse); + + // Test with optional fields + Lakehouse lakehouse1 = + new Lakehouse.Builder().withId(id).withName(name).withAuditInfo(info).build(); + + String serJson1 = JsonUtils.objectMapper().writeValueAsString(lakehouse1); + String expectedJson1 = + String.format( + lakehouseJson, + id, + withQuotes(name), + null, + null, + String.format( + auditInfoJson, + withQuotes(creator), + withQuotes(now.toString()), + null, + null, + null, + null)); + Assertions.assertEquals(expectedJson1, serJson1); + Lakehouse deserLakehouse1 = JsonUtils.objectMapper().readValue(serJson1, Lakehouse.class); + Assertions.assertEquals(lakehouse1, deserLakehouse1); + } + + @Test + public void testZoneSerDe() throws Exception { + Long id = 1L; + Long lakehouseId = 2L; + String name = "zone"; + String comment = "comment"; + String creator = "creator"; + Instant now = Instant.now(); + AuditInfo info = new AuditInfo.Builder().withCreator(creator).withCreateTime(now).build(); + Map properties = ImmutableMap.of("k1", "v1", "k2", "v2"); + + // Test with required fields + Zone zone = + new Zone.Builder() + .withId(id) + .withLakehouseId(lakehouseId) + .withName(name) + .withComment(comment) + .withProperties(properties) + .withAuditInfo(info) + .build(); + + String serJson = JsonUtils.objectMapper().writeValueAsString(zone); + String expectedJson = + String.format( + zoneJson, + id, + lakehouseId, + withQuotes(name), + withQuotes(comment), + JsonUtils.objectMapper().writeValueAsString(properties), + String.format( + auditInfoJson, + withQuotes(creator), + withQuotes(now.toString()), + null, + null, + null, + null)); + Assertions.assertEquals(expectedJson, serJson); + Zone deserZone = JsonUtils.objectMapper().readValue(serJson, Zone.class); + Assertions.assertEquals(zone, deserZone); + + // Test with optional fields + Zone zone1 = + new Zone.Builder() + .withId(id) + .withLakehouseId(lakehouseId) + .withName(name) + .withAuditInfo(info) + .build(); + + String serJson1 = JsonUtils.objectMapper().writeValueAsString(zone1); + String expectedJson1 = + String.format( + zoneJson, + id, + lakehouseId, + withQuotes(name), + null, + null, + String.format( + auditInfoJson, + withQuotes(creator), + withQuotes(now.toString()), + null, + null, + null, + null)); + Assertions.assertEquals(expectedJson1, serJson1); + Zone deserZone1 = JsonUtils.objectMapper().readValue(serJson1, Zone.class); + Assertions.assertEquals(zone1, deserZone1); + } + + @Test + public void testColumnSerDe() throws Exception { + Integer id = 1; + Long entityId = 1L; + Long entitySnapshotId = 2L; + String name = "column"; + io.substrait.type.Type type = TypeCreator.NULLABLE.I8; + String comment = "comment"; + Integer position = 1; + String creator = "creator"; + Instant now = Instant.now(); + AuditInfo info = new AuditInfo.Builder().withCreator(creator).withCreateTime(now).build(); + + // Test required fields + Column column = + new Column.Builder() + .withId(id) + .withEntityId(entityId) + .withEntitySnapshotId(entitySnapshotId) + .withName(name) + .withType(type) + .withComment(comment) + .withPosition(position) + .withAuditInfo(info) + .build(); + + String serJson = JsonUtils.objectMapper().writeValueAsString(column); + String expectedJson = + String.format( + columnJson, + id, + entityId, + entitySnapshotId, + withQuotes(name), + withQuotes(type.accept(new StringTypeVisitor())), + withQuotes(comment), + position, + String.format( + auditInfoJson, + withQuotes(creator), + withQuotes(now.toString()), + null, + null, + null, + null)); + Assertions.assertEquals(expectedJson, serJson); + Column deserColumn = JsonUtils.objectMapper().readValue(serJson, Column.class); + Assertions.assertEquals(column, deserColumn); + } + + @Test + public void testTableSerDe() throws Exception { + Integer columnId = 1; + Long tableId = 1L; + Long tableSnapshotId = 2L; + String name = "column"; + io.substrait.type.Type type = TypeCreator.NULLABLE.I8; + String comment = "comment"; + Integer position = 1; + String creator = "creator"; + Instant now = Instant.now(); + AuditInfo info = new AuditInfo.Builder().withCreator(creator).withCreateTime(now).build(); + + Long zoneId = 2L; + String tableName = "table"; + String tableComment = "comment"; + Table.TableType tableType = Table.TableType.VIRTUAL; + Map properties = ImmutableMap.of("k1", "v1", "k2", "v2"); + + Integer connectionId = 1; + List identifiers = ImmutableList.of("db1", "tabl1"); + hasExtraInfo.ExtraInfo extraInfo = new VirtualTableInfo(connectionId, identifiers); + + Column column = + new Column.Builder() + .withId(columnId) + .withEntityId(tableId) + .withEntitySnapshotId(tableSnapshotId) + .withName(name) + .withType(type) + .withComment(comment) + .withPosition(position) + .withAuditInfo(info) + .build(); + + Table table = + new Table.Builder() + .withId(tableId) + .withZoneId(zoneId) + .withName(tableName) + .withComment(tableComment) + .withSnapshotId(tableSnapshotId) + .withProperties(properties) + .withExtraInfo(extraInfo) + .withAuditInfo(info) + .withType(tableType) + .withColumns(ImmutableList.of(column)) + .build(); + + String serJson = JsonUtils.objectMapper().writeValueAsString(table); + String expectedJson = + String.format( + tableJson, + tableId, + zoneId, + withQuotes(tableName), + withQuotes(tableComment), + withQuotes(tableType.getTypeStr()), + tableSnapshotId, + JsonUtils.objectMapper().writeValueAsString(properties), + String.format( + auditInfoJson, + withQuotes(creator), + withQuotes(now.toString()), + null, + null, + null, + null), + withQuotes(tableType.getTypeStr()), + connectionId, + JsonUtils.objectMapper().writeValueAsString(identifiers), + String.format( + columnJson, + columnId, + tableId, + tableSnapshotId, + withQuotes(name), + withQuotes(type.accept(new StringTypeVisitor())), + withQuotes(comment), + position, + String.format( + auditInfoJson, + withQuotes(creator), + withQuotes(now.toString()), + null, + null, + null, + null))); + Assertions.assertEquals(expectedJson, serJson); + + Table deserTable = JsonUtils.objectMapper().readValue(serJson, Table.class); + Assertions.assertEquals(table, deserTable); + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3e014185f3b..4036cbf9361 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,7 @@ substrait-java-core = { group = "io.substrait", name = "core", version.ref = "su jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jackson" } jackson-annotations = { group = "com.fasterxml.jackson.core", name = "jackson-annotations", version.ref = "jackson" } jackson-datatype-jdk8 = { group = "com.fasterxml.jackson.datatype", name = "jackson-datatype-jdk8", version.ref = "jackson" } +jackson-datatype-jsr310 = { group = "com.fasterxml.jackson.datatype", name = "jackson-datatype-jsr310", version.ref = "jackson" } guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } lombok = { group = "org.projectlombok", name = "lombok", version.ref = "lombok" } junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" }