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 6fddb0875721..1e0ef660271b 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.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 static LoadTableResponse createTable( 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 2bf95fea03e9..06f2de32df0c 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.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 static void registerAll(ObjectMapper mapper) { .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 UpdateTableRequest deserialize(JsonParser p, DeserializationContext conte return UpdateTableRequestParser.fromJson(jsonNode); } } + + public static class RegisterTableRequestSerializer + extends JsonSerializer { + @Override + public void serialize(T request, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + RegisterTableRequestParser.toJson(request, gen); + } + } + + public static class RegisterTableRequestDeserializer + extends JsonDeserializer { + @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 884e35dd6251..72547eec3486 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.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 void invalidateTable(SessionContext context, TableIdentifier ident) {} @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 7e4f9e0c9888..b5b974f14c5e 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 String table(TableIdentifier ident) { 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 000000000000..33b37dae242f --- /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; + +@Value.Immutable +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 000000000000..961b6c185b87 --- /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 15259d979e18..5641dce89f9c 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.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 void tableCreationWithoutNamespace() { .hasMessageContaining("Namespace does not exist: non-existing"); } + @Test + public void testRegisterTable() { + C catalog = catalog(); + + if (requiresNamespaceCreate()) { + catalog.createNamespace(TABLE.namespace()); + } + + Map 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 73520cb94122..4634de57073d 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.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 void testConversions() { 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 2990fcca7899..0772601b77df 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.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 @@ enum Route { 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 T handleRequest( 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 b2a5f5073ea0..e0e61a594e7d 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 void testTableWithMultipartNamespace() { .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 000000000000..9b479d89d7d7 --- /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 17fff47f815d..2e5465f77a13 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 f75b74e20060..12f2f26b22e1 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: