You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by bl...@apache.org on 2022/06/29 14:38:00 UTC
[iceberg] branch master updated: Core: Test serialization of LoadTableResponse (#5118)
This is an automated email from the ASF dual-hosted git repository.
blue pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iceberg.git
The following commit(s) were added to refs/heads/master by this push:
new 878022bb6d Core: Test serialization of LoadTableResponse (#5118)
878022bb6d is described below
commit 878022bb6daa86dfcba576f4ed67f4d46473d497
Author: Kyle Bendickson <kj...@gmail.com>
AuthorDate: Wed Jun 29 07:37:52 2022 -0700
Core: Test serialization of LoadTableResponse (#5118)
---
.../iceberg/rest/responses/LoadTableResponse.java | 1 +
.../rest/responses/TestLoadTableResponse.java | 220 +++++++++++++++++++++
open-api/rest-catalog-open-api.yaml | 13 +-
3 files changed, 228 insertions(+), 6 deletions(-)
diff --git a/core/src/main/java/org/apache/iceberg/rest/responses/LoadTableResponse.java b/core/src/main/java/org/apache/iceberg/rest/responses/LoadTableResponse.java
index d96db06059..01cd84bba8 100644
--- a/core/src/main/java/org/apache/iceberg/rest/responses/LoadTableResponse.java
+++ b/core/src/main/java/org/apache/iceberg/rest/responses/LoadTableResponse.java
@@ -53,6 +53,7 @@ public class LoadTableResponse implements RESTResponse {
@Override
public void validate() {
+ Preconditions.checkNotNull(metadata, "Invalid metadata: null");
}
public String metadataLocation() {
diff --git a/core/src/test/java/org/apache/iceberg/rest/responses/TestLoadTableResponse.java b/core/src/test/java/org/apache/iceberg/rest/responses/TestLoadTableResponse.java
new file mode 100644
index 0000000000..34ae22f10e
--- /dev/null
+++ b/core/src/test/java/org/apache/iceberg/rest/responses/TestLoadTableResponse.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iceberg.rest.responses;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import org.apache.iceberg.AssertHelpers;
+import org.apache.iceberg.NullOrder;
+import org.apache.iceberg.PartitionSpec;
+import org.apache.iceberg.Schema;
+import org.apache.iceberg.Snapshot;
+import org.apache.iceberg.SortOrder;
+import org.apache.iceberg.TableMetadata;
+import org.apache.iceberg.TableMetadataParser;
+import org.apache.iceberg.expressions.Expressions;
+import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
+import org.apache.iceberg.relocated.com.google.common.collect.Lists;
+import org.apache.iceberg.rest.RequestResponseTestBase;
+import org.apache.iceberg.types.Types;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.iceberg.TestHelpers.assertSameSchemaList;
+
+public class TestLoadTableResponse extends RequestResponseTestBase<LoadTableResponse> {
+
+ private static final String TEST_METADATA_LOCATION = "s3://bucket/test/location/metadata/v1.metadata.json";
+
+ private static final String TEST_TABLE_LOCATION = "s3://bucket/test/location";
+
+ private static final Schema SCHEMA_7 = new Schema(7,
+ Types.NestedField.required(1, "x", Types.LongType.get()),
+ Types.NestedField.required(2, "y", Types.LongType.get(), "comment"),
+ Types.NestedField.required(3, "z", Types.LongType.get())
+ );
+
+ private static final PartitionSpec SPEC_5 = PartitionSpec.builderFor(SCHEMA_7).withSpecId(5).build();
+
+ private static final SortOrder SORT_ORDER_3 = SortOrder.builderFor(SCHEMA_7)
+ .withOrderId(3)
+ .asc("y", NullOrder.NULLS_FIRST)
+ .desc(Expressions.bucket("z", 4), NullOrder.NULLS_LAST)
+ .build();
+
+ private static final Map<String, String> TABLE_PROPS = ImmutableMap.of(
+ "format-version", "1",
+ "owner", "hank");
+
+ private static final Map<String, String> CONFIG = ImmutableMap.of("foo", "bar");
+
+ @Override
+ public String[] allFieldsFromSpec() {
+ return new String[] { "metadata-location", "metadata", "config" };
+ }
+
+ @Override
+ public LoadTableResponse createExampleInstance() {
+ TableMetadata metadata =
+ TableMetadata
+ .buildFrom(
+ TableMetadata.newTableMetadata(SCHEMA_7, SPEC_5, SORT_ORDER_3, TEST_TABLE_LOCATION, TABLE_PROPS))
+ .withMetadataLocation(TEST_METADATA_LOCATION)
+ .build();
+
+ return LoadTableResponse.builder()
+ .withTableMetadata(metadata)
+ .addAllConfig(CONFIG)
+ .build();
+ }
+
+ @Override
+ public LoadTableResponse deserialize(String json) throws JsonProcessingException {
+ LoadTableResponse resp = mapper().readValue(json, LoadTableResponse.class);
+ resp.validate();
+ return resp;
+ }
+
+ @Test
+ public void testFailures() {
+ AssertHelpers.assertThrows(
+ "Table metadata should be required",
+ NullPointerException.class,
+ "Invalid metadata: null",
+ () -> LoadTableResponse.builder().build());
+ }
+
+ @Test
+ public void testRoundTripSerdeWithV1TableMetadata() throws Exception {
+ String tableMetadataJson = readTableMetadataInputFile("TableMetadataV1Valid.json");
+ TableMetadata v1Metadata = TableMetadataParser.fromJson(null, TEST_METADATA_LOCATION, tableMetadataJson);
+ // Convert the TableMetadata JSON from the file to an object and then back to JSON so that missing fields
+ // are filled in with their default values.
+ String json = String.format(
+ "{\"metadata-location\":\"%s\",\"metadata\":%s,\"config\":{\"foo\":\"bar\"}}",
+ TEST_METADATA_LOCATION, TableMetadataParser.toJson(v1Metadata));
+ LoadTableResponse resp = LoadTableResponse.builder()
+ .withTableMetadata(v1Metadata)
+ .addAllConfig(CONFIG)
+ .build();
+ assertRoundTripSerializesEquallyFrom(json, resp);
+ }
+
+ @Test
+ public void testRoundTripSerdeWithV2TableMetadata() throws Exception {
+ String tableMetadataJson = readTableMetadataInputFile("TableMetadataV2Valid.json");
+ TableMetadata v2Metadata = TableMetadataParser.fromJson(null, TEST_METADATA_LOCATION, tableMetadataJson);
+ // Convert the TableMetadata JSON from the file to an object and then back to JSON so that missing fields
+ // are filled in with their default values.
+ String json = String.format(
+ "{\"metadata-location\":\"%s\",\"metadata\":%s,\"config\":{\"foo\":\"bar\"}}",
+ TEST_METADATA_LOCATION, TableMetadataParser.toJson(v2Metadata));
+ LoadTableResponse resp = LoadTableResponse.builder()
+ .withTableMetadata(v2Metadata)
+ .addAllConfig(CONFIG)
+ .build();
+ assertRoundTripSerializesEquallyFrom(json, resp);
+ }
+
+ @Test
+ public void testCanDeserializeWithoutDefaultValues() throws Exception {
+ String metadataJson = readTableMetadataInputFile("TableMetadataV1Valid.json");
+ // `config` is missing in the JSON
+ String json = String.format("{\"metadata-location\":\"%s\",\"metadata\":%s}", TEST_METADATA_LOCATION, metadataJson);
+ TableMetadata metadata = TableMetadataParser.fromJson(null, TEST_METADATA_LOCATION, metadataJson);
+ LoadTableResponse actual = deserialize(json);
+ LoadTableResponse expected = LoadTableResponse.builder()
+ .withTableMetadata(metadata)
+ .build();
+ assertEquals(actual, expected);
+ Assert.assertEquals("Deserialized JSON with missing fields should have the default values", ImmutableMap.of(),
+ actual.config());
+ }
+
+ @Override
+ public void assertEquals(LoadTableResponse actual, LoadTableResponse expected) {
+ Assert.assertEquals("Should have the same configuration", expected.config(), actual.config());
+ assertEqualTableMetadata(actual.tableMetadata(), expected.tableMetadata());
+ Assert.assertEquals("Should have the same metadata location",
+ expected.metadataLocation(), actual.metadataLocation());
+ }
+
+ private void assertEqualTableMetadata(TableMetadata actual, TableMetadata expected) {
+ Assert.assertEquals("Format version should match",
+ expected.formatVersion(), actual.formatVersion());
+ Assert.assertEquals("Table UUID should match",
+ expected.uuid(), actual.uuid());
+ Assert.assertEquals("Table location should match",
+ expected.location(), actual.location());
+ Assert.assertEquals("Last column id", expected.lastColumnId(), actual.lastColumnId());
+ Assert.assertEquals("Schema should match", expected.schema().asStruct(), actual.schema().asStruct());
+ assertSameSchemaList(expected.schemas(), actual.schemas());
+ Assert.assertEquals("Current schema id should match", expected.currentSchemaId(), actual.currentSchemaId());
+ Assert.assertEquals("Schema should match", expected.schema().asStruct(), actual.schema().asStruct());
+ Assert.assertEquals("Last sequence number should match",
+ expected.lastSequenceNumber(), actual.lastSequenceNumber());
+ Assert.assertEquals("Partition spec should match",
+ expected.spec().toString(), actual.spec().toString());
+ Assert.assertEquals("Default spec ID should match",
+ expected.defaultSpecId(), actual.defaultSpecId());
+ Assert.assertEquals("PartitionSpec map should match",
+ expected.specs(), actual.specs());
+ Assert.assertEquals("Default Sort ID should match",
+ expected.defaultSortOrderId(), actual.defaultSortOrderId());
+ Assert.assertEquals("Sort order should match",
+ expected.sortOrder(), actual.sortOrder());
+ Assert.assertEquals("Sort order map should match",
+ expected.sortOrders(), actual.sortOrders());
+ Assert.assertEquals("Properties should match",
+ expected.properties(), actual.properties());
+ Assert.assertEquals("Snapshots should match",
+ Lists.transform(expected.snapshots(), Snapshot::snapshotId),
+ Lists.transform(actual.snapshots(), Snapshot::snapshotId));
+ Assert.assertEquals("History should match", expected.snapshotLog(), actual.snapshotLog());
+ Snapshot expectedCurrentSnapshot = expected.currentSnapshot();
+ Snapshot actualCurrentSnapshot = actual.currentSnapshot();
+ Assert.assertTrue("Both expected and actual current snapshot should either be null or non-null",
+ (expectedCurrentSnapshot != null && actualCurrentSnapshot != null) ||
+ (expectedCurrentSnapshot == null && actualCurrentSnapshot == null));
+ if (expectedCurrentSnapshot != null) {
+ Assert.assertEquals("Current snapshot ID should match",
+ expected.currentSnapshot().snapshotId(), actual.currentSnapshot().snapshotId());
+ Assert.assertEquals("Parent snapshot ID should match",
+ expected.currentSnapshot().parentId(), actual.currentSnapshot().parentId());
+ Assert.assertEquals("Schema ID for current snapshot should match",
+ expected.currentSnapshot().schemaId(), actual.currentSnapshot().schemaId());
+ }
+ Assert.assertEquals("Metadata file location should match",
+ expected.metadataFileLocation(), actual.metadataFileLocation());
+ Assert.assertEquals("Last column id should match", expected.lastColumnId(), actual.lastColumnId());
+ Assert.assertEquals("Schema should match", expected.schema().asStruct(), actual.schema().asStruct());
+ assertSameSchemaList(expected.schemas(), actual.schemas());
+ Assert.assertEquals("Current schema id should match", expected.currentSchemaId(), actual.currentSchemaId());
+ Assert.assertEquals("Refs map should match",
+ expected.refs(), actual.refs());
+ }
+
+ private String readTableMetadataInputFile(String fileName) throws Exception {
+ Path path = Paths.get(getClass().getClassLoader().getResource(fileName).toURI());
+ return String.join("", java.nio.file.Files.readAllLines(path));
+ }
+}
diff --git a/open-api/rest-catalog-open-api.yaml b/open-api/rest-catalog-open-api.yaml
index ffc79f4df3..2ba3af6994 100644
--- a/open-api/rest-catalog-open-api.yaml
+++ b/open-api/rest-catalog-open-api.yaml
@@ -1464,19 +1464,20 @@ components:
LoadTableResult:
description:
Result used when a table is successfully loaded.
-
-
- The table metadata JSON is returned in the `metadata` field. The corresponding file location of table metadata must be returned in the `metadata-location` field. Clients can check whether metadata has changed by comparing metadata locations.
-
-
+
+
+ The table metadata JSON is returned in the `metadata` field. The corresponding file location of table metadata should be returned in the `metadata-location` field, unless the metadata is not yet committed. For example, a create transaction may return metadata that is staged but not committed.
+ Clients can check whether metadata has changed by comparing metadata locations after the table has been created.
+
+
The `config` map returns table-specific configuration for the table's resources, including its HTTP client and FileIO. For example, config may contain a specific FileIO implementation class for the table depending on its underlying storage.
type: object
required:
- - metadata-location
- metadata
properties:
metadata-location:
type: string
+ description: May be null if the table is staged as part of a transaction
metadata:
$ref: '#/components/schemas/TableMetadata'
config: