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 f3826bd9b1 Core: Support registerTable with REST session catalog
(#6512)
f3826bd9b1 is described below
commit f3826bd9b1111d08262109df741ca8bf5aa0b697
Author: Vikash Kumar <[email protected]>
AuthorDate: Wed Jul 5 02:36:09 2023 +0530
Core: Support registerTable with REST session catalog (#6512)
---
.../org/apache/iceberg/rest/CatalogHandlers.java | 16 ++++
.../org/apache/iceberg/rest/RESTSerializers.java | 28 ++++++-
.../apache/iceberg/rest/RESTSessionCatalog.java | 37 +++++++++-
.../org/apache/iceberg/rest/ResourcePaths.java | 4 +
.../rest/requests/RegisterTableRequest.java | 35 +++++++++
.../rest/requests/RegisterTableRequestParser.java | 69 +++++++++++++++++
.../org/apache/iceberg/catalog/CatalogTests.java | 86 ++++++++++++++++++++++
.../org/apache/iceberg/jdbc/TestJdbcCatalog.java | 33 ---------
.../apache/iceberg/rest/RESTCatalogAdapter.java | 14 ++++
.../org/apache/iceberg/rest/TestResourcePaths.java | 8 ++
.../requests/TestRegisterTableRequestParser.java | 80 ++++++++++++++++++++
open-api/rest-catalog-open-api.py | 5 ++
open-api/rest-catalog-open-api.yaml | 63 ++++++++++++++++
13 files changed, 443 insertions(+), 35 deletions(-)
diff --git a/core/src/main/java/org/apache/iceberg/rest/CatalogHandlers.java
b/core/src/main/java/org/apache/iceberg/rest/CatalogHandlers.java
index 6fddb08757..1e0ef66027 100644
--- a/core/src/main/java/org/apache/iceberg/rest/CatalogHandlers.java
+++ b/core/src/main/java/org/apache/iceberg/rest/CatalogHandlers.java
@@ -54,6 +54,7 @@ import
org.apache.iceberg.relocated.com.google.common.collect.Maps;
import org.apache.iceberg.relocated.com.google.common.collect.Sets;
import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
import org.apache.iceberg.rest.requests.CreateTableRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequest;
import org.apache.iceberg.rest.requests.RenameTableRequest;
import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
import org.apache.iceberg.rest.requests.UpdateTableRequest;
@@ -223,6 +224,21 @@ public class CatalogHandlers {
throw new IllegalStateException("Cannot wrap catalog that does not produce
BaseTable");
}
+ public static LoadTableResponse registerTable(
+ Catalog catalog, Namespace namespace, RegisterTableRequest request) {
+ request.validate();
+
+ TableIdentifier identifier = TableIdentifier.of(namespace, request.name());
+ Table table = catalog.registerTable(identifier,
request.metadataLocation());
+ if (table instanceof BaseTable) {
+ return LoadTableResponse.builder()
+ .withTableMetadata(((BaseTable) table).operations().current())
+ .build();
+ }
+
+ throw new IllegalStateException("Cannot wrap catalog that does not produce
BaseTable");
+ }
+
public static void dropTable(Catalog catalog, TableIdentifier ident) {
boolean dropped = catalog.dropTable(ident, false);
if (!dropped) {
diff --git a/core/src/main/java/org/apache/iceberg/rest/RESTSerializers.java
b/core/src/main/java/org/apache/iceberg/rest/RESTSerializers.java
index 2bf95fea03..06f2de32df 100644
--- a/core/src/main/java/org/apache/iceberg/rest/RESTSerializers.java
+++ b/core/src/main/java/org/apache/iceberg/rest/RESTSerializers.java
@@ -44,7 +44,10 @@ import org.apache.iceberg.catalog.TableIdentifierParser;
import org.apache.iceberg.rest.auth.OAuth2Util;
import org.apache.iceberg.rest.requests.CommitTransactionRequest;
import org.apache.iceberg.rest.requests.CommitTransactionRequestParser;
+import org.apache.iceberg.rest.requests.ImmutableRegisterTableRequest;
import org.apache.iceberg.rest.requests.ImmutableReportMetricsRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequestParser;
import org.apache.iceberg.rest.requests.ReportMetricsRequest;
import org.apache.iceberg.rest.requests.ReportMetricsRequestParser;
import org.apache.iceberg.rest.requests.UpdateRequirementParser;
@@ -93,7 +96,12 @@ public class RESTSerializers {
.addSerializer(CommitTransactionRequest.class, new
CommitTransactionRequestSerializer())
.addDeserializer(CommitTransactionRequest.class, new
CommitTransactionRequestDeserializer())
.addSerializer(UpdateTableRequest.class, new
UpdateTableRequestSerializer())
- .addDeserializer(UpdateTableRequest.class, new
UpdateTableRequestDeserializer());
+ .addDeserializer(UpdateTableRequest.class, new
UpdateTableRequestDeserializer())
+ .addSerializer(RegisterTableRequest.class, new
RegisterTableRequestSerializer<>())
+ .addDeserializer(RegisterTableRequest.class, new
RegisterTableRequestDeserializer<>())
+ .addSerializer(ImmutableRegisterTableRequest.class, new
RegisterTableRequestSerializer<>())
+ .addDeserializer(
+ ImmutableRegisterTableRequest.class, new
RegisterTableRequestDeserializer<>());
mapper.registerModule(module);
}
@@ -353,4 +361,22 @@ public class RESTSerializers {
return UpdateTableRequestParser.fromJson(jsonNode);
}
}
+
+ public static class RegisterTableRequestSerializer<T extends
RegisterTableRequest>
+ extends JsonSerializer<T> {
+ @Override
+ public void serialize(T request, JsonGenerator gen, SerializerProvider
serializers)
+ throws IOException {
+ RegisterTableRequestParser.toJson(request, gen);
+ }
+ }
+
+ public static class RegisterTableRequestDeserializer<T extends
RegisterTableRequest>
+ extends JsonDeserializer<T> {
+ @Override
+ public T deserialize(JsonParser p, DeserializationContext context) throws
IOException {
+ JsonNode jsonNode = p.getCodec().readTree(p);
+ return (T) RegisterTableRequestParser.fromJson(jsonNode);
+ }
+ }
}
diff --git a/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
b/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
index 884e35dd62..72547eec34 100644
--- a/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
+++ b/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
@@ -71,6 +71,8 @@ import org.apache.iceberg.rest.auth.OAuth2Util.AuthSession;
import org.apache.iceberg.rest.requests.CommitTransactionRequest;
import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
import org.apache.iceberg.rest.requests.CreateTableRequest;
+import org.apache.iceberg.rest.requests.ImmutableRegisterTableRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequest;
import org.apache.iceberg.rest.requests.RenameTableRequest;
import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
import org.apache.iceberg.rest.requests.UpdateTableRequest;
@@ -404,7 +406,40 @@ public class RESTSessionCatalog extends BaseSessionCatalog
@Override
public Table registerTable(
SessionContext context, TableIdentifier ident, String
metadataFileLocation) {
- throw new UnsupportedOperationException("Register table is not supported");
+ checkIdentifierIsValid(ident);
+
+ Preconditions.checkArgument(
+ metadataFileLocation != null && !metadataFileLocation.isEmpty(),
+ "Invalid metadata file location: %s",
+ metadataFileLocation);
+
+ RegisterTableRequest request =
+ ImmutableRegisterTableRequest.builder()
+ .name(ident.name())
+ .metadataLocation(metadataFileLocation)
+ .build();
+
+ LoadTableResponse response =
+ client.post(
+ paths.register(ident.namespace()),
+ request,
+ LoadTableResponse.class,
+ headers(context),
+ ErrorHandlers.tableErrorHandler());
+
+ AuthSession session = tableSession(response.config(), session(context));
+ RESTTableOperations ops =
+ new RESTTableOperations(
+ client,
+ paths.table(ident),
+ session::headers,
+ tableFileIO(context, response.config()),
+ response.tableMetadata());
+
+ trackFileIO(ops);
+
+ return new BaseTable(
+ ops, fullTableName(ident), metricsReporter(paths.metrics(ident),
session::headers));
}
@Override
diff --git a/core/src/main/java/org/apache/iceberg/rest/ResourcePaths.java
b/core/src/main/java/org/apache/iceberg/rest/ResourcePaths.java
index 7e4f9e0c98..b5b974f14c 100644
--- a/core/src/main/java/org/apache/iceberg/rest/ResourcePaths.java
+++ b/core/src/main/java/org/apache/iceberg/rest/ResourcePaths.java
@@ -71,6 +71,10 @@ public class ResourcePaths {
RESTUtil.encodeString(ident.name()));
}
+ public String register(Namespace ns) {
+ return SLASH.join("v1", prefix, "namespaces",
RESTUtil.encodeNamespace(ns), "register");
+ }
+
public String rename() {
return SLASH.join("v1", prefix, "tables", "rename");
}
diff --git
a/core/src/main/java/org/apache/iceberg/rest/requests/RegisterTableRequest.java
b/core/src/main/java/org/apache/iceberg/rest/requests/RegisterTableRequest.java
new file mode 100644
index 0000000000..33b37dae24
--- /dev/null
+++
b/core/src/main/java/org/apache/iceberg/rest/requests/RegisterTableRequest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.requests;
+
+import org.apache.iceberg.rest.RESTRequest;
+import org.immutables.value.Value;
+
[email protected]
+public interface RegisterTableRequest extends RESTRequest {
+
+ String name();
+
+ String metadataLocation();
+
+ @Override
+ default void validate() {
+ // nothing to validate as it's not possible to create an invalid instance
+ }
+}
diff --git
a/core/src/main/java/org/apache/iceberg/rest/requests/RegisterTableRequestParser.java
b/core/src/main/java/org/apache/iceberg/rest/requests/RegisterTableRequestParser.java
new file mode 100644
index 0000000000..961b6c185b
--- /dev/null
+++
b/core/src/main/java/org/apache/iceberg/rest/requests/RegisterTableRequestParser.java
@@ -0,0 +1,69 @@
+/*
+ * 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.requests;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import java.io.IOException;
+import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
+import org.apache.iceberg.util.JsonUtil;
+
+public class RegisterTableRequestParser {
+
+ private static final String NAME = "name";
+ private static final String METADATA_LOCATION = "metadata-location";
+
+ private RegisterTableRequestParser() {}
+
+ public static String toJson(RegisterTableRequest request) {
+ return toJson(request, false);
+ }
+
+ public static String toJson(RegisterTableRequest request, boolean pretty) {
+ return JsonUtil.generate(gen -> toJson(request, gen), pretty);
+ }
+
+ public static void toJson(RegisterTableRequest request, JsonGenerator gen)
throws IOException {
+ Preconditions.checkArgument(null != request, "Invalid register table
request: null");
+
+ gen.writeStartObject();
+
+ gen.writeStringField(NAME, request.name());
+ gen.writeStringField(METADATA_LOCATION, request.metadataLocation());
+
+ gen.writeEndObject();
+ }
+
+ public static RegisterTableRequest fromJson(String json) {
+ return JsonUtil.parse(json, RegisterTableRequestParser::fromJson);
+ }
+
+ public static RegisterTableRequest fromJson(JsonNode json) {
+ Preconditions.checkArgument(
+ null != json, "Cannot parse register table request from null object");
+
+ String name = JsonUtil.getString(NAME, json);
+ String metadataLocation = JsonUtil.getString(METADATA_LOCATION, json);
+
+ return ImmutableRegisterTableRequest.builder()
+ .name(name)
+ .metadataLocation(metadataLocation)
+ .build();
+ }
+}
diff --git a/core/src/test/java/org/apache/iceberg/catalog/CatalogTests.java
b/core/src/test/java/org/apache/iceberg/catalog/CatalogTests.java
index 15259d979e..5641dce89f 100644
--- a/core/src/test/java/org/apache/iceberg/catalog/CatalogTests.java
+++ b/core/src/test/java/org/apache/iceberg/catalog/CatalogTests.java
@@ -42,6 +42,7 @@ import org.apache.iceberg.SortOrder;
import org.apache.iceberg.Table;
import org.apache.iceberg.TableOperations;
import org.apache.iceberg.TableProperties;
+import org.apache.iceberg.TestHelpers;
import org.apache.iceberg.Transaction;
import org.apache.iceberg.UpdatePartitionSpec;
import org.apache.iceberg.UpdateSchema;
@@ -2615,6 +2616,91 @@ public abstract class CatalogTests<C extends Catalog &
SupportsNamespaces> {
.hasMessageContaining("Namespace does not exist: non-existing");
}
+ @Test
+ public void testRegisterTable() {
+ C catalog = catalog();
+
+ if (requiresNamespaceCreate()) {
+ catalog.createNamespace(TABLE.namespace());
+ }
+
+ Map<String, String> properties =
+ ImmutableMap.of("user", "someone", "created-at",
"2023-01-15T00:00:01");
+ Table originalTable =
+ catalog
+ .buildTable(TABLE, SCHEMA)
+ .withPartitionSpec(SPEC)
+ .withSortOrder(WRITE_ORDER)
+ .withProperties(properties)
+ .create();
+
+ originalTable.newFastAppend().appendFile(FILE_A).commit();
+ originalTable.newFastAppend().appendFile(FILE_B).commit();
+ originalTable.newDelete().deleteFile(FILE_A).commit();
+ originalTable.newFastAppend().appendFile(FILE_C).commit();
+
+ TableOperations ops = ((BaseTable) originalTable).operations();
+ String metadataLocation = ops.current().metadataFileLocation();
+
+ catalog.dropTable(TABLE, false /* do not purge */);
+
+ Table registeredTable = catalog.registerTable(TABLE, metadataLocation);
+
+ Assertions.assertThat(registeredTable).isNotNull();
+ Assertions.assertThat(catalog.tableExists(TABLE)).as("Table must
exist").isTrue();
+ Assertions.assertThat(registeredTable.properties())
+ .as("Props must match")
+ .containsAllEntriesOf(properties);
+ Assertions.assertThat(registeredTable.schema().asStruct())
+ .as("Schema must match")
+ .isEqualTo(originalTable.schema().asStruct());
+ Assertions.assertThat(registeredTable.specs())
+ .as("Specs must match")
+ .isEqualTo(originalTable.specs());
+ Assertions.assertThat(registeredTable.sortOrders())
+ .as("Sort orders must match")
+ .isEqualTo(originalTable.sortOrders());
+ Assertions.assertThat(registeredTable.currentSnapshot())
+ .as("Current snapshot must match")
+ .isEqualTo(originalTable.currentSnapshot());
+ Assertions.assertThat(registeredTable.snapshots())
+ .as("Snapshots must match")
+ .isEqualTo(originalTable.snapshots());
+ Assertions.assertThat(registeredTable.history())
+ .as("History must match")
+ .isEqualTo(originalTable.history());
+
+ TestHelpers.assertSameSchemaMap(registeredTable.schemas(),
originalTable.schemas());
+ assertFiles(registeredTable, FILE_B, FILE_C);
+
+ registeredTable.newFastAppend().appendFile(FILE_A).commit();
+ assertFiles(registeredTable, FILE_B, FILE_C, FILE_A);
+
+ Assertions.assertThat(catalog.loadTable(TABLE)).isNotNull();
+ Assertions.assertThat(catalog.dropTable(TABLE)).isTrue();
+ Assertions.assertThat(catalog.tableExists(TABLE)).isFalse();
+ }
+
+ @Test
+ public void testRegisterExistingTable() {
+ C catalog = catalog();
+
+ TableIdentifier identifier = TableIdentifier.of("a", "t1");
+
+ if (requiresNamespaceCreate()) {
+ catalog.createNamespace(identifier.namespace());
+ }
+
+ catalog.createTable(identifier, SCHEMA);
+ Table table = catalog.loadTable(identifier);
+ TableOperations ops = ((BaseTable) table).operations();
+ String metadataLocation = ops.current().metadataFileLocation();
+ Assertions.assertThatThrownBy(() -> catalog.registerTable(identifier,
metadataLocation))
+ .isInstanceOf(AlreadyExistsException.class)
+ .hasMessage("Table already exists: a.t1");
+ Assertions.assertThat(catalog.dropTable(identifier)).isTrue();
+ }
+
private static void assertEmpty(String context, Catalog catalog, Namespace
ns) {
try {
Assertions.assertThat(catalog.listTables(ns)).as(context).isEmpty();
diff --git a/core/src/test/java/org/apache/iceberg/jdbc/TestJdbcCatalog.java
b/core/src/test/java/org/apache/iceberg/jdbc/TestJdbcCatalog.java
index 73520cb941..4634de5707 100644
--- a/core/src/test/java/org/apache/iceberg/jdbc/TestJdbcCatalog.java
+++ b/core/src/test/java/org/apache/iceberg/jdbc/TestJdbcCatalog.java
@@ -44,13 +44,11 @@ import org.apache.iceberg.DataFile;
import org.apache.iceberg.DataFiles;
import org.apache.iceberg.FileFormat;
import org.apache.iceberg.FileScanTask;
-import org.apache.iceberg.HasTableOperations;
import org.apache.iceberg.PartitionSpec;
import org.apache.iceberg.Schema;
import org.apache.iceberg.SortOrder;
import org.apache.iceberg.Table;
import org.apache.iceberg.TableOperations;
-import org.apache.iceberg.TestHelpers;
import org.apache.iceberg.Transaction;
import org.apache.iceberg.catalog.CatalogTests;
import org.apache.iceberg.catalog.Namespace;
@@ -751,37 +749,6 @@ public class TestJdbcCatalog extends
CatalogTests<JdbcCatalog> {
assertThat(JdbcUtil.stringToNamespace(nsString)).isEqualTo(ns);
}
- @Test
- public void testRegisterTable() {
- TableIdentifier identifier = TableIdentifier.of("a", "t1");
- catalog.createTable(identifier, SCHEMA);
- Table registeringTable = catalog.loadTable(identifier);
- catalog.dropTable(identifier, false);
- TableOperations ops = ((HasTableOperations) registeringTable).operations();
- String metadataLocation = ((JdbcTableOperations)
ops).currentMetadataLocation();
- Table registeredTable = catalog.registerTable(identifier,
metadataLocation);
- Assertions.assertThat(registeredTable).isNotNull();
- TestHelpers.assertSerializedAndLoadedMetadata(registeringTable,
registeredTable);
- String expectedMetadataLocation =
- ((HasTableOperations)
registeredTable).operations().current().metadataFileLocation();
-
Assertions.assertThat(metadataLocation).isEqualTo(expectedMetadataLocation);
- Assertions.assertThat(catalog.loadTable(identifier)).isNotNull();
- Assertions.assertThat(catalog.dropTable(identifier)).isTrue();
- }
-
- @Test
- public void testRegisterExistingTable() {
- TableIdentifier identifier = TableIdentifier.of("a", "t1");
- catalog.createTable(identifier, SCHEMA);
- Table registeringTable = catalog.loadTable(identifier);
- TableOperations ops = ((HasTableOperations) registeringTable).operations();
- String metadataLocation = ((JdbcTableOperations)
ops).currentMetadataLocation();
- Assertions.assertThatThrownBy(() -> catalog.registerTable(identifier,
metadataLocation))
- .isInstanceOf(AlreadyExistsException.class)
- .hasMessage("Table already exists: a.t1");
- Assertions.assertThat(catalog.dropTable(identifier)).isTrue();
- }
-
@Test
public void testCatalogWithCustomMetricsReporter() throws IOException {
JdbcCatalog catalogWithCustomReporter =
diff --git a/core/src/test/java/org/apache/iceberg/rest/RESTCatalogAdapter.java
b/core/src/test/java/org/apache/iceberg/rest/RESTCatalogAdapter.java
index 2990fcca78..0772601b77 100644
--- a/core/src/test/java/org/apache/iceberg/rest/RESTCatalogAdapter.java
+++ b/core/src/test/java/org/apache/iceberg/rest/RESTCatalogAdapter.java
@@ -49,6 +49,7 @@ import
org.apache.iceberg.relocated.com.google.common.collect.Lists;
import org.apache.iceberg.rest.requests.CommitTransactionRequest;
import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
import org.apache.iceberg.rest.requests.CreateTableRequest;
+import org.apache.iceberg.rest.requests.RegisterTableRequest;
import org.apache.iceberg.rest.requests.RenameTableRequest;
import org.apache.iceberg.rest.requests.ReportMetricsRequest;
import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
@@ -126,6 +127,11 @@ public class RESTCatalogAdapter implements RESTClient {
LoadTableResponse.class),
LOAD_TABLE(
HTTPMethod.GET, "v1/namespaces/{namespace}/tables/{table}", null,
LoadTableResponse.class),
+ REGISTER_TABLE(
+ HTTPMethod.POST,
+ "v1/namespaces/{namespace}/register",
+ RegisterTableRequest.class,
+ LoadTableResponse.class),
UPDATE_TABLE(
HTTPMethod.POST,
"v1/namespaces/{namespace}/tables/{table}",
@@ -345,6 +351,14 @@ public class RESTCatalogAdapter implements RESTClient {
return castResponse(responseType, CatalogHandlers.loadTable(catalog,
ident));
}
+ case REGISTER_TABLE:
+ {
+ Namespace namespace = namespaceFromPathVars(vars);
+ RegisterTableRequest request =
castRequest(RegisterTableRequest.class, body);
+ return castResponse(
+ responseType, CatalogHandlers.registerTable(catalog, namespace,
request));
+ }
+
case UPDATE_TABLE:
{
TableIdentifier ident = identFromPathVars(vars);
diff --git a/core/src/test/java/org/apache/iceberg/rest/TestResourcePaths.java
b/core/src/test/java/org/apache/iceberg/rest/TestResourcePaths.java
index b2a5f5073e..e0e61a594e 100644
--- a/core/src/test/java/org/apache/iceberg/rest/TestResourcePaths.java
+++ b/core/src/test/java/org/apache/iceberg/rest/TestResourcePaths.java
@@ -135,4 +135,12 @@ public class TestResourcePaths {
.isEqualTo("v1/ws/catalog/namespaces/n%1Fs/tables/table");
Assertions.assertThat(withoutPrefix.table(ident)).isEqualTo("v1/namespaces/n%1Fs/tables/table");
}
+
+ @Test
+ public void testRegister() {
+ Namespace ns = Namespace.of("ns");
+ Assertions.assertThat(withPrefix.register(ns))
+ .isEqualTo("v1/ws/catalog/namespaces/ns/register");
+
Assertions.assertThat(withoutPrefix.register(ns)).isEqualTo("v1/namespaces/ns/register");
+ }
}
diff --git
a/core/src/test/java/org/apache/iceberg/rest/requests/TestRegisterTableRequestParser.java
b/core/src/test/java/org/apache/iceberg/rest/requests/TestRegisterTableRequestParser.java
new file mode 100644
index 0000000000..9b479d89d7
--- /dev/null
+++
b/core/src/test/java/org/apache/iceberg/rest/requests/TestRegisterTableRequestParser.java
@@ -0,0 +1,80 @@
+/*
+ * 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.requests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestRegisterTableRequestParser {
+
+ @Test
+ public void nullCheck() {
+ Assertions.assertThatThrownBy(() ->
RegisterTableRequestParser.toJson(null))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Invalid register table request: null");
+
+ Assertions.assertThatThrownBy(() ->
RegisterTableRequestParser.fromJson((JsonNode) null))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Cannot parse register table request from null object");
+ }
+
+ @Test
+ public void missingFields() {
+ Assertions.assertThatThrownBy(() ->
RegisterTableRequestParser.fromJson("{}"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Cannot parse missing string: name");
+
+ Assertions.assertThatThrownBy(
+ () -> RegisterTableRequestParser.fromJson("{\"name\" :
\"test_tbl\"}"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Cannot parse missing string: metadata-location");
+
+ Assertions.assertThatThrownBy(
+ () ->
+ RegisterTableRequestParser.fromJson(
+ "{\"metadata-location\" :
\"file://tmp/NS/test_tbl/metadata/00000-d4f60d2f-2ad2-408b-8832-0ed7fbd851ee.metadata.json\"}"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Cannot parse missing string: name");
+ }
+
+ @Test
+ public void roundTripSerde() {
+ RegisterTableRequest request =
+ ImmutableRegisterTableRequest.builder()
+ .name("table_1")
+ .metadataLocation(
+
"file://tmp/NS/test_tbl/metadata/00000-d4f60d2f-2ad2-408b-8832-0ed7fbd851ee.metadata.json")
+ .build();
+
+ String expectedJson =
+ "{\n"
+ + " \"name\" : \"table_1\",\n"
+ + " \"metadata-location\" :
\"file://tmp/NS/test_tbl/metadata/00000-d4f60d2f-2ad2-408b-8832-0ed7fbd851ee.metadata.json\"\n"
+ + "}";
+
+ String json = RegisterTableRequestParser.toJson(request, true);
+ assertThat(json).isEqualTo(expectedJson);
+
+
assertThat(RegisterTableRequestParser.toJson(RegisterTableRequestParser.fromJson(json),
true))
+ .isEqualTo(expectedJson);
+ }
+}
diff --git a/open-api/rest-catalog-open-api.py
b/open-api/rest-catalog-open-api.py
index 17fff47f81..2e5465f77a 100644
--- a/open-api/rest-catalog-open-api.py
+++ b/open-api/rest-catalog-open-api.py
@@ -362,6 +362,11 @@ class TableRequirement(BaseModel):
default_sort_order_id: Optional[int] = Field(None,
alias='default-sort-order-id')
+class RegisterTableRequest(BaseModel):
+ name: str
+ metadata_location: str = Field(..., alias='metadata-location')
+
+
class TokenType(Enum):
"""
Token type identifier, from RFC 8693 Section 3
diff --git a/open-api/rest-catalog-open-api.yaml
b/open-api/rest-catalog-open-api.yaml
index 29b19e4a1b..6b61e5b5d4 100644
--- a/open-api/rest-catalog-open-api.yaml
+++ b/open-api/rest-catalog-open-api.yaml
@@ -498,6 +498,58 @@ paths:
5XX:
$ref: '#/components/responses/ServerErrorResponse'
+ /v1/{prefix}/namespaces/{namespace}/register:
+ parameters:
+ - $ref: '#/components/parameters/prefix'
+ - $ref: '#/components/parameters/namespace'
+
+ post:
+ tags:
+ - Catalog API
+ summary: Register a table in the given namespace using given metadata
file location
+ description:
+ Register a table using given metadata file location.
+
+ operationId: registerTable
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RegisterTableRequest'
+ responses:
+ 200:
+ $ref: '#/components/responses/LoadTableResponse'
+ 400:
+ $ref: '#/components/responses/BadRequestErrorResponse'
+ 401:
+ $ref: '#/components/responses/UnauthorizedResponse'
+ 403:
+ $ref: '#/components/responses/ForbiddenResponse'
+ 404:
+ description: Not Found - The namespace specified does not exist
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorModel'
+ examples:
+ NamespaceNotFound:
+ $ref: '#/components/examples/NoSuchNamespaceError'
+ 409:
+ description: Conflict - The table already exists
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorModel'
+ examples:
+ NamespaceAlreadyExists:
+ $ref: '#/components/examples/TableAlreadyExistsError'
+ 419:
+ $ref: '#/components/responses/AuthenticationTimeoutResponse'
+ 503:
+ $ref: '#/components/responses/ServiceUnavailableResponse'
+ 5XX:
+ $ref: '#/components/responses/ServerErrorResponse'
+
/v1/{prefix}/namespaces/{namespace}/tables/{table}:
parameters:
- $ref: '#/components/parameters/prefix'
@@ -1913,6 +1965,17 @@ components:
additionalProperties:
type: string
+ RegisterTableRequest:
+ type: object
+ required:
+ - name
+ - metadata-location
+ properties:
+ name:
+ type: string
+ metadata-location:
+ type: string
+
TokenType:
type: string
enum: