From 71711e18b6a2a01da9c2ca1e7a8b4daa903399c5 Mon Sep 17 00:00:00 2001 From: Eduard Tudenhoefner Date: Tue, 2 May 2023 16:05:56 +0200 Subject: [PATCH] Core: Add REST spec and request for committing changes against multiple tables --- .../apache/iceberg/rest/RESTSerializers.java | 30 ++- .../apache/iceberg/rest/ResourcePaths.java | 4 + .../requests/CommitTransactionRequest.java | 43 +++ .../CommitTransactionRequestParser.java | 117 +++++++++ .../rest/requests/UpdateTableRequest.java | 24 +- .../TestCommitTransactionRequestParser.java | 245 ++++++++++++++++++ open-api/rest-catalog-open-api.yaml | 129 +++++++++ 7 files changed, 590 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/org/apache/iceberg/rest/requests/CommitTransactionRequest.java create mode 100644 core/src/main/java/org/apache/iceberg/rest/requests/CommitTransactionRequestParser.java create mode 100644 core/src/test/java/org/apache/iceberg/rest/requests/TestCommitTransactionRequestParser.java 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 9e17f50c530a..1d6ad7d14b73 100644 --- a/core/src/main/java/org/apache/iceberg/rest/RESTSerializers.java +++ b/core/src/main/java/org/apache/iceberg/rest/RESTSerializers.java @@ -42,6 +42,9 @@ import org.apache.iceberg.catalog.TableIdentifier; 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.ImmutableCommitTransactionRequest; import org.apache.iceberg.rest.requests.ImmutableReportMetricsRequest; import org.apache.iceberg.rest.requests.ReportMetricsRequest; import org.apache.iceberg.rest.requests.ReportMetricsRequestParser; @@ -83,7 +86,14 @@ public static void registerAll(ObjectMapper mapper) { .addDeserializer(ReportMetricsRequest.class, new ReportMetricsRequestDeserializer<>()) .addSerializer(ImmutableReportMetricsRequest.class, new ReportMetricsRequestSerializer<>()) .addDeserializer( - ImmutableReportMetricsRequest.class, new ReportMetricsRequestDeserializer<>()); + ImmutableReportMetricsRequest.class, new ReportMetricsRequestDeserializer<>()) + .addSerializer(CommitTransactionRequest.class, new CommitTransactionRequestSerializer<>()) + .addSerializer( + ImmutableCommitTransactionRequest.class, new CommitTransactionRequestSerializer<>()) + .addDeserializer( + CommitTransactionRequest.class, new CommitTransactionRequestDeserializer<>()) + .addDeserializer( + ImmutableCommitTransactionRequest.class, new CommitTransactionRequestDeserializer<>()); mapper.registerModule(module); } @@ -280,4 +290,22 @@ public T deserialize(JsonParser p, DeserializationContext context) throws IOExce return (T) ReportMetricsRequestParser.fromJson(jsonNode); } } + + public static class CommitTransactionRequestSerializer + extends JsonSerializer { + @Override + public void serialize(T request, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + CommitTransactionRequestParser.toJson(request, gen); + } + } + + public static class CommitTransactionRequestDeserializer + extends JsonDeserializer { + @Override + public T deserialize(JsonParser p, DeserializationContext context) throws IOException { + JsonNode jsonNode = p.getCodec().readTree(p); + return (T) CommitTransactionRequestParser.fromJson(jsonNode); + } + } } 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 6fa09f33d2ba..7e4f9e0c9888 100644 --- a/core/src/main/java/org/apache/iceberg/rest/ResourcePaths.java +++ b/core/src/main/java/org/apache/iceberg/rest/ResourcePaths.java @@ -85,4 +85,8 @@ public String metrics(TableIdentifier identifier) { RESTUtil.encodeString(identifier.name()), "metrics"); } + + public String commitTransaction() { + return SLASH.join("v1", prefix, "transactions", "commit"); + } } diff --git a/core/src/main/java/org/apache/iceberg/rest/requests/CommitTransactionRequest.java b/core/src/main/java/org/apache/iceberg/rest/requests/CommitTransactionRequest.java new file mode 100644 index 000000000000..ef57aa9a5599 --- /dev/null +++ b/core/src/main/java/org/apache/iceberg/rest/requests/CommitTransactionRequest.java @@ -0,0 +1,43 @@ +/* + * 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 java.util.List; +import org.apache.iceberg.relocated.com.google.common.base.Preconditions; +import org.apache.iceberg.rest.RESTRequest; +import org.immutables.value.Value; + +@Value.Immutable +public interface CommitTransactionRequest extends RESTRequest { + List tableChanges(); + + @Override + default void validate() { + check(); + } + + @Value.Check + default void check() { + Preconditions.checkArgument(!tableChanges().isEmpty(), "Invalid table changes: empty"); + for (UpdateTableRequest tableChange : tableChanges()) { + Preconditions.checkArgument( + null != tableChange.identifier(), "Invalid table changes: table identifier required"); + } + } +} diff --git a/core/src/main/java/org/apache/iceberg/rest/requests/CommitTransactionRequestParser.java b/core/src/main/java/org/apache/iceberg/rest/requests/CommitTransactionRequestParser.java new file mode 100644 index 000000000000..399423238d99 --- /dev/null +++ b/core/src/main/java/org/apache/iceberg/rest/requests/CommitTransactionRequestParser.java @@ -0,0 +1,117 @@ +/* + * 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 java.util.List; +import org.apache.iceberg.MetadataUpdate; +import org.apache.iceberg.MetadataUpdateParser; +import org.apache.iceberg.catalog.TableIdentifier; +import org.apache.iceberg.catalog.TableIdentifierParser; +import org.apache.iceberg.relocated.com.google.common.base.Preconditions; +import org.apache.iceberg.relocated.com.google.common.collect.Lists; +import org.apache.iceberg.util.JsonUtil; + +public class CommitTransactionRequestParser { + private static final String TABLE_CHANGES = "table-changes"; + private static final String IDENTIFIER = "identifier"; + private static final String REQUIREMENTS = "requirements"; + private static final String UPDATES = "updates"; + + private CommitTransactionRequestParser() {} + + public static String toJson(CommitTransactionRequest request) { + return toJson(request, false); + } + + public static String toJson(CommitTransactionRequest request, boolean pretty) { + return JsonUtil.generate(gen -> toJson(request, gen), pretty); + } + + public static void toJson(CommitTransactionRequest request, JsonGenerator gen) + throws IOException { + Preconditions.checkArgument(null != request, "Invalid commit tx request: null"); + + gen.writeStartObject(); + gen.writeFieldName(TABLE_CHANGES); + gen.writeStartArray(); + + for (UpdateTableRequest tableChange : request.tableChanges()) { + gen.writeStartObject(); + + gen.writeFieldName(IDENTIFIER); + TableIdentifierParser.toJson(tableChange.identifier(), gen); + + gen.writeArrayFieldStart(REQUIREMENTS); + for (UpdateTableRequest.UpdateRequirement updateRequirement : tableChange.requirements()) { + UpdateRequirementParser.toJson(updateRequirement, gen); + } + gen.writeEndArray(); + + gen.writeArrayFieldStart(UPDATES); + for (MetadataUpdate metadataUpdate : tableChange.updates()) { + MetadataUpdateParser.toJson(metadataUpdate, gen); + } + gen.writeEndArray(); + + gen.writeEndObject(); + } + + gen.writeEndArray(); + gen.writeEndObject(); + } + + public static CommitTransactionRequest fromJson(String json) { + return JsonUtil.parse(json, CommitTransactionRequestParser::fromJson); + } + + public static CommitTransactionRequest fromJson(JsonNode json) { + Preconditions.checkArgument(null != json, "Cannot parse commit tx request from null object"); + + ImmutableCommitTransactionRequest.Builder builder = ImmutableCommitTransactionRequest.builder(); + JsonNode changes = JsonUtil.get(TABLE_CHANGES, json); + + Preconditions.checkArgument( + changes.isArray(), "Cannot parse commit tx request from non-array: %s", changes); + + for (JsonNode node : changes) { + TableIdentifier identifier = TableIdentifierParser.fromJson(JsonUtil.get(IDENTIFIER, node)); + + JsonNode requirementsNode = JsonUtil.get(REQUIREMENTS, node); + List requirements = Lists.newArrayList(); + Preconditions.checkArgument( + requirementsNode.isArray(), + "Cannot parse requirements from non-array: %s", + requirementsNode); + requirementsNode.forEach(req -> requirements.add(UpdateRequirementParser.fromJson(req))); + + JsonNode updatesNode = JsonUtil.get(UPDATES, node); + List updates = Lists.newArrayList(); + Preconditions.checkArgument( + updatesNode.isArray(), "Cannot parse metadata updates from non-array: %s", updatesNode); + + updatesNode.forEach(update -> updates.add(MetadataUpdateParser.fromJson(update))); + builder.addTableChanges(new UpdateTableRequest(identifier, requirements, updates)); + } + + return builder.build(); + } +} diff --git a/core/src/main/java/org/apache/iceberg/rest/requests/UpdateTableRequest.java b/core/src/main/java/org/apache/iceberg/rest/requests/UpdateTableRequest.java index 694e44e841dc..75d69169723c 100644 --- a/core/src/main/java/org/apache/iceberg/rest/requests/UpdateTableRequest.java +++ b/core/src/main/java/org/apache/iceberg/rest/requests/UpdateTableRequest.java @@ -23,6 +23,7 @@ import org.apache.iceberg.MetadataUpdate; import org.apache.iceberg.SnapshotRef; import org.apache.iceberg.TableMetadata; +import org.apache.iceberg.catalog.TableIdentifier; import org.apache.iceberg.exceptions.CommitFailedException; import org.apache.iceberg.relocated.com.google.common.base.MoreObjects; import org.apache.iceberg.relocated.com.google.common.base.Preconditions; @@ -33,6 +34,7 @@ public class UpdateTableRequest implements RESTRequest { + private TableIdentifier identifier; private List requirements; private List updates; @@ -45,6 +47,14 @@ public UpdateTableRequest(List requirements, List requirements, + List updates) { + this(requirements, updates); + this.identifier = identifier; + } + @Override public void validate() {} @@ -56,6 +66,10 @@ public List updates() { return updates != null ? updates : ImmutableList.of(); } + public TableIdentifier identifier() { + return identifier; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) @@ -84,6 +98,7 @@ public static class Builder { private final List updates = Lists.newArrayList(); private final Set changedRefs = Sets.newHashSet(); private final boolean isReplace; + private TableIdentifier identifier = null; private boolean addedSchema = false; private boolean setSchemaId = false; private boolean addedSpec = false; @@ -95,6 +110,12 @@ public Builder(TableMetadata base, boolean isReplace) { this.isReplace = isReplace; } + public Builder forTable(TableIdentifier ident) { + Preconditions.checkArgument(null != ident, "Invalid table identifier: null"); + this.identifier = ident; + return this; + } + private Builder require(UpdateRequirement requirement) { Preconditions.checkArgument(requirement != null, "Invalid requirement: null"); requirements.add(requirement); @@ -217,7 +238,8 @@ private void update(MetadataUpdate.SetDefaultSortOrder update) { } public UpdateTableRequest build() { - return new UpdateTableRequest(requirements.build(), ImmutableList.copyOf(updates)); + return new UpdateTableRequest( + identifier, requirements.build(), ImmutableList.copyOf(updates)); } } diff --git a/core/src/test/java/org/apache/iceberg/rest/requests/TestCommitTransactionRequestParser.java b/core/src/test/java/org/apache/iceberg/rest/requests/TestCommitTransactionRequestParser.java new file mode 100644 index 000000000000..330041895ce2 --- /dev/null +++ b/core/src/test/java/org/apache/iceberg/rest/requests/TestCommitTransactionRequestParser.java @@ -0,0 +1,245 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.iceberg.MetadataUpdate; +import org.apache.iceberg.catalog.TableIdentifier; +import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList; +import org.apache.iceberg.rest.requests.UpdateTableRequest.UpdateRequirement; +import org.junit.jupiter.api.Test; + +public class TestCommitTransactionRequestParser { + + @Test + public void nullAndEmptyCheck() { + assertThatThrownBy(() -> CommitTransactionRequestParser.toJson(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid commit tx request: null"); + + assertThatThrownBy(() -> CommitTransactionRequestParser.fromJson((JsonNode) null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse commit tx request from null object"); + + assertThatThrownBy(() -> CommitTransactionRequestParser.fromJson("{}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse missing field: table-changes"); + + assertThatThrownBy(() -> CommitTransactionRequestParser.fromJson("{\"table-changes\":{}}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse commit tx request from non-array: {}"); + + assertThatThrownBy(() -> CommitTransactionRequestParser.fromJson("{\"table-changes\":[]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid table changes: empty"); + } + + @Test + public void invalidTableIdentifier() { + assertThatThrownBy( + () -> + CommitTransactionRequestParser.fromJson( + "{\"table-changes\":[{\"ns1.table1\" : \"ns1.table1\"}]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse missing field: identifier"); + + assertThatThrownBy( + () -> + CommitTransactionRequestParser.fromJson( + "{\"table-changes\":[{\"identifier\" : {}}]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse missing string: name"); + + assertThatThrownBy( + () -> + CommitTransactionRequestParser.fromJson( + "{\"table-changes\":[{\"identifier\" : { \"name\": 23}}]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse to a string value: name: 23"); + } + + @Test + public void invalidRequirements() { + assertThatThrownBy( + () -> + CommitTransactionRequestParser.fromJson( + "{\"table-changes\":[{\"identifier\":{\"namespace\":[\"ns1\"],\"name\":\"table1\"}}]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse missing field: requirements"); + + assertThatThrownBy( + () -> + CommitTransactionRequestParser.fromJson( + "{\"table-changes\":[{\"identifier\":{\"namespace\":[\"ns1\"],\"name\":\"table1\"}," + + "\"requirements\":[23],\"updates\":[]}]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse update requirement from non-object value: 23"); + + assertThatThrownBy( + () -> + CommitTransactionRequestParser.fromJson( + "{\"table-changes\":[{\"identifier\":{\"namespace\":[\"ns1\"],\"name\":\"table1\"}," + + "\"requirements\":[{}],\"updates\":[]}]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse update requirement. Missing field: type"); + + assertThatThrownBy( + () -> + CommitTransactionRequestParser.fromJson( + "{\"table-changes\":[{\"identifier\":{\"namespace\":[\"ns1\"],\"name\":\"table1\"}," + + "\"requirements\":[{\"type\":\"assert-table-uuid\"}],\"updates\":[]}]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse missing string: uuid"); + } + + @Test + public void invalidMetadataUpdates() { + assertThatThrownBy( + () -> + CommitTransactionRequestParser.fromJson( + "{\"table-changes\":[{\"identifier\":{\"namespace\":[\"ns1\"],\"name\":\"table1\"},\"requirements\":[]}]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse missing field: updates"); + + assertThatThrownBy( + () -> + CommitTransactionRequestParser.fromJson( + "{\"table-changes\":[{\"identifier\":{\"namespace\":[\"ns1\"],\"name\":\"table1\"}," + + "\"requirements\":[],\"updates\":[23]}]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse metadata update from non-object value: 23"); + + assertThatThrownBy( + () -> + CommitTransactionRequestParser.fromJson( + "{\"table-changes\":[{\"identifier\":{\"namespace\":[\"ns1\"],\"name\":\"table1\"}," + + "\"requirements\":[],\"updates\":[{}]}]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse metadata update. Missing field: action"); + + assertThatThrownBy( + () -> + CommitTransactionRequestParser.fromJson( + "{\"table-changes\":[{\"identifier\":{\"namespace\":[\"ns1\"],\"name\":\"table1\"}," + + "\"requirements\":[],\"updates\":[{\"action\":\"assign-uuid\"}]}]}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse missing string: uuid"); + } + + @Test + public void roundTripSerde() { + String uuid = "2cc52516-5e73-41f2-b139-545d41a4e151"; + UpdateTableRequest commitTableRequestOne = + new UpdateTableRequest( + TableIdentifier.of("ns1", "table1"), + ImmutableList.of( + new UpdateRequirement.AssertTableUUID(uuid), + new UpdateRequirement.AssertTableDoesNotExist()), + ImmutableList.of( + new MetadataUpdate.AssignUUID(uuid), new MetadataUpdate.SetCurrentSchema(23))); + + UpdateTableRequest commitTableRequestTwo = + new UpdateTableRequest( + TableIdentifier.of("ns1", "table2"), + ImmutableList.of( + new UpdateRequirement.AssertDefaultSpecID(4), + new UpdateRequirement.AssertCurrentSchemaID(24)), + ImmutableList.of( + new MetadataUpdate.RemoveSnapshot(101L), new MetadataUpdate.SetCurrentSchema(25))); + + CommitTransactionRequest request = + ImmutableCommitTransactionRequest.builder() + .addTableChanges(commitTableRequestOne, commitTableRequestTwo) + .build(); + + String expectedJson = + "{\n" + + " \"table-changes\" : [ {\n" + + " \"identifier\" : {\n" + + " \"namespace\" : [ \"ns1\" ],\n" + + " \"name\" : \"table1\"\n" + + " },\n" + + " \"requirements\" : [ {\n" + + " \"type\" : \"assert-table-uuid\",\n" + + " \"uuid\" : \"2cc52516-5e73-41f2-b139-545d41a4e151\"\n" + + " }, {\n" + + " \"type\" : \"assert-create\"\n" + + " } ],\n" + + " \"updates\" : [ {\n" + + " \"action\" : \"assign-uuid\",\n" + + " \"uuid\" : \"2cc52516-5e73-41f2-b139-545d41a4e151\"\n" + + " }, {\n" + + " \"action\" : \"set-current-schema\",\n" + + " \"schema-id\" : 23\n" + + " } ]\n" + + " }, {\n" + + " \"identifier\" : {\n" + + " \"namespace\" : [ \"ns1\" ],\n" + + " \"name\" : \"table2\"\n" + + " },\n" + + " \"requirements\" : [ {\n" + + " \"type\" : \"assert-default-spec-id\",\n" + + " \"default-spec-id\" : 4\n" + + " }, {\n" + + " \"type\" : \"assert-current-schema-id\",\n" + + " \"current-schema-id\" : 24\n" + + " } ],\n" + + " \"updates\" : [ {\n" + + " \"action\" : \"remove-snapshots\",\n" + + " \"snapshot-ids\" : [ 101 ]\n" + + " }, {\n" + + " \"action\" : \"set-current-schema\",\n" + + " \"schema-id\" : 25\n" + + " } ]\n" + + " } ]\n" + + "}"; + + String json = CommitTransactionRequestParser.toJson(request, true); + assertThat(json).isEqualTo(expectedJson); + + // can't do an equality comparison on CommitTransactionRequest because updates/requirements + // don't implement equals/hashcode + assertThat( + CommitTransactionRequestParser.toJson( + CommitTransactionRequestParser.fromJson(json), true)) + .isEqualTo(expectedJson); + } + + @Test + public void emptyRequirementsAndUpdates() { + CommitTransactionRequest commitTxRequest = + ImmutableCommitTransactionRequest.builder() + .addTableChanges( + new UpdateTableRequest( + TableIdentifier.of("ns1", "table1"), ImmutableList.of(), ImmutableList.of())) + .build(); + + String json = + "{\"table-changes\":[{\"identifier\":{\"namespace\":[\"ns1\"],\"name\":\"table1\"},\"requirements\":[],\"updates\":[]}]}"; + + assertThat(CommitTransactionRequestParser.toJson(commitTxRequest)).isEqualTo(json); + // can't do an equality comparison on CommitTransactionRequest because updates/requirements + // don't implement equals/hashcode + assertThat(CommitTransactionRequestParser.toJson(CommitTransactionRequestParser.fromJson(json))) + .isEqualTo(json); + } +} diff --git a/open-api/rest-catalog-open-api.yaml b/open-api/rest-catalog-open-api.yaml index 2de99a751777..905aa2d7f413 100644 --- a/open-api/rest-catalog-open-api.yaml +++ b/open-api/rest-catalog-open-api.yaml @@ -848,6 +848,121 @@ paths: 5XX: $ref: '#/components/responses/ServerErrorResponse' + /v1/{prefix}/transactions/commit: + parameters: + - $ref: '#/components/parameters/prefix' + + post: + tags: + - Catalog API + summary: Commit updates to multiple tables in an atomic operation + operationId: commitTransaction + requestBody: + description: + Commit updates to multiple tables in an atomic operation + + + A commit for a single table consists of a table identifier with requirements and updates. + Requirements are assertions that will be validated before attempting to make and commit changes. + For example, `assert-ref-snapshot-id` will check that a named ref's snapshot ID has a certain value. + + + Updates are changes to make to table metadata. For example, after asserting that the current main ref + is at the expected snapshot, a commit may add a new child snapshot and set the ref to the new + snapshot id. + content: + application/json: + schema: + $ref: '#/components/schemas/CommitTransactionRequest' + required: true + responses: + 204: + description: Success, no content + 400: + $ref: '#/components/responses/BadRequestErrorResponse' + 401: + $ref: '#/components/responses/UnauthorizedResponse' + 403: + $ref: '#/components/responses/ForbiddenResponse' + 404: + description: + Not Found - NoSuchTableException, table to load does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorModel' + examples: + TableToUpdateDoesNotExist: + $ref: '#/components/examples/NoSuchTableError' + 409: + description: + Conflict - CommitFailedException, one or more requirements failed. The client may retry. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorModel' + 419: + $ref: '#/components/responses/AuthenticationTimeoutResponse' + 500: + description: + An unknown server-side problem occurred; the commit state is unknown. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorModel' + example: { + "error": { + "message": "Internal Server Error", + "type": "CommitStateUnknownException", + "code": 500 + } + } + 503: + $ref: '#/components/responses/ServiceUnavailableResponse' + 502: + description: + A gateway or proxy received an invalid response from the upstream server; the commit state is unknown. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorModel' + example: { + "error": { + "message": "Invalid response from the upstream server", + "type": "CommitStateUnknownException", + "code": 502 + } + } + 504: + description: + A server-side gateway timeout occurred; the commit state is unknown. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorModel' + example: { + "error": { + "message": "Gateway timed out during commit", + "type": "CommitStateUnknownException", + "code": 504 + } + } + 5XX: + description: + A server-side problem that might not be addressable on the client. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorModel' + example: { + "error": { + "message": "Bad Gateway", + "type": "InternalServerError", + "code": 502 + } + } + + components: ####################################################### # Common Parameter Definitions Used In Several Routes # @@ -1749,6 +1864,9 @@ components: - requirements - updates properties: + identifier: + description: Table identifier to update; must be present for CommitTransactionRequest + $ref: '#/components/schemas/TableIdentifier' requirements: type: array items: @@ -1758,6 +1876,17 @@ components: items: $ref: '#/components/schemas/TableUpdate' + CommitTransactionRequest: + type: object + required: + - table-changes + properties: + table-changes: + type: array + items: + description: Table commit request; must provide an `identifier` + $ref: '#/components/schemas/CommitTableRequest' + CreateTableRequest: type: object required: