From 62f3ca334c4bf377b1aaf9b7386bec1bfaddb5cd Mon Sep 17 00:00:00 2001 From: Kyle Bendickson Date: Mon, 2 May 2022 20:02:58 -0700 Subject: [PATCH] Core: Add serialization for AddPartitionSpec, AddSortOrder (#4668) --- .../apache/iceberg/MetadataUpdateParser.java | 103 +++++--- .../iceberg/TestMetadataUpdateParser.java | 230 +++++++++++++++--- 2 files changed, 262 insertions(+), 71 deletions(-) diff --git a/core/src/main/java/org/apache/iceberg/MetadataUpdateParser.java b/core/src/main/java/org/apache/iceberg/MetadataUpdateParser.java index 175feb9fbb3a..7d4fa60afa68 100644 --- a/core/src/main/java/org/apache/iceberg/MetadataUpdateParser.java +++ b/core/src/main/java/org/apache/iceberg/MetadataUpdateParser.java @@ -37,21 +37,21 @@ private MetadataUpdateParser() { private static final String ACTION = "action"; - // action types - private static final String ASSIGN_UUID = "assign-uuid"; - private static final String UPGRADE_FORMAT_VERSION = "upgrade-format-version"; - private static final String ADD_SCHEMA = "add-schema"; - private static final String SET_CURRENT_SCHEMA = "set-current-schema"; - private static final String ADD_PARTITION_SPEC = "add-spec"; - private static final String SET_DEFAULT_PARTITION_SPEC = "set-default-spec"; - private static final String ADD_SORT_ORDER = "add-sort-order"; - private static final String SET_DEFAULT_SORT_ORDER = "set-default-sort-order"; - private static final String ADD_SNAPSHOT = "add-snapshot"; - private static final String REMOVE_SNAPSHOTS = "remove-snapshots"; - private static final String SET_SNAPSHOT_REF = "set-snapshot-ref"; - private static final String SET_PROPERTIES = "set-properties"; - private static final String REMOVE_PROPERTIES = "remove-properties"; - private static final String SET_LOCATION = "set-location"; + // action types - visible for testing + static final String ASSIGN_UUID = "assign-uuid"; + static final String UPGRADE_FORMAT_VERSION = "upgrade-format-version"; + static final String ADD_SCHEMA = "add-schema"; + static final String SET_CURRENT_SCHEMA = "set-current-schema"; + static final String ADD_PARTITION_SPEC = "add-spec"; + static final String SET_DEFAULT_PARTITION_SPEC = "set-default-spec"; + static final String ADD_SORT_ORDER = "add-sort-order"; + static final String SET_DEFAULT_SORT_ORDER = "set-default-sort-order"; + static final String ADD_SNAPSHOT = "add-snapshot"; + static final String REMOVE_SNAPSHOTS = "remove-snapshots"; + static final String SET_SNAPSHOT_REF = "set-snapshot-ref"; + static final String SET_PROPERTIES = "set-properties"; + static final String REMOVE_PROPERTIES = "remove-properties"; + static final String SET_LOCATION = "set-location"; // UpgradeFormatVersion private static final String FORMAT_VERSION = "format-version"; @@ -69,6 +69,9 @@ private MetadataUpdateParser() { // SetDefaultPartitionSpec private static final String SPEC_ID = "spec-id"; + // AddSortOrder + private static final String SORT_ORDER = "sort-order"; + private static final Map, String> ACTIONS = ImmutableMap ., String>builder() .put(MetadataUpdate.AssignUUID.class, ASSIGN_UUID) @@ -116,19 +119,23 @@ public static void toJson(MetadataUpdate metadataUpdate, JsonGenerator generator case ASSIGN_UUID: throw new UnsupportedOperationException("Not Implemented: MetadataUpdate#toJson for AssignUUID"); case UPGRADE_FORMAT_VERSION: - writeAsUpgradeFormatVersion((MetadataUpdate.UpgradeFormatVersion) metadataUpdate, generator); + writeUpgradeFormatVersion((MetadataUpdate.UpgradeFormatVersion) metadataUpdate, generator); break; case ADD_SCHEMA: - writeAsAddSchema((MetadataUpdate.AddSchema) metadataUpdate, generator); + writeAddSchema((MetadataUpdate.AddSchema) metadataUpdate, generator); break; case SET_CURRENT_SCHEMA: - writeAsSetCurrentSchema((MetadataUpdate.SetCurrentSchema) metadataUpdate, generator); + writeSetCurrentSchema((MetadataUpdate.SetCurrentSchema) metadataUpdate, generator); + break; + case ADD_PARTITION_SPEC: + writeAddPartitionSpec((MetadataUpdate.AddPartitionSpec) metadataUpdate, generator); break; case SET_DEFAULT_PARTITION_SPEC: - writeAsSetDefaultPartitionSpec((MetadataUpdate.SetDefaultPartitionSpec) metadataUpdate, generator); + writeSetDefaultPartitionSpec((MetadataUpdate.SetDefaultPartitionSpec) metadataUpdate, generator); break; - case ADD_PARTITION_SPEC: case ADD_SORT_ORDER: + writeAddSortOrder((MetadataUpdate.AddSortOrder) metadataUpdate, generator); + break; case SET_DEFAULT_SORT_ORDER: case ADD_SNAPSHOT: case REMOVE_SNAPSHOTS: @@ -169,15 +176,17 @@ public static MetadataUpdate fromJson(JsonNode jsonNode) { case ASSIGN_UUID: throw new UnsupportedOperationException("Not implemented: AssignUUID"); case UPGRADE_FORMAT_VERSION: - return readAsUpgradeFormatVersion(jsonNode); + return readUpgradeFormatVersion(jsonNode); case ADD_SCHEMA: - return readAsAddSchema(jsonNode); + return readAddSchema(jsonNode); case SET_CURRENT_SCHEMA: - return readAsSetCurrentSchema(jsonNode); - case SET_DEFAULT_PARTITION_SPEC: - return readAsSetDefaultPartitionSpec(jsonNode); + return readSetCurrentSchema(jsonNode); case ADD_PARTITION_SPEC: + return readAddPartitionSpec(jsonNode); + case SET_DEFAULT_PARTITION_SPEC: + return readSetDefaultPartitionSpec(jsonNode); case ADD_SORT_ORDER: + return readAddSortOrder(jsonNode); case SET_DEFAULT_SORT_ORDER: case ADD_SNAPSHOT: case REMOVE_SNAPSHOTS: @@ -192,51 +201,75 @@ public static MetadataUpdate fromJson(JsonNode jsonNode) { } } - private static void writeAsUpgradeFormatVersion(MetadataUpdate.UpgradeFormatVersion update, JsonGenerator gen) + private static void writeUpgradeFormatVersion(MetadataUpdate.UpgradeFormatVersion update, JsonGenerator gen) throws IOException { gen.writeNumberField(FORMAT_VERSION, update.formatVersion()); } - private static void writeAsAddSchema(MetadataUpdate.AddSchema update, JsonGenerator gen) throws IOException { + private static void writeAddSchema(MetadataUpdate.AddSchema update, JsonGenerator gen) throws IOException { gen.writeFieldName(SCHEMA); SchemaParser.toJson(update.schema(), gen); gen.writeNumberField(LAST_COLUMN_ID, update.lastColumnId()); } - private static void writeAsSetCurrentSchema(MetadataUpdate.SetCurrentSchema update, JsonGenerator gen) + private static void writeSetCurrentSchema(MetadataUpdate.SetCurrentSchema update, JsonGenerator gen) throws IOException { gen.writeNumberField(SCHEMA_ID, update.schemaId()); } - private static void writeAsSetDefaultPartitionSpec(MetadataUpdate.SetDefaultPartitionSpec update, JsonGenerator gen) + private static void writeAddPartitionSpec(MetadataUpdate.AddPartitionSpec update, JsonGenerator gen) + throws IOException { + gen.writeFieldName(SPEC); + PartitionSpecParser.toJson(update.spec(), gen); + } + + private static void writeSetDefaultPartitionSpec(MetadataUpdate.SetDefaultPartitionSpec update, JsonGenerator gen) throws IOException { gen.writeNumberField(SPEC_ID, update.specId()); } - private static MetadataUpdate readAsUpgradeFormatVersion(JsonNode node) { + private static void writeAddSortOrder(MetadataUpdate.AddSortOrder update, JsonGenerator gen) + throws IOException { + gen.writeFieldName(SORT_ORDER); + SortOrderParser.toJson(update.sortOrder(), gen); + } + + private static MetadataUpdate readUpgradeFormatVersion(JsonNode node) { int formatVersion = JsonUtil.getInt(FORMAT_VERSION, node); return new MetadataUpdate.UpgradeFormatVersion(formatVersion); } - private static MetadataUpdate readAsAddSchema(JsonNode node) { + private static MetadataUpdate readAddSchema(JsonNode node) { Preconditions.checkArgument(node.hasNonNull(SCHEMA), "Cannot parse missing field: schema"); JsonNode schemaNode = node.get(SCHEMA); - Preconditions.checkArgument(schemaNode.isObject(), - "Invalid type for schema field. Expected object"); Schema schema = SchemaParser.fromJson(schemaNode); int lastColumnId = JsonUtil.getInt(LAST_COLUMN_ID, node); return new MetadataUpdate.AddSchema(schema, lastColumnId); } - private static MetadataUpdate readAsSetCurrentSchema(JsonNode node) { + private static MetadataUpdate readSetCurrentSchema(JsonNode node) { int schemaId = JsonUtil.getInt(SCHEMA_ID, node); return new MetadataUpdate.SetCurrentSchema(schemaId); } - private static MetadataUpdate readAsSetDefaultPartitionSpec(JsonNode node) { + private static MetadataUpdate readAddPartitionSpec(JsonNode node) { + Preconditions.checkArgument(node.hasNonNull(SPEC), "Missing required field: spec"); + JsonNode specNode = node.get(SPEC); + UnboundPartitionSpec spec = PartitionSpecParser.fromJson(specNode); + return new MetadataUpdate.AddPartitionSpec(spec); + } + + private static MetadataUpdate readSetDefaultPartitionSpec(JsonNode node) { int specId = JsonUtil.getInt(SPEC_ID, node); return new MetadataUpdate.SetDefaultPartitionSpec(specId); } + + private static MetadataUpdate readAddSortOrder(JsonNode node) { + Preconditions.checkArgument(node.hasNonNull(SORT_ORDER), "Cannot parse missing field: sort-order"); + JsonNode sortOrderNode = node.get(SORT_ORDER); + UnboundSortOrder sortOrder = SortOrderParser.fromJson(sortOrderNode); + return new MetadataUpdate.AddSortOrder(sortOrder); + } } diff --git a/core/src/test/java/org/apache/iceberg/TestMetadataUpdateParser.java b/core/src/test/java/org/apache/iceberg/TestMetadataUpdateParser.java index 921d18aee229..35e440af66ed 100644 --- a/core/src/test/java/org/apache/iceberg/TestMetadataUpdateParser.java +++ b/core/src/test/java/org/apache/iceberg/TestMetadataUpdateParser.java @@ -20,6 +20,8 @@ package org.apache.iceberg; import java.util.List; +import java.util.Objects; +import java.util.stream.IntStream; import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList; import org.apache.iceberg.types.Types; import org.assertj.core.api.Assertions; @@ -28,22 +30,6 @@ public class TestMetadataUpdateParser { - // MetadataUpdate actions - private static final String ASSIGN_UUID = "assign-uuid"; - private static final String UPGRADE_FORMAT_VERSION = "upgrade-format-version"; - private static final String ADD_SCHEMA = "add-schema"; - private static final String SET_CURRENT_SCHEMA = "set-current-schema"; - private static final String ADD_PARTITION_SPEC = "add-spec"; - private static final String SET_DEFAULT_PARTITION_SPEC = "set-default-spec"; - private static final String ADD_SORT_ORDER = "add-sort-order"; - private static final String SET_DEFAULT_SORT_ORDER = "set-default-sort-order"; - private static final String ADD_SNAPSHOT = "add-snapshot"; - private static final String REMOVE_SNAPSHOTS = "remove-snapshots"; - private static final String SET_SNAPSHOT_REF = "set-snapshot-ref"; - private static final String SET_PROPERTIES = "set-properties"; - private static final String REMOVE_PROPERTIES = "remove-properties"; - private static final String SET_LOCATION = "set-location"; - private static final Schema ID_DATA_SCHEMA = new Schema( Types.NestedField.required(1, "id", Types.IntegerType.get()), Types.NestedField.optional(2, "data", Types.StringType.get())); @@ -67,7 +53,7 @@ public void testMetadataUpdateWithoutActionCannotDeserialize() { @Test public void testUpgradeFormatVersionToJson() { int formatVersion = 2; - String action = UPGRADE_FORMAT_VERSION; + String action = MetadataUpdateParser.UPGRADE_FORMAT_VERSION; String json = "{\"action\":\"upgrade-format-version\",\"format-version\":2}"; MetadataUpdate.UpgradeFormatVersion expected = new MetadataUpdate.UpgradeFormatVersion(formatVersion); assertEquals(action, expected, MetadataUpdateParser.fromJson(json)); @@ -84,7 +70,7 @@ public void testUpgradeFormatVersionFromJson() { @Test public void testAddSchemaFromJson() { - String action = "add-schema"; + String action = MetadataUpdateParser.ADD_SCHEMA; Schema schema = ID_DATA_SCHEMA; int lastColumnId = schema.highestFieldId(); String json = String.format("{\"action\":\"add-schema\",\"schema\":%s,\"last-column-id\":%d}", @@ -106,7 +92,7 @@ public void testAddSchemaToJson() { @Test public void testSetCurrentSchemaFromJson() { - String action = SET_CURRENT_SCHEMA; + String action = MetadataUpdateParser.SET_CURRENT_SCHEMA; int schemaId = 6; String json = String.format("{\"action\":\"%s\",\"schema-id\":%d}", action, schemaId); MetadataUpdate.SetCurrentSchema expected = new MetadataUpdate.SetCurrentSchema(schemaId); @@ -115,7 +101,7 @@ public void testSetCurrentSchemaFromJson() { @Test public void testSetCurrentSchemaToJson() { - String action = SET_CURRENT_SCHEMA; + String action = MetadataUpdateParser.SET_CURRENT_SCHEMA; int schemaId = 6; String expected = String.format("{\"action\":\"%s\",\"schema-id\":%d}", action, schemaId); MetadataUpdate update = new MetadataUpdate.SetCurrentSchema(schemaId); @@ -123,9 +109,101 @@ public void testSetCurrentSchemaToJson() { Assert.assertEquals("Set current schema should convert to the correct JSON value", expected, actual); } + @Test + public void testAddPartitionSpecFromJsonWithFieldId() { + String action = MetadataUpdateParser.ADD_PARTITION_SPEC; + String specString = "{" + + "\"spec-id\":1," + + "\"fields\":[{" + + "\"name\":\"id_bucket\"," + + "\"transform\":\"bucket[8]\"," + + "\"source-id\":1," + + "\"field-id\":1000" + + "},{" + + "\"name\":\"data_bucket\"," + + "\"transform\":\"bucket[16]\"," + + "\"source-id\":2," + + "\"field-id\":1001" + + "}]" + + "}"; + + UnboundPartitionSpec actualSpec = PartitionSpecParser.fromJson(ID_DATA_SCHEMA, specString).toUnbound(); + String json = String.format("{\"action\":\"%s\",\"spec\":%s}", action, PartitionSpecParser.toJson(actualSpec)); + + // Partition spec order declaration needs to match declaration in spec string to be assigned the same field ids. + PartitionSpec expectedSpec = PartitionSpec.builderFor(ID_DATA_SCHEMA) + .bucket("id", 8) + .bucket("data", 16) + .withSpecId(1) + .build(); + MetadataUpdate expected = new MetadataUpdate.AddPartitionSpec(expectedSpec); + assertEquals(action, expected, MetadataUpdateParser.fromJson(json)); + } + + @Test + public void testAddPartitionSpecFromJsonWithoutFieldId() { + // partition field ids are missing in old PartitionSpec, they always auto-increment from 1000 in declared order + String action = MetadataUpdateParser.ADD_PARTITION_SPEC; + String specString = "{" + + "\"spec-id\":1," + + "\"fields\":[{" + + "\"name\":\"id_bucket\"," + + "\"transform\":\"bucket[8]\"," + + "\"source-id\":1" + + "},{" + + "\"name\": \"data_bucket\"," + + "\"transform\":\"bucket[16]\"," + + "\"source-id\":2" + + "}]" + + "}"; + + UnboundPartitionSpec actualSpec = PartitionSpecParser.fromJson(ID_DATA_SCHEMA, specString).toUnbound(); + String json = String.format("{\"action\":\"%s\",\"spec\":%s}", action, PartitionSpecParser.toJson(actualSpec)); + + PartitionSpec expectedSpec = PartitionSpec.builderFor(ID_DATA_SCHEMA) + .bucket("id", 8) + .bucket("data", 16) + .withSpecId(1) + .build(); + MetadataUpdate expected = new MetadataUpdate.AddPartitionSpec(expectedSpec.toUnbound()); + assertEquals(action, expected, MetadataUpdateParser.fromJson(json)); + } + + @Test + public void testAddPartitionSpecToJson() { + String action = MetadataUpdateParser.ADD_PARTITION_SPEC; + String specString = "{" + + "\"spec-id\":1," + + "\"fields\":[{" + + "\"name\":\"id_bucket\"," + + "\"transform\":\"bucket[8]\"," + + "\"source-id\":1," + + "\"field-id\":1000" + + "},{" + + "\"name\":\"data_bucket\"," + + "\"transform\":\"bucket[16]\"," + + "\"source-id\":2," + + "\"field-id\":1001" + + "}]" + + "}"; + + UnboundPartitionSpec actualSpec = PartitionSpecParser.fromJson(ID_DATA_SCHEMA, specString).toUnbound(); + String expected = String.format("{\"action\":\"%s\",\"spec\":%s}", action, PartitionSpecParser.toJson(actualSpec)); + + // Partition spec order declaration needs to match declaration in spec string to be assigned the same field ids. + PartitionSpec expectedSpec = PartitionSpec.builderFor(ID_DATA_SCHEMA) + .bucket("id", 8) + .bucket("data", 16) + .withSpecId(1) + .build(); + MetadataUpdate update = new MetadataUpdate.AddPartitionSpec(expectedSpec); + String actual = MetadataUpdateParser.toJson(update); + Assert.assertEquals("Add partition spec should convert to the correct JSON value", expected, actual); + } + @Test public void testSetDefaultPartitionSpecToJson() { - String action = SET_DEFAULT_PARTITION_SPEC; + String action = MetadataUpdateParser.SET_DEFAULT_PARTITION_SPEC; int specId = 4; String expected = String.format("{\"action\":\"%s\",\"spec-id\":%d}", action, specId); MetadataUpdate update = new MetadataUpdate.SetDefaultPartitionSpec(specId); @@ -135,42 +213,80 @@ public void testSetDefaultPartitionSpecToJson() { @Test public void testSetDefaultPartitionSpecFromJson() { - String action = SET_DEFAULT_PARTITION_SPEC; + String action = MetadataUpdateParser.SET_DEFAULT_PARTITION_SPEC; int specId = 4; String json = String.format("{\"action\":\"%s\",\"spec-id\":%d}", action, specId); MetadataUpdate.SetDefaultPartitionSpec expected = new MetadataUpdate.SetDefaultPartitionSpec(specId); assertEquals(action, expected, MetadataUpdateParser.fromJson(json)); } + @Test + public void testAddSortOrderToJson() { + String action = MetadataUpdateParser.ADD_SORT_ORDER; + UnboundSortOrder sortOrder = SortOrder.builderFor(ID_DATA_SCHEMA) + .withOrderId(3) + .asc("id", NullOrder.NULLS_FIRST) + .desc("data") + .build() + .toUnbound(); + + String expected = String.format("{\"action\":\"%s\",\"sort-order\":%s}", action, SortOrderParser.toJson(sortOrder)); + MetadataUpdate update = new MetadataUpdate.AddSortOrder(sortOrder); + Assert.assertEquals("Add sort order should serialize to the correct JSON value", + expected, MetadataUpdateParser.toJson(update)); + } + + @Test + public void testAddSortOrderFromJson() { + String action = MetadataUpdateParser.ADD_SORT_ORDER; + UnboundSortOrder sortOrder = SortOrder.builderFor(ID_DATA_SCHEMA) + .withOrderId(3) + .asc("id", NullOrder.NULLS_FIRST) + .desc("data") + .build() + .toUnbound(); + + String json = String.format("{\"action\":\"%s\",\"sort-order\":%s}", action, SortOrderParser.toJson(sortOrder)); + MetadataUpdate.AddSortOrder expected = new MetadataUpdate.AddSortOrder(sortOrder); + assertEquals(action, expected, MetadataUpdateParser.fromJson(json)); + } + + public void assertEquals(String action, MetadataUpdate expectedUpdate, MetadataUpdate actualUpdate) { switch (action) { - case ASSIGN_UUID: + case MetadataUpdateParser.ASSIGN_UUID: Assert.fail(String.format("MetadataUpdateParser for %s is not implemented", action)); break; - case UPGRADE_FORMAT_VERSION: + case MetadataUpdateParser.UPGRADE_FORMAT_VERSION: assertEqualsUpgradeFormatVersion((MetadataUpdate.UpgradeFormatVersion) expectedUpdate, (MetadataUpdate.UpgradeFormatVersion) actualUpdate); break; - case ADD_SCHEMA: + case MetadataUpdateParser.ADD_SCHEMA: assertEqualsAddSchema((MetadataUpdate.AddSchema) expectedUpdate, (MetadataUpdate.AddSchema) actualUpdate); break; - case SET_CURRENT_SCHEMA: + case MetadataUpdateParser.SET_CURRENT_SCHEMA: assertEqualsSetCurrentSchema((MetadataUpdate.SetCurrentSchema) expectedUpdate, (MetadataUpdate.SetCurrentSchema) actualUpdate); break; - case SET_DEFAULT_PARTITION_SPEC: + case MetadataUpdateParser.ADD_PARTITION_SPEC: + assertEqualsAddPartitionSpec((MetadataUpdate.AddPartitionSpec) expectedUpdate, + (MetadataUpdate.AddPartitionSpec) actualUpdate); + break; + case MetadataUpdateParser.SET_DEFAULT_PARTITION_SPEC: assertEqualsSetDefaultPartitionSpec((MetadataUpdate.SetDefaultPartitionSpec) expectedUpdate, (MetadataUpdate.SetDefaultPartitionSpec) actualUpdate); break; - case ADD_PARTITION_SPEC: - case ADD_SORT_ORDER: - case SET_DEFAULT_SORT_ORDER: - case ADD_SNAPSHOT: - case REMOVE_SNAPSHOTS: - case SET_SNAPSHOT_REF: - case SET_PROPERTIES: - case REMOVE_PROPERTIES: - case SET_LOCATION: + case MetadataUpdateParser.ADD_SORT_ORDER: + assertEqualsAddSortOrder((MetadataUpdate.AddSortOrder) expectedUpdate, + (MetadataUpdate.AddSortOrder) actualUpdate); + break; + case MetadataUpdateParser.SET_DEFAULT_SORT_ORDER: + case MetadataUpdateParser.ADD_SNAPSHOT: + case MetadataUpdateParser.REMOVE_SNAPSHOTS: + case MetadataUpdateParser.SET_SNAPSHOT_REF: + case MetadataUpdateParser.SET_PROPERTIES: + case MetadataUpdateParser.REMOVE_PROPERTIES: + case MetadataUpdateParser.SET_LOCATION: Assert.fail(String.format("MetadataUpdateParser for %s is not implemented yet", action)); break; default: @@ -197,4 +313,46 @@ private static void assertEqualsSetDefaultPartitionSpec( MetadataUpdate.SetDefaultPartitionSpec expected, MetadataUpdate.SetDefaultPartitionSpec actual) { Assertions.assertThat(actual.specId()).isEqualTo(expected.specId()); } + + private static void assertEqualsAddPartitionSpec( + MetadataUpdate.AddPartitionSpec expected, MetadataUpdate.AddPartitionSpec actual) { + Assert.assertEquals("Unbound partition specs should have the same spec id", + expected.spec().specId(), actual.spec().specId()); + Assert.assertEquals("Unbound partition specs should have the same number of fields", + expected.spec().fields().size(), actual.spec().fields().size()); + + IntStream.range(0, expected.spec().fields().size()) + .forEachOrdered(i -> { + UnboundPartitionSpec.UnboundPartitionField expectedField = expected.spec().fields().get(i); + UnboundPartitionSpec.UnboundPartitionField actualField = actual.spec().fields().get(i); + Assert.assertTrue( + "Fields of the unbound partition spec should be the same", + Objects.equals(expectedField.partitionId(), actualField.partitionId()) && + expectedField.name().equals(actualField.name()) && + Objects.equals(expectedField.transformAsString(), actualField.transformAsString()) && + expectedField.sourceId() == actualField.sourceId()); + }); + } + + private static void assertEqualsAddSortOrder( + MetadataUpdate.AddSortOrder expected, MetadataUpdate.AddSortOrder actual) { + UnboundSortOrder expectedSortOrder = expected.sortOrder(); + UnboundSortOrder actualSortOrder = actual.sortOrder(); + Assert.assertEquals("Order id of the sort order should be the same", + expected.sortOrder().orderId(), actual.sortOrder().orderId()); + + Assert.assertEquals("Sort orders should have the same number of fields", + expected.sortOrder().fields().size(), actual.sortOrder().fields().size()); + + IntStream.range(0, expected.sortOrder().fields().size()) + .forEachOrdered(i -> { + UnboundSortOrder.UnboundSortField expectedField = expected.sortOrder().fields().get(i); + UnboundSortOrder.UnboundSortField actualField = actual.sortOrder().fields().get(i); + Assert.assertTrue("Fields of the sort order should be the same", + expectedField.sourceId() == actualField.sourceId() && + expectedField.nullOrder().equals(actualField.nullOrder()) && + expectedField.direction().equals(actualField.direction()) && + Objects.equals(expectedField.transformAsString(), actualField.transformAsString())); + }); + } }