From 52a5471035af278286e291e8404e588231d7e2e7 Mon Sep 17 00:00:00 2001 From: yantian Date: Wed, 25 Dec 2024 14:13:08 +0800 Subject: [PATCH 1/7] support alter table --- .../apache/paimon/rest/RESTObjectMapper.java | 35 ++++ .../paimon/rest/SchemaChangeParser.java | 166 ++++++++++++++++++ .../rest/requests/AlterTableRequest.java | 49 ++++++ .../apache/paimon/utils/JsonSerdeUtil.java | 21 +++ 4 files changed, 271 insertions(+) create mode 100644 paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java create mode 100644 paimon-core/src/main/java/org/apache/paimon/rest/requests/AlterTableRequest.java diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java index 11314bb1532c..9fda4c67e903 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java @@ -18,17 +18,24 @@ package org.apache.paimon.rest; +import org.apache.paimon.schema.SchemaChange; import org.apache.paimon.types.DataField; import org.apache.paimon.types.DataType; import org.apache.paimon.types.DataTypeJsonParser; +import org.apache.paimon.utils.JsonDeserializer; +import org.apache.paimon.utils.JsonSerializer; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.core.JsonGenerator; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.DeserializationFeature; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.JsonNode; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.Module; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.ObjectMapper; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.SerializationFeature; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.module.SimpleModule; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + import static org.apache.paimon.utils.JsonSerdeUtil.registerJsonObjects; /** Object mapper for REST request and response. */ @@ -52,6 +59,34 @@ public static Module createPaimonRestJacksonModule() { DataTypeJsonParser::parseDataField); registerJsonObjects( module, DataType.class, DataType::serializeJson, DataTypeJsonParser::parseDataType); + registerJsonObjects( + module, + SchemaChange.class, + SchemaChangeSerializer.getInstance(), + SchemaChangeSerializer.getInstance()); return module; } + + public static class SchemaChangeSerializer + implements JsonSerializer, JsonDeserializer { + + public static final SchemaChangeSerializer INSTANCE = new SchemaChangeSerializer(); + + public static SchemaChangeSerializer getInstance() { + return INSTANCE; + } + + @Override + public SchemaChange deserialize(JsonNode node) { + return SchemaChangeParser.fromJson(node); + } + + @Override + public void serialize(SchemaChange schemaChange, JsonGenerator generator) + throws IOException { + SchemaChangeParser.toJson(schemaChange, generator); + } + + private SchemaChangeSerializer() {} + } } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java b/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java new file mode 100644 index 000000000000..e252f05fb830 --- /dev/null +++ b/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java @@ -0,0 +1,166 @@ +/* + * 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.paimon.rest; + +import org.apache.paimon.schema.SchemaChange; +import org.apache.paimon.types.DataType; +import org.apache.paimon.types.DataTypeJsonParser; +import org.apache.paimon.utils.JsonSerdeUtil; + +import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableMap; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.core.JsonGenerator; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.JsonNode; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.apache.paimon.utils.Preconditions.checkArgument; + +/** Parser for schema change. */ +public class SchemaChangeParser { + private static final String ACTION = "action"; + + static final String SET_OPTION = "set-option"; + static final String REMOVE_OPTION = "remove-option"; + static final String UPDATE_COMMENT = "update-comment"; + static final String ADD_COLUMN = "add-column"; + static final String RENAME_COLUMN = "rename-column"; + static final String DROP_COLUMN = "drop-column"; + static final String UPDATE_COLUMN_TYPE = "update-column-type"; + static final String UPDATE_COLUMN_NULLABILITY = "update-column-nullability"; + static final String UPDATE_COLUMN_COMMENT = "update-column-comment"; + static final String UPDATE_COLUMN_POSITION = "update-column-position"; + + private static final String FIELD_FILED_NAMES = "fieldNames"; + private static final String FIELD_DATA_TYPE = "dataType"; + private static final String FIELD_COMMENT = "comment"; + private static final String FIELD_MOVE = "move"; + // move + private static final String FIELD_FILED_NAME = "fieldName"; + private static final String FIELD_REFERENCE_FIELD_NAME = "referenceFieldName"; + private static final String FIELD_TYPE = "type"; + + private static final Map, String> ACTIONS = + ImmutableMap., String>builder() + .put(SchemaChange.AddColumn.class, ADD_COLUMN) + .build(); + + public static void toJson(SchemaChange schemaChange, JsonGenerator generator) + throws IOException { + String updateAction = ACTIONS.get(schemaChange.getClass()); + + // Provide better exception message than the NPE thrown by writing null for the change + // action, + // which is required + checkArgument( + updateAction != null, + "Cannot convert schema change to json. Unrecognized schema change type: %s", + schemaChange.getClass().getName()); + + generator.writeStartObject(); + generator.writeStringField(ACTION, updateAction); + + switch (updateAction) { + case ADD_COLUMN: + writeAddColumn((SchemaChange.AddColumn) schemaChange, generator); + break; + default: + throw new IllegalArgumentException( + String.format( + "Cannot convert metadata update to json. Unrecognized action: %s", + updateAction)); + } + + generator.writeEndObject(); + } + + public static SchemaChange fromJson(String json) { + return JsonSerdeUtil.fromJson(json, SchemaChangeParser::fromJson); + } + + public static SchemaChange fromJson(JsonNode jsonNode) { + checkArgument( + jsonNode != null && jsonNode.isObject(), + "Cannot parse schema change from non-object value: %s", + jsonNode); + checkArgument( + jsonNode.hasNonNull(ACTION), "Cannot parse schema change. Missing field: action"); + String action = jsonNode.get(ACTION).asText().toLowerCase(Locale.ROOT); + + switch (action) { + case ADD_COLUMN: + return readAddColumn(jsonNode); + default: + throw new UnsupportedOperationException( + String.format("Cannot convert schema change action to json: %s", action)); + } + } + + private static SchemaChange readAddColumn(JsonNode node) { + Iterator fieldNamesJson = node.get(FIELD_FILED_NAMES).elements(); + List filedNames = new ArrayList<>(); + while (fieldNamesJson.hasNext()) { + filedNames.add(fieldNamesJson.next().asText()); + } + JsonNode dataTypeJson = node.get(FIELD_DATA_TYPE); + DataType dataType = DataTypeJsonParser.parseDataType(dataTypeJson); + String comment = node.has(FIELD_COMMENT) ? node.get(FIELD_COMMENT).asText() : null; + SchemaChange.Move move = node.has(FIELD_MOVE) ? readMove(node.get(FIELD_MOVE)) : null; + return SchemaChange.addColumn(filedNames.toArray(new String[0]), dataType, comment, move); + } + + private static SchemaChange.Move readMove(JsonNode node) { + String filedName = node.get(FIELD_FILED_NAME).asText(); + String referenceFieldName = node.get(FIELD_REFERENCE_FIELD_NAME).asText(); + SchemaChange.Move.MoveType type = + SchemaChange.Move.MoveType.valueOf(node.get(FIELD_TYPE).asText()); + return new SchemaChange.Move(filedName, referenceFieldName, type); + } + + private static void writeAddColumn(SchemaChange.AddColumn schemaChange, JsonGenerator generator) + throws IOException { + generator.writeArrayFieldStart(FIELD_FILED_NAMES); + for (String fieldName : schemaChange.fieldNames()) { + generator.writeString(fieldName); + } + generator.writeEndArray(); + generator.writeFieldName(FIELD_DATA_TYPE); + schemaChange.dataType().serializeJson(generator); + if (schemaChange.description() != null) { + generator.writeStringField(FIELD_COMMENT, schemaChange.description()); + } + if (schemaChange.move() != null) { + generator.writeFieldName(FIELD_MOVE); + writeMove(schemaChange.move(), generator); + } + } + + private static void writeMove(SchemaChange.Move move, JsonGenerator generator) + throws IOException { + generator.writeStartObject(); + generator.writeStringField(FIELD_FILED_NAME, move.fieldName()); + generator.writeStringField(FIELD_REFERENCE_FIELD_NAME, move.referenceFieldName()); + generator.writeStringField(FIELD_TYPE, move.type().name()); + generator.writeEndObject(); + } +} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/requests/AlterTableRequest.java b/paimon-core/src/main/java/org/apache/paimon/rest/requests/AlterTableRequest.java new file mode 100644 index 000000000000..32ec62c5eea5 --- /dev/null +++ b/paimon-core/src/main/java/org/apache/paimon/rest/requests/AlterTableRequest.java @@ -0,0 +1,49 @@ +/* + * 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.paimon.rest.requests; + +import org.apache.paimon.rest.RESTRequest; +import org.apache.paimon.schema.SchemaChange; + +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonGetter; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** Request for altering table. */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class AlterTableRequest implements RESTRequest { + + private static final String FIELD_NEW_UPDATE = "changes"; + + @JsonProperty(FIELD_NEW_UPDATE) + private final List changes; + + @JsonCreator + public AlterTableRequest(@JsonProperty(FIELD_NEW_UPDATE) List changes) { + this.changes = changes; + } + + @JsonGetter(FIELD_NEW_UPDATE) + public List getChanges() { + return changes; + } +} diff --git a/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java b/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java index edc6dac5f992..67df86dbcf95 100644 --- a/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java +++ b/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java @@ -138,6 +138,22 @@ public static T fromJson(String json, Class clazz) { } } + /** + * Helper for parsing JSON from a String. + * + * @param json a JSON string + * @param parser a function that converts a JsonNode to a Java object + * @param type of objects created by the parser + * @return the parsed Java object + */ + public static T fromJson(String json, FromJson parser) { + try { + return parser.parse(OBJECT_MAPPER_INSTANCE.readValue(json, JsonNode.class)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + public static String toJson(T t) { try { return OBJECT_MAPPER_INSTANCE.writerWithDefaultPrettyPrinter().writeValueAsString(t); @@ -263,5 +279,10 @@ public static boolean isNull(JsonNode jsonNode) { return jsonNode == null || jsonNode.isNull(); } + @FunctionalInterface + public interface FromJson { + T parse(JsonNode node) throws JsonProcessingException; + } + private JsonSerdeUtil() {} } From c4a5bab20832f893f8f5dda50db6d6dc8ff58597 Mon Sep 17 00:00:00 2001 From: yantian Date: Wed, 25 Dec 2024 17:04:58 +0800 Subject: [PATCH 2/7] support other schema change type and update rename table url --- .../org/apache/paimon/rest/RESTCatalog.java | 34 ++- .../org/apache/paimon/rest/ResourcePaths.java | 11 + .../paimon/rest/SchemaChangeParser.java | 286 +++++++++++++++--- .../apache/paimon/schema/SchemaChange.java | 93 +----- .../apache/paimon/rest/MockRESTMessage.java | 8 + .../apache/paimon/rest/RESTCatalogTest.java | 30 +- .../paimon/rest/RESTObjectMapperTest.java | 9 + paimon-open-api/rest-catalog-open-api.yaml | 178 ++++++++++- .../open/api/RESTCatalogController.java | 35 ++- 9 files changed, 542 insertions(+), 142 deletions(-) diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java index b98b0e7078f8..e0b3dccdd56b 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java @@ -39,6 +39,7 @@ import org.apache.paimon.rest.exceptions.ForbiddenException; import org.apache.paimon.rest.exceptions.NoSuchResourceException; import org.apache.paimon.rest.requests.AlterDatabaseRequest; +import org.apache.paimon.rest.requests.AlterTableRequest; import org.apache.paimon.rest.requests.CreateDatabaseRequest; import org.apache.paimon.rest.requests.CreateTableRequest; import org.apache.paimon.rest.requests.RenameTableRequest; @@ -302,7 +303,13 @@ public void createTable(Identifier identifier, Schema schema, boolean ignoreIfEx public void renameTable(Identifier fromTable, Identifier toTable, boolean ignoreIfNotExists) throws TableNotExistException, TableAlreadyExistException { try { - renameTable(fromTable, toTable); + RenameTableRequest request = new RenameTableRequest(toTable); + client.post( + resourcePaths.renameTable( + fromTable.getDatabaseName(), fromTable.getTableName()), + request, + GetTableResponse.class, + headers()); } catch (NoSuchResourceException e) { if (!ignoreIfNotExists) { throw new TableNotExistException(fromTable); @@ -318,7 +325,20 @@ public void renameTable(Identifier fromTable, Identifier toTable, boolean ignore public void alterTable( Identifier identifier, List changes, boolean ignoreIfNotExists) throws TableNotExistException, ColumnAlreadyExistException, ColumnNotExistException { - throw new UnsupportedOperationException("TODO"); + try { + AlterTableRequest request = new AlterTableRequest(changes); + client.post( + resourcePaths.table(identifier.getDatabaseName(), identifier.getTableName()), + request, + GetTableResponse.class, + headers()); + } catch (NoSuchResourceException e) { + if (!ignoreIfNotExists) { + throw new TableNotExistException(identifier); + } + } catch (ForbiddenException e) { + throw new TableNoPermissionException(identifier, e); + } } @Override @@ -376,16 +396,6 @@ Map fetchOptionsFromServer( return response.merge(clientProperties); } - @VisibleForTesting - void renameTable(Identifier fromTable, Identifier newIdentifier) { - RenameTableRequest request = new RenameTableRequest(newIdentifier); - client.post( - resourcePaths.table(fromTable.getDatabaseName(), fromTable.getTableName()), - request, - GetTableResponse.class, - headers()); - } - @VisibleForTesting Table getDataOrFormatTable(Identifier identifier) throws TableNotExistException { Preconditions.checkArgument(identifier.getSystemTableName() == null); diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java b/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java index 567dfea49046..f006713fe2b6 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java @@ -71,4 +71,15 @@ public String table(String databaseName, String tableName) { .add(tableName) .toString(); } + + public String renameTable(String databaseName, String tableName) { + return SLASH.add("v1") + .add(prefix) + .add("databases") + .add(databaseName) + .add("tables") + .add(tableName) + .add("rename") + .toString(); + } } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java b/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java index e252f05fb830..ef039aa269e3 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java @@ -34,40 +34,54 @@ import java.util.Locale; import java.util.Map; +import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.ADD_COLUMN; +import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.DROP_COLUMN; +import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.REMOVE_OPTION; +import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.RENAME_COLUMN; +import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.SET_OPTION; +import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.UPDATE_COLUMN_COMMENT; +import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.UPDATE_COLUMN_NULLABILITY; +import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.UPDATE_COLUMN_TYPE; +import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.UPDATE_COMMENT; import static org.apache.paimon.utils.Preconditions.checkArgument; /** Parser for schema change. */ public class SchemaChangeParser { private static final String ACTION = "action"; - static final String SET_OPTION = "set-option"; - static final String REMOVE_OPTION = "remove-option"; - static final String UPDATE_COMMENT = "update-comment"; - static final String ADD_COLUMN = "add-column"; - static final String RENAME_COLUMN = "rename-column"; - static final String DROP_COLUMN = "drop-column"; - static final String UPDATE_COLUMN_TYPE = "update-column-type"; - static final String UPDATE_COLUMN_NULLABILITY = "update-column-nullability"; - static final String UPDATE_COLUMN_COMMENT = "update-column-comment"; - static final String UPDATE_COLUMN_POSITION = "update-column-position"; - + private static final String FIELD_REMOVE_OPTION = "removeOptionKey"; private static final String FIELD_FILED_NAMES = "fieldNames"; private static final String FIELD_DATA_TYPE = "dataType"; private static final String FIELD_COMMENT = "comment"; private static final String FIELD_MOVE = "move"; + private static final String FIELD_NEW_NAME = "newName"; + private static final String FIELD_NEW_DATA_TYPE = "newDataType"; + private static final String FIELD_KEEP_NULLABILITY = "keepNullability"; + private static final String FIELD_NEW_NULLABILITY = "newNullability"; // move private static final String FIELD_FILED_NAME = "fieldName"; private static final String FIELD_REFERENCE_FIELD_NAME = "referenceFieldName"; private static final String FIELD_TYPE = "type"; - private static final Map, String> ACTIONS = - ImmutableMap., String>builder() + private static final Map, SchemaChangeActionEnum> ACTIONS = + ImmutableMap., SchemaChangeActionEnum>builder() + .put(SchemaChange.SetOption.class, SET_OPTION) + .put(SchemaChange.RemoveOption.class, REMOVE_OPTION) + .put(SchemaChange.UpdateComment.class, UPDATE_COMMENT) .put(SchemaChange.AddColumn.class, ADD_COLUMN) + .put(SchemaChange.RenameColumn.class, RENAME_COLUMN) + .put(SchemaChange.DropColumn.class, DROP_COLUMN) + .put(SchemaChange.UpdateColumnType.class, UPDATE_COLUMN_TYPE) + .put(SchemaChange.UpdateColumnNullability.class, UPDATE_COLUMN_NULLABILITY) + .put(SchemaChange.UpdateColumnComment.class, UPDATE_COLUMN_COMMENT) + .put( + SchemaChange.UpdateColumnPosition.class, + SchemaChangeActionEnum.UPDATE_COLUMN_POSITION) .build(); public static void toJson(SchemaChange schemaChange, JsonGenerator generator) throws IOException { - String updateAction = ACTIONS.get(schemaChange.getClass()); + SchemaChangeActionEnum updateAction = ACTIONS.get(schemaChange.getClass()); // Provide better exception message than the NPE thrown by writing null for the change // action, @@ -78,12 +92,42 @@ public static void toJson(SchemaChange schemaChange, JsonGenerator generator) schemaChange.getClass().getName()); generator.writeStartObject(); - generator.writeStringField(ACTION, updateAction); + generator.writeStringField(ACTION, updateAction.name().toLowerCase(Locale.ROOT)); switch (updateAction) { + case SET_OPTION: + writeSetOption((SchemaChange.SetOption) schemaChange, generator); + break; + case REMOVE_OPTION: + writeRemoveOption((SchemaChange.RemoveOption) schemaChange, generator); + break; + case UPDATE_COMMENT: + writeUpdateComment((SchemaChange.UpdateComment) schemaChange, generator); + break; case ADD_COLUMN: writeAddColumn((SchemaChange.AddColumn) schemaChange, generator); break; + case RENAME_COLUMN: + writeRenameColumn((SchemaChange.RenameColumn) schemaChange, generator); + break; + case DROP_COLUMN: + writeDropColumn((SchemaChange.DropColumn) schemaChange, generator); + break; + case UPDATE_COLUMN_TYPE: + writeUpdateColumnType((SchemaChange.UpdateColumnType) schemaChange, generator); + break; + case UPDATE_COLUMN_NULLABILITY: + writeUpdateColumnNullability( + (SchemaChange.UpdateColumnNullability) schemaChange, generator); + break; + case UPDATE_COLUMN_COMMENT: + writeUpdateColumnComment( + (SchemaChange.UpdateColumnComment) schemaChange, generator); + break; + case UPDATE_COLUMN_POSITION: + writeUpdateColumnPosition( + (SchemaChange.UpdateColumnPosition) schemaChange, generator); + break; default: throw new IllegalArgumentException( String.format( @@ -105,45 +149,72 @@ public static SchemaChange fromJson(JsonNode jsonNode) { jsonNode); checkArgument( jsonNode.hasNonNull(ACTION), "Cannot parse schema change. Missing field: action"); - String action = jsonNode.get(ACTION).asText().toLowerCase(Locale.ROOT); + SchemaChangeActionEnum action = + SchemaChangeActionEnum.fromString(jsonNode.get(ACTION).asText()); switch (action) { + case SET_OPTION: + return readSetOption(jsonNode); + case REMOVE_OPTION: + return readRemoveOption(jsonNode); + case UPDATE_COMMENT: + return readUpdateComment(jsonNode); case ADD_COLUMN: return readAddColumn(jsonNode); + case RENAME_COLUMN: + return readRenameColumn(jsonNode); + case DROP_COLUMN: + return readDropColumn(jsonNode); + case UPDATE_COLUMN_TYPE: + return readUpdateColumnType(jsonNode); + case UPDATE_COLUMN_NULLABILITY: + return readUpdateColumnNullability(jsonNode); + case UPDATE_COLUMN_COMMENT: + return readUpdateColumnComment(jsonNode); + case UPDATE_COLUMN_POSITION: + return readUpdateColumnPosition(jsonNode); default: throw new UnsupportedOperationException( String.format("Cannot convert schema change action to json: %s", action)); } } - private static SchemaChange readAddColumn(JsonNode node) { - Iterator fieldNamesJson = node.get(FIELD_FILED_NAMES).elements(); - List filedNames = new ArrayList<>(); - while (fieldNamesJson.hasNext()) { - filedNames.add(fieldNamesJson.next().asText()); + /** Schema change action type. */ + public enum SchemaChangeActionEnum { + SET_OPTION, + REMOVE_OPTION, + UPDATE_COMMENT, + ADD_COLUMN, + RENAME_COLUMN, + DROP_COLUMN, + UPDATE_COLUMN_TYPE, + UPDATE_COLUMN_NULLABILITY, + UPDATE_COLUMN_COMMENT, + UPDATE_COLUMN_POSITION; + + public static SchemaChangeActionEnum fromString(String action) { + return SchemaChangeActionEnum.valueOf(action.toUpperCase()); } - JsonNode dataTypeJson = node.get(FIELD_DATA_TYPE); - DataType dataType = DataTypeJsonParser.parseDataType(dataTypeJson); - String comment = node.has(FIELD_COMMENT) ? node.get(FIELD_COMMENT).asText() : null; - SchemaChange.Move move = node.has(FIELD_MOVE) ? readMove(node.get(FIELD_MOVE)) : null; - return SchemaChange.addColumn(filedNames.toArray(new String[0]), dataType, comment, move); } - private static SchemaChange.Move readMove(JsonNode node) { - String filedName = node.get(FIELD_FILED_NAME).asText(); - String referenceFieldName = node.get(FIELD_REFERENCE_FIELD_NAME).asText(); - SchemaChange.Move.MoveType type = - SchemaChange.Move.MoveType.valueOf(node.get(FIELD_TYPE).asText()); - return new SchemaChange.Move(filedName, referenceFieldName, type); + private static void writeSetOption(SchemaChange.SetOption schemaChange, JsonGenerator generator) + throws IOException { + generator.writeStringField(schemaChange.key(), schemaChange.value()); + } + + private static void writeRemoveOption( + SchemaChange.RemoveOption schemaChange, JsonGenerator generator) throws IOException { + generator.writeStringField(FIELD_REMOVE_OPTION, schemaChange.key()); + } + + private static void writeUpdateComment( + SchemaChange.UpdateComment schemaChange, JsonGenerator generator) throws IOException { + generator.writeStringField(FIELD_COMMENT, schemaChange.comment()); } private static void writeAddColumn(SchemaChange.AddColumn schemaChange, JsonGenerator generator) throws IOException { - generator.writeArrayFieldStart(FIELD_FILED_NAMES); - for (String fieldName : schemaChange.fieldNames()) { - generator.writeString(fieldName); - } - generator.writeEndArray(); + writeFiledNames(schemaChange.fieldNames(), generator); generator.writeFieldName(FIELD_DATA_TYPE); schemaChange.dataType().serializeJson(generator); if (schemaChange.description() != null) { @@ -155,6 +226,15 @@ private static void writeAddColumn(SchemaChange.AddColumn schemaChange, JsonGene } } + private static void writeFiledNames(String[] fieldNames, JsonGenerator generator) + throws IOException { + generator.writeArrayFieldStart(FIELD_FILED_NAMES); + for (String fieldName : fieldNames) { + generator.writeString(fieldName); + } + generator.writeEndArray(); + } + private static void writeMove(SchemaChange.Move move, JsonGenerator generator) throws IOException { generator.writeStartObject(); @@ -163,4 +243,136 @@ private static void writeMove(SchemaChange.Move move, JsonGenerator generator) generator.writeStringField(FIELD_TYPE, move.type().name()); generator.writeEndObject(); } + + private static void writeRenameColumn( + SchemaChange.RenameColumn schemaChange, JsonGenerator generator) throws IOException { + writeFiledNames(schemaChange.fieldNames(), generator); + generator.writeStringField(FIELD_NEW_NAME, schemaChange.newName()); + } + + private static void writeDropColumn( + SchemaChange.DropColumn schemaChange, JsonGenerator generator) throws IOException { + writeFiledNames(schemaChange.fieldNames(), generator); + } + + private static void writeUpdateColumnType( + SchemaChange.UpdateColumnType schemaChange, JsonGenerator generator) + throws IOException { + writeFiledNames(schemaChange.fieldNames(), generator); + generator.writeFieldName(FIELD_NEW_DATA_TYPE); + schemaChange.newDataType().serializeJson(generator); + generator.writeBooleanField(FIELD_KEEP_NULLABILITY, schemaChange.keepNullability()); + } + + private static void writeUpdateColumnNullability( + SchemaChange.UpdateColumnNullability schemaChange, JsonGenerator generator) + throws IOException { + writeFiledNames(schemaChange.fieldNames(), generator); + generator.writeBooleanField(FIELD_NEW_NULLABILITY, schemaChange.newNullability()); + } + + private static void writeUpdateColumnComment( + SchemaChange.UpdateColumnComment schemaChange, JsonGenerator generator) + throws IOException { + writeFiledNames(schemaChange.fieldNames(), generator); + generator.writeStringField(FIELD_COMMENT, schemaChange.newDescription()); + } + + private static void writeUpdateColumnPosition( + SchemaChange.UpdateColumnPosition schemaChange, JsonGenerator generator) + throws IOException { + if (schemaChange.move() != null) { + generator.writeFieldName(FIELD_MOVE); + writeMove(schemaChange.move(), generator); + } + } + + private static SchemaChange readSetOption(JsonNode node) { + Iterator fieldNames = node.fieldNames(); + String key = null; + String value = null; + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + if (ACTION.equals(fieldName)) { + continue; + } + key = fieldName; + value = node.get(fieldName).asText(); + } + return SchemaChange.setOption(key, value); + } + + private static SchemaChange readRemoveOption(JsonNode node) { + return SchemaChange.removeOption(node.get(FIELD_REMOVE_OPTION).asText()); + } + + private static SchemaChange readUpdateComment(JsonNode node) { + String comment = node.has(FIELD_COMMENT) ? node.get(FIELD_COMMENT).asText() : null; + return SchemaChange.updateComment(comment); + } + + private static SchemaChange readAddColumn(JsonNode node) { + String[] fieldNames = getFieldNames(node); + JsonNode dataTypeJson = node.get(FIELD_DATA_TYPE); + DataType dataType = DataTypeJsonParser.parseDataType(dataTypeJson); + String comment = node.has(FIELD_COMMENT) ? node.get(FIELD_COMMENT).asText() : null; + SchemaChange.Move move = node.has(FIELD_MOVE) ? readMove(node.get(FIELD_MOVE)) : null; + return SchemaChange.addColumn(fieldNames, dataType, comment, move); + } + + private static SchemaChange.Move readMove(JsonNode node) { + String filedName = node.get(FIELD_FILED_NAME).asText(); + String referenceFieldName = node.get(FIELD_REFERENCE_FIELD_NAME).asText(); + SchemaChange.Move.MoveType type = + SchemaChange.Move.MoveType.valueOf(node.get(FIELD_TYPE).asText()); + return new SchemaChange.Move(filedName, referenceFieldName, type); + } + + private static SchemaChange readRenameColumn(JsonNode node) { + String[] fieldNames = getFieldNames(node); + String newName = node.get(FIELD_NEW_NAME).asText(); + return SchemaChange.renameColumn(fieldNames, newName); + } + + private static SchemaChange readDropColumn(JsonNode node) { + String[] fieldNames = getFieldNames(node); + return SchemaChange.dropColumn(fieldNames); + } + + private static SchemaChange readUpdateColumnType(JsonNode node) { + String[] fieldNames = getFieldNames(node); + JsonNode dataTypeJson = node.get(FIELD_NEW_DATA_TYPE); + DataType newDataType = DataTypeJsonParser.parseDataType(dataTypeJson); + boolean keepNullability = + node.has(FIELD_KEEP_NULLABILITY) + ? node.get(FIELD_KEEP_NULLABILITY).asBoolean() + : false; + return SchemaChange.updateColumnType(fieldNames, newDataType, keepNullability); + } + + private static SchemaChange readUpdateColumnNullability(JsonNode node) { + String[] fieldNames = getFieldNames(node); + boolean newNullability = node.get(FIELD_NEW_NULLABILITY).asBoolean(); + return SchemaChange.updateColumnNullability(fieldNames, newNullability); + } + + private static SchemaChange readUpdateColumnComment(JsonNode node) { + String[] fieldNames = getFieldNames(node); + String comment = node.get(FIELD_COMMENT).asText(); + return SchemaChange.updateColumnComment(fieldNames, comment); + } + + private static SchemaChange readUpdateColumnPosition(JsonNode node) { + SchemaChange.Move move = node.has(FIELD_MOVE) ? readMove(node.get(FIELD_MOVE)) : null; + return SchemaChange.updateColumnPosition(move); + } + + private static String[] getFieldNames(JsonNode jsonNode) { + Iterator fieldNamesJson = jsonNode.get(FIELD_FILED_NAMES).elements(); + List filedNames = new ArrayList<>(); + while (fieldNamesJson.hasNext()) { + filedNames.add(fieldNamesJson.next().asText()); + } + return filedNames.toArray(new String[0]); + } } diff --git a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java index 5f6b10457541..358f9b041769 100644 --- a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java +++ b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java @@ -22,9 +22,7 @@ import org.apache.paimon.types.DataType; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator; -import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonGetter; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonProperty; import javax.annotation.Nullable; @@ -226,58 +224,39 @@ public int hashCode() { } /** A SchemaChange to add a field. */ - @JsonIgnoreProperties(ignoreUnknown = true) final class AddColumn implements SchemaChange { private static final long serialVersionUID = 1L; - private static final String FIELD_FILED_NAMES = "fieldNames"; - private static final String FIELD_DATA_TYPE = "dataType"; - private static final String FIELD_COMMENT = "comment"; - private static final String FIELD_MOVE = "move"; - - @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; - @JsonProperty(FIELD_DATA_TYPE) private final DataType dataType; - @JsonProperty(FIELD_COMMENT) private final String description; - @JsonProperty(FIELD_MOVE) private final Move move; - @JsonCreator - private AddColumn( - @JsonProperty(FIELD_FILED_NAMES) String[] fieldNames, - @JsonProperty(FIELD_DATA_TYPE) DataType dataType, - @JsonProperty(FIELD_COMMENT) String description, - @JsonProperty(FIELD_MOVE) Move move) { + private AddColumn(String[] fieldNames, DataType dataType, String description, Move move) { this.fieldNames = fieldNames; this.dataType = dataType; this.description = description; this.move = move; } - @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } - @JsonGetter(FIELD_DATA_TYPE) public DataType dataType() { return dataType; } @Nullable - @JsonGetter(FIELD_COMMENT) public String description() { return description; } @Nullable - @JsonGetter(FIELD_MOVE) public Move move() { return move; } @@ -312,29 +291,20 @@ final class RenameColumn implements SchemaChange { private static final long serialVersionUID = 1L; - private static final String FIELD_FILED_NAMES = "fieldNames"; - private static final String FIELD_NEW_NAME = "newName"; - - @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; - @JsonProperty(FIELD_NEW_NAME) private final String newName; @JsonCreator - private RenameColumn( - @JsonProperty(FIELD_FILED_NAMES) String[] fieldNames, - @JsonProperty(FIELD_NEW_NAME) String newName) { + private RenameColumn(String[] fieldNames, String newName) { this.fieldNames = fieldNames; this.newName = newName; } - @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } - @JsonGetter(FIELD_NEW_NAME) public String newName() { return newName; } @@ -366,17 +336,13 @@ final class DropColumn implements SchemaChange { private static final long serialVersionUID = 1L; - private static final String FIELD_FILED_NAMES = "fieldNames"; - - @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; @JsonCreator - private DropColumn(@JsonProperty(FIELD_FILED_NAMES) String[] fieldNames) { + private DropColumn(String[] fieldNames) { this.fieldNames = fieldNames; } - @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } @@ -400,44 +366,32 @@ public int hashCode() { } /** A SchemaChange to update the field type. */ - @JsonIgnoreProperties(ignoreUnknown = true) final class UpdateColumnType implements SchemaChange { private static final long serialVersionUID = 1L; - private static final String FIELD_FILED_NAMES = "fieldNames"; - private static final String FIELD_NEW_DATA_TYPE = "newDataType"; - private static final String FIELD_KEEP_NULLABILITY = "keepNullability"; - @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; - @JsonProperty(FIELD_NEW_DATA_TYPE) private final DataType newDataType; // If true, do not change the target field nullability - @JsonProperty(FIELD_KEEP_NULLABILITY) private final boolean keepNullability; @JsonCreator private UpdateColumnType( - @JsonProperty(FIELD_FILED_NAMES) String[] fieldNames, - @JsonProperty(FIELD_NEW_DATA_TYPE) DataType newDataType, - @JsonProperty(FIELD_KEEP_NULLABILITY) boolean keepNullability) { + String[] fieldNames, DataType newDataType, boolean keepNullability) { this.fieldNames = fieldNames; this.newDataType = newDataType; this.keepNullability = keepNullability; } - @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } - @JsonGetter(FIELD_NEW_DATA_TYPE) public DataType newDataType() { return newDataType; } - @JsonGetter(FIELD_KEEP_NULLABILITY) public boolean keepNullability() { return keepNullability; } @@ -497,7 +451,6 @@ public int hashCode() { } /** Represents a requested column move in a struct. */ - @JsonIgnoreProperties(ignoreUnknown = true) class Move implements Serializable { public enum MoveType { @@ -525,40 +478,26 @@ public static Move last(String fieldName) { private static final long serialVersionUID = 1L; - private static final String FIELD_FILED_NAMES = "fieldName"; - private static final String FIELD_REFERENCE_FIELD_NAME = "referenceFieldName"; - private static final String FIELD_TYPE = "type"; - - @JsonProperty(FIELD_FILED_NAMES) private final String fieldName; - @JsonProperty(FIELD_REFERENCE_FIELD_NAME) private final String referenceFieldName; - @JsonProperty(FIELD_TYPE) private final MoveType type; - @JsonCreator - public Move( - @JsonProperty(FIELD_FILED_NAMES) String fieldName, - @JsonProperty(FIELD_REFERENCE_FIELD_NAME) String referenceFieldName, - @JsonProperty(FIELD_TYPE) MoveType type) { + public Move(String fieldName, String referenceFieldName, MoveType type) { this.fieldName = fieldName; this.referenceFieldName = referenceFieldName; this.type = type; } - @JsonGetter(FIELD_FILED_NAMES) public String fieldName() { return fieldName; } - @JsonGetter(FIELD_REFERENCE_FIELD_NAME) public String referenceFieldName() { return referenceFieldName; } - @JsonGetter(FIELD_TYPE) public MoveType type() { return type; } @@ -589,29 +528,20 @@ final class UpdateColumnNullability implements SchemaChange { private static final long serialVersionUID = 1L; - private static final String FIELD_FILED_NAMES = "fieldNames"; - private static final String FIELD_NEW_NULLABILITY = "newNullability"; - - @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; - @JsonProperty(FIELD_NEW_NULLABILITY) private final boolean newNullability; @JsonCreator - public UpdateColumnNullability( - @JsonProperty(FIELD_FILED_NAMES) String[] fieldNames, - @JsonProperty(FIELD_NEW_NULLABILITY) boolean newNullability) { + public UpdateColumnNullability(String[] fieldNames, boolean newNullability) { this.fieldNames = fieldNames; this.newNullability = newNullability; } - @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } - @JsonGetter(FIELD_NEW_NULLABILITY) public boolean newNullability() { return newNullability; } @@ -643,29 +573,20 @@ final class UpdateColumnComment implements SchemaChange { private static final long serialVersionUID = 1L; - private static final String FIELD_FILED_NAMES = "fieldNames"; - private static final String FIELD_NEW_COMMENT = "newComment"; - - @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; - @JsonProperty(FIELD_NEW_COMMENT) private final String newDescription; @JsonCreator - public UpdateColumnComment( - @JsonProperty(FIELD_FILED_NAMES) String[] fieldNames, - @JsonProperty(FIELD_NEW_COMMENT) String newDescription) { + public UpdateColumnComment(String[] fieldNames, String newDescription) { this.fieldNames = fieldNames; this.newDescription = newDescription; } - @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } - @JsonGetter(FIELD_NEW_COMMENT) public String newDescription() { return newDescription; } diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java index 6a9f2df7516c..96d0c9d7c724 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java @@ -20,6 +20,7 @@ import org.apache.paimon.catalog.Identifier; import org.apache.paimon.rest.requests.AlterDatabaseRequest; +import org.apache.paimon.rest.requests.AlterTableRequest; import org.apache.paimon.rest.requests.CreateDatabaseRequest; import org.apache.paimon.rest.requests.CreateTableRequest; import org.apache.paimon.rest.requests.RenameTableRequest; @@ -126,11 +127,17 @@ public static RenameTableRequest renameRequest(String toTableName) { return new RenameTableRequest(newIdentifier); } + public static AlterTableRequest alterTableRequest() { + return new AlterTableRequest(getChanges()); + } + public static List getChanges() { // add option SchemaChange addOption = SchemaChange.setOption("snapshot.time-retained", "2h"); // remove option SchemaChange removeOption = SchemaChange.removeOption("compaction.max.file-num"); + // update comment + SchemaChange updateComment = SchemaChange.updateComment(null); // add column SchemaChange addColumn = SchemaChange.addColumn("col1_after", DataTypes.ARRAY(DataTypes.STRING())); @@ -179,6 +186,7 @@ public static List getChanges() { List schemaChanges = new ArrayList<>(); schemaChanges.add(addOption); schemaChanges.add(removeOption); + schemaChanges.add(updateComment); schemaChanges.add(addColumn); schemaChanges.add(addColumnMap); schemaChanges.add(addColumnRowType); diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java index 95a991a74a68..01555adc3df4 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java @@ -32,6 +32,7 @@ import org.apache.paimon.rest.responses.GetTableResponse; import org.apache.paimon.rest.responses.ListDatabasesResponse; import org.apache.paimon.rest.responses.ListTablesResponse; +import org.apache.paimon.schema.SchemaChange; import org.apache.paimon.table.Table; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.core.JsonProcessingException; @@ -55,6 +56,8 @@ import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -279,7 +282,7 @@ public void testRenameTable() throws Exception { Identifier.create(databaseName, fromTableName), Identifier.create(databaseName, toTableName), true)); - verify(mockRestCatalog, times(1)).renameTable(any(), any()); + verify(mockRestCatalog, times(1)).renameTable(any(), any(), anyBoolean()); } @Test @@ -312,6 +315,31 @@ public void testRenameTableWhenToTableAlreadyExist() throws Exception { false)); } + @Test + public void testAlterTable() throws Exception { + String databaseName = MockRESTMessage.databaseName(); + List changes = MockRESTMessage.getChanges(); + GetTableResponse response = MockRESTMessage.getTableResponse(); + mockResponse(mapper.writeValueAsString(response), 200); + assertDoesNotThrow( + () -> + mockRestCatalog.alterTable( + Identifier.create(databaseName, "t1"), changes, true)); + verify(mockRestCatalog, times(1)).alterTable(any(), anyList(), anyBoolean()); + } + + @Test + public void testAlterTableWhenTableNotExistAndIgnoreIfNotExistsIsFalse() throws Exception { + String databaseName = MockRESTMessage.databaseName(); + List changes = MockRESTMessage.getChanges(); + mockResponse("", 404); + assertThrows( + Catalog.TableNotExistException.class, + () -> + mockRestCatalog.alterTable( + Identifier.create(databaseName, "t1"), changes, false)); + } + @Test public void testDropTable() throws Exception { String databaseName = MockRESTMessage.databaseName(); diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java index c2d67fd46e1e..26b3db615d11 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java @@ -19,6 +19,7 @@ package org.apache.paimon.rest; import org.apache.paimon.rest.requests.AlterDatabaseRequest; +import org.apache.paimon.rest.requests.AlterTableRequest; import org.apache.paimon.rest.requests.CreateDatabaseRequest; import org.apache.paimon.rest.requests.CreateTableRequest; import org.apache.paimon.rest.requests.RenameTableRequest; @@ -184,4 +185,12 @@ public void listTablesResponseParseTest() throws Exception { ListTablesResponse parseData = mapper.readValue(responseStr, ListTablesResponse.class); assertEquals(response.getTables(), parseData.getTables()); } + + @Test + public void alterTableRequestParseTest() throws Exception { + AlterTableRequest request = MockRESTMessage.alterTableRequest(); + String requestStr = mapper.writeValueAsString(request); + AlterTableRequest parseData = mapper.readValue(requestStr, AlterTableRequest.class); + assertEquals(parseData.getChanges().size(), parseData.getChanges().size()); + } } diff --git a/paimon-open-api/rest-catalog-open-api.yaml b/paimon-open-api/rest-catalog-open-api.yaml index de0faeeb4a09..e729265ea114 100644 --- a/paimon-open-api/rest-catalog-open-api.yaml +++ b/paimon-open-api/rest-catalog-open-api.yaml @@ -176,8 +176,8 @@ paths: post: tags: - table - summary: Rename table - operationId: renameTable + summary: Alter table + operationId: alterTable parameters: - name: prefix in: path @@ -198,7 +198,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RenameTableRequest' + $ref: '#/components/schemas/AlterTableRequest' responses: "200": description: OK @@ -233,6 +233,42 @@ paths: $ref: '#/components/schemas/ErrorResponse' "500": description: Internal Server Error + /v1/{prefix}/databases/{database}/tables/{table}/rename: + post: + tags: + - table + summary: Rename table + operationId: renameTable + parameters: + - name: prefix + in: path + required: true + schema: + type: string + - name: database + in: path + required: true + schema: + type: string + - name: table + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RenameTableRequest' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetTableResponse' + "500": + description: Internal Server Error /v1/{prefix}/databases/{database}/properties: post: tags: @@ -437,6 +473,142 @@ components: format: int64 schema: $ref: '#/components/schemas/Schema' + AlterTableRequest: + type: object + properties: + changes: + type: array + items: + $ref: '#/components/schemas/SchemaChange' + SchemaChange: + anyOf: + - $ref: '#/components/schemas/SetOption' + - $ref: '#/components/schemas/RemoveOption' + - $ref: '#/components/schemas/UpdateComment' + - $ref: '#/components/schemas/AddColumn' + - $ref: '#/components/schemas/RenameColumn' + - $ref: '#/components/schemas/DropColumn' + - $ref: '#/components/schemas/UpdateColumnComment' + - $ref: '#/components/schemas/UpdateColumnType' + - $ref: '#/components/schemas/UpdateColumnPosition' + - $ref: '#/components/schemas/UpdateColumnNullability' + SetOption: + type: object + properties: + action: + type: string + enum: ["set_option"] + additionalProperties: + type: string + RemoveOption: + type: object + properties: + action: + type: string + enum: ["remove_option"] + removeOptionKey: + type: string + UpdateComment: + type: object + properties: + action: + type: string + enum: ["update_comment"] + comment: + type: string + AddColumn: + type: object + properties: + action: + type: string + enum: ["add_column"] + fieldNames: + type: array + items: + type: string + dataType: + $ref: '#/components/schemas/DataType' + comment: + type: string + move: + $ref: '#/components/schemas/Move' + RenameColumn: + type: object + properties: + action: + type: string + enum: ["rename_column"] + fieldNames: + type: array + items: + type: string + newName: + type: string + DropColumn: + type: object + properties: + action: + type: string + enum: ["drop_column"] + fieldNames: + type: array + items: + type: string + UpdateColumnComment: + type: object + properties: + action: + type: string + enum: [ "update_column_comment" ] + fieldNames: + type: array + items: + type: string + comment: + type: string + UpdateColumnType: + type: object + properties: + action: + type: string + enum: [ "update_column_type" ] + fieldNames: + type: array + items: + type: string + dataType: + $ref: '#/components/schemas/DataType' + keepNullability: + type: boolean + UpdateColumnPosition: + type: object + properties: + action: + type: string + enum: [ "update_column_position" ] + move: + $ref: '#/components/schemas/Move' + UpdateColumnNullability: + type: object + properties: + action: + type: string + enum: [ "update_column_nullability" ] + fieldNames: + type: array + items: + type: string + newNullability: + type: boolean + Move: + type: object + properties: + fieldName: + type: string + referenceFieldName: + type: string + type: + type: string RenameTableRequest: type: object properties: diff --git a/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java b/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java index 3ea8faee8c52..495ac19e3b49 100644 --- a/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java +++ b/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java @@ -20,6 +20,7 @@ import org.apache.paimon.rest.ResourcePaths; import org.apache.paimon.rest.requests.AlterDatabaseRequest; +import org.apache.paimon.rest.requests.AlterTableRequest; import org.apache.paimon.rest.requests.CreateDatabaseRequest; import org.apache.paimon.rest.requests.CreateTableRequest; import org.apache.paimon.rest.requests.RenameTableRequest; @@ -261,7 +262,7 @@ public GetTableResponse createTable( } @Operation( - summary = "Update table", + summary = "Alter table", tags = {"table"}) @ApiResponses({ @ApiResponse( @@ -272,11 +273,11 @@ public GetTableResponse createTable( content = {@Content(schema = @Schema())}) }) @PostMapping("/v1/{prefix}/databases/{database}/tables/{table}") - public GetTableResponse renameTable( + public GetTableResponse alterTable( @PathVariable String prefix, @PathVariable String database, @PathVariable String table, - @RequestBody RenameTableRequest request) { + @RequestBody AlterTableRequest request) { return new GetTableResponse( "", 1, @@ -305,4 +306,32 @@ public void dropTable( @PathVariable String prefix, @PathVariable String database, @PathVariable String table) {} + + @Operation( + summary = "Rename table", + tags = {"table"}) + @ApiResponses({ + @ApiResponse( + responseCode = "200", + content = {@Content(schema = @Schema(implementation = GetTableResponse.class))}), + @ApiResponse( + responseCode = "500", + content = {@Content(schema = @Schema())}) + }) + @PostMapping("/v1/{prefix}/databases/{database}/tables/{table}/rename") + public GetTableResponse renameTable( + @PathVariable String prefix, + @PathVariable String database, + @PathVariable String table, + @RequestBody RenameTableRequest request) { + return new GetTableResponse( + "", + 1, + new org.apache.paimon.schema.Schema( + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + new HashMap<>(), + "comment")); + } } From e884a2fd08ceeafbb340dcee196dbf283addd705 Mon Sep 17 00:00:00 2001 From: yantian Date: Wed, 25 Dec 2024 17:12:25 +0800 Subject: [PATCH 3/7] fix checkstyle fail --- .../main/java/org/apache/paimon/rest/RESTObjectMapper.java | 1 + .../src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java index 9fda4c67e903..cbee33da41f4 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java @@ -67,6 +67,7 @@ public static Module createPaimonRestJacksonModule() { return module; } + /** Serializer for SchemaChange. */ public static class SchemaChangeSerializer implements JsonSerializer, JsonDeserializer { diff --git a/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java b/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java index 67df86dbcf95..8539ee8133a4 100644 --- a/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java +++ b/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java @@ -279,6 +279,11 @@ public static boolean isNull(JsonNode jsonNode) { return jsonNode == null || jsonNode.isNull(); } + /** + * A functional interface for parsing JSON data into a specific object type. + * + * @param The type of the object to be parsed from JSON. + */ @FunctionalInterface public interface FromJson { T parse(JsonNode node) throws JsonProcessingException; From 2634e87fec429d6dfe50ee7090d16545b3b999df Mon Sep 17 00:00:00 2001 From: yantian Date: Thu, 26 Dec 2024 10:50:11 +0800 Subject: [PATCH 4/7] use annotation to support json parse SchemaChange --- .../apache/paimon/rest/RESTObjectMapper.java | 36 -- .../paimon/rest/SchemaChangeActions.java | 33 ++ .../paimon/rest/SchemaChangeParser.java | 378 ------------------ .../apache/paimon/schema/SchemaChange.java | 162 +++++++- .../apache/paimon/utils/JsonSerdeUtil.java | 26 -- paimon-open-api/rest-catalog-open-api.yaml | 52 ++- .../open/api/RESTCatalogController.java | 22 +- 7 files changed, 240 insertions(+), 469 deletions(-) create mode 100644 paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeActions.java delete mode 100644 paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java index cbee33da41f4..11314bb1532c 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTObjectMapper.java @@ -18,24 +18,17 @@ package org.apache.paimon.rest; -import org.apache.paimon.schema.SchemaChange; import org.apache.paimon.types.DataField; import org.apache.paimon.types.DataType; import org.apache.paimon.types.DataTypeJsonParser; -import org.apache.paimon.utils.JsonDeserializer; -import org.apache.paimon.utils.JsonSerializer; -import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.core.JsonGenerator; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.DeserializationFeature; -import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.JsonNode; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.Module; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.ObjectMapper; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.SerializationFeature; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.module.SimpleModule; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import java.io.IOException; - import static org.apache.paimon.utils.JsonSerdeUtil.registerJsonObjects; /** Object mapper for REST request and response. */ @@ -59,35 +52,6 @@ public static Module createPaimonRestJacksonModule() { DataTypeJsonParser::parseDataField); registerJsonObjects( module, DataType.class, DataType::serializeJson, DataTypeJsonParser::parseDataType); - registerJsonObjects( - module, - SchemaChange.class, - SchemaChangeSerializer.getInstance(), - SchemaChangeSerializer.getInstance()); return module; } - - /** Serializer for SchemaChange. */ - public static class SchemaChangeSerializer - implements JsonSerializer, JsonDeserializer { - - public static final SchemaChangeSerializer INSTANCE = new SchemaChangeSerializer(); - - public static SchemaChangeSerializer getInstance() { - return INSTANCE; - } - - @Override - public SchemaChange deserialize(JsonNode node) { - return SchemaChangeParser.fromJson(node); - } - - @Override - public void serialize(SchemaChange schemaChange, JsonGenerator generator) - throws IOException { - SchemaChangeParser.toJson(schemaChange, generator); - } - - private SchemaChangeSerializer() {} - } } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeActions.java b/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeActions.java new file mode 100644 index 000000000000..cbf7612ff307 --- /dev/null +++ b/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeActions.java @@ -0,0 +1,33 @@ +/* + * 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.paimon.rest; + +public class SchemaChangeActions { + public static final String FIELD_ACTION = "action"; + public static final String SET_OPTION_ACTION = "setOption"; + public static final String REMOVE_OPTION_ACTION = "removeOption"; + public static final String UPDATE_COMMENT_ACTION = "updateComment"; + public static final String ADD_COLUMN_ACTION = "addColumn"; + public static final String RENAME_COLUMN_ACTION = "renameColumn"; + public static final String DROP_COLUMN_ACTION = "dropColumn"; + public static final String UPDATE_COLUMN_TYPE_ACTION = "updateColumnType"; + public static final String UPDATE_COLUMN_NULLABILITY_ACTION = "updateColumnNullability"; + public static final String UPDATE_COLUMN_COMMENT_ACTION = "updateColumnComment"; + public static final String UPDATE_COLUMN_POSITION_ACTION = "updateColumnPosition"; +} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java b/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java deleted file mode 100644 index ef039aa269e3..000000000000 --- a/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeParser.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * 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.paimon.rest; - -import org.apache.paimon.schema.SchemaChange; -import org.apache.paimon.types.DataType; -import org.apache.paimon.types.DataTypeJsonParser; -import org.apache.paimon.utils.JsonSerdeUtil; - -import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableMap; -import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.core.JsonGenerator; -import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.JsonNode; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.ADD_COLUMN; -import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.DROP_COLUMN; -import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.REMOVE_OPTION; -import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.RENAME_COLUMN; -import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.SET_OPTION; -import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.UPDATE_COLUMN_COMMENT; -import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.UPDATE_COLUMN_NULLABILITY; -import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.UPDATE_COLUMN_TYPE; -import static org.apache.paimon.rest.SchemaChangeParser.SchemaChangeActionEnum.UPDATE_COMMENT; -import static org.apache.paimon.utils.Preconditions.checkArgument; - -/** Parser for schema change. */ -public class SchemaChangeParser { - private static final String ACTION = "action"; - - private static final String FIELD_REMOVE_OPTION = "removeOptionKey"; - private static final String FIELD_FILED_NAMES = "fieldNames"; - private static final String FIELD_DATA_TYPE = "dataType"; - private static final String FIELD_COMMENT = "comment"; - private static final String FIELD_MOVE = "move"; - private static final String FIELD_NEW_NAME = "newName"; - private static final String FIELD_NEW_DATA_TYPE = "newDataType"; - private static final String FIELD_KEEP_NULLABILITY = "keepNullability"; - private static final String FIELD_NEW_NULLABILITY = "newNullability"; - // move - private static final String FIELD_FILED_NAME = "fieldName"; - private static final String FIELD_REFERENCE_FIELD_NAME = "referenceFieldName"; - private static final String FIELD_TYPE = "type"; - - private static final Map, SchemaChangeActionEnum> ACTIONS = - ImmutableMap., SchemaChangeActionEnum>builder() - .put(SchemaChange.SetOption.class, SET_OPTION) - .put(SchemaChange.RemoveOption.class, REMOVE_OPTION) - .put(SchemaChange.UpdateComment.class, UPDATE_COMMENT) - .put(SchemaChange.AddColumn.class, ADD_COLUMN) - .put(SchemaChange.RenameColumn.class, RENAME_COLUMN) - .put(SchemaChange.DropColumn.class, DROP_COLUMN) - .put(SchemaChange.UpdateColumnType.class, UPDATE_COLUMN_TYPE) - .put(SchemaChange.UpdateColumnNullability.class, UPDATE_COLUMN_NULLABILITY) - .put(SchemaChange.UpdateColumnComment.class, UPDATE_COLUMN_COMMENT) - .put( - SchemaChange.UpdateColumnPosition.class, - SchemaChangeActionEnum.UPDATE_COLUMN_POSITION) - .build(); - - public static void toJson(SchemaChange schemaChange, JsonGenerator generator) - throws IOException { - SchemaChangeActionEnum updateAction = ACTIONS.get(schemaChange.getClass()); - - // Provide better exception message than the NPE thrown by writing null for the change - // action, - // which is required - checkArgument( - updateAction != null, - "Cannot convert schema change to json. Unrecognized schema change type: %s", - schemaChange.getClass().getName()); - - generator.writeStartObject(); - generator.writeStringField(ACTION, updateAction.name().toLowerCase(Locale.ROOT)); - - switch (updateAction) { - case SET_OPTION: - writeSetOption((SchemaChange.SetOption) schemaChange, generator); - break; - case REMOVE_OPTION: - writeRemoveOption((SchemaChange.RemoveOption) schemaChange, generator); - break; - case UPDATE_COMMENT: - writeUpdateComment((SchemaChange.UpdateComment) schemaChange, generator); - break; - case ADD_COLUMN: - writeAddColumn((SchemaChange.AddColumn) schemaChange, generator); - break; - case RENAME_COLUMN: - writeRenameColumn((SchemaChange.RenameColumn) schemaChange, generator); - break; - case DROP_COLUMN: - writeDropColumn((SchemaChange.DropColumn) schemaChange, generator); - break; - case UPDATE_COLUMN_TYPE: - writeUpdateColumnType((SchemaChange.UpdateColumnType) schemaChange, generator); - break; - case UPDATE_COLUMN_NULLABILITY: - writeUpdateColumnNullability( - (SchemaChange.UpdateColumnNullability) schemaChange, generator); - break; - case UPDATE_COLUMN_COMMENT: - writeUpdateColumnComment( - (SchemaChange.UpdateColumnComment) schemaChange, generator); - break; - case UPDATE_COLUMN_POSITION: - writeUpdateColumnPosition( - (SchemaChange.UpdateColumnPosition) schemaChange, generator); - break; - default: - throw new IllegalArgumentException( - String.format( - "Cannot convert metadata update to json. Unrecognized action: %s", - updateAction)); - } - - generator.writeEndObject(); - } - - public static SchemaChange fromJson(String json) { - return JsonSerdeUtil.fromJson(json, SchemaChangeParser::fromJson); - } - - public static SchemaChange fromJson(JsonNode jsonNode) { - checkArgument( - jsonNode != null && jsonNode.isObject(), - "Cannot parse schema change from non-object value: %s", - jsonNode); - checkArgument( - jsonNode.hasNonNull(ACTION), "Cannot parse schema change. Missing field: action"); - SchemaChangeActionEnum action = - SchemaChangeActionEnum.fromString(jsonNode.get(ACTION).asText()); - - switch (action) { - case SET_OPTION: - return readSetOption(jsonNode); - case REMOVE_OPTION: - return readRemoveOption(jsonNode); - case UPDATE_COMMENT: - return readUpdateComment(jsonNode); - case ADD_COLUMN: - return readAddColumn(jsonNode); - case RENAME_COLUMN: - return readRenameColumn(jsonNode); - case DROP_COLUMN: - return readDropColumn(jsonNode); - case UPDATE_COLUMN_TYPE: - return readUpdateColumnType(jsonNode); - case UPDATE_COLUMN_NULLABILITY: - return readUpdateColumnNullability(jsonNode); - case UPDATE_COLUMN_COMMENT: - return readUpdateColumnComment(jsonNode); - case UPDATE_COLUMN_POSITION: - return readUpdateColumnPosition(jsonNode); - default: - throw new UnsupportedOperationException( - String.format("Cannot convert schema change action to json: %s", action)); - } - } - - /** Schema change action type. */ - public enum SchemaChangeActionEnum { - SET_OPTION, - REMOVE_OPTION, - UPDATE_COMMENT, - ADD_COLUMN, - RENAME_COLUMN, - DROP_COLUMN, - UPDATE_COLUMN_TYPE, - UPDATE_COLUMN_NULLABILITY, - UPDATE_COLUMN_COMMENT, - UPDATE_COLUMN_POSITION; - - public static SchemaChangeActionEnum fromString(String action) { - return SchemaChangeActionEnum.valueOf(action.toUpperCase()); - } - } - - private static void writeSetOption(SchemaChange.SetOption schemaChange, JsonGenerator generator) - throws IOException { - generator.writeStringField(schemaChange.key(), schemaChange.value()); - } - - private static void writeRemoveOption( - SchemaChange.RemoveOption schemaChange, JsonGenerator generator) throws IOException { - generator.writeStringField(FIELD_REMOVE_OPTION, schemaChange.key()); - } - - private static void writeUpdateComment( - SchemaChange.UpdateComment schemaChange, JsonGenerator generator) throws IOException { - generator.writeStringField(FIELD_COMMENT, schemaChange.comment()); - } - - private static void writeAddColumn(SchemaChange.AddColumn schemaChange, JsonGenerator generator) - throws IOException { - writeFiledNames(schemaChange.fieldNames(), generator); - generator.writeFieldName(FIELD_DATA_TYPE); - schemaChange.dataType().serializeJson(generator); - if (schemaChange.description() != null) { - generator.writeStringField(FIELD_COMMENT, schemaChange.description()); - } - if (schemaChange.move() != null) { - generator.writeFieldName(FIELD_MOVE); - writeMove(schemaChange.move(), generator); - } - } - - private static void writeFiledNames(String[] fieldNames, JsonGenerator generator) - throws IOException { - generator.writeArrayFieldStart(FIELD_FILED_NAMES); - for (String fieldName : fieldNames) { - generator.writeString(fieldName); - } - generator.writeEndArray(); - } - - private static void writeMove(SchemaChange.Move move, JsonGenerator generator) - throws IOException { - generator.writeStartObject(); - generator.writeStringField(FIELD_FILED_NAME, move.fieldName()); - generator.writeStringField(FIELD_REFERENCE_FIELD_NAME, move.referenceFieldName()); - generator.writeStringField(FIELD_TYPE, move.type().name()); - generator.writeEndObject(); - } - - private static void writeRenameColumn( - SchemaChange.RenameColumn schemaChange, JsonGenerator generator) throws IOException { - writeFiledNames(schemaChange.fieldNames(), generator); - generator.writeStringField(FIELD_NEW_NAME, schemaChange.newName()); - } - - private static void writeDropColumn( - SchemaChange.DropColumn schemaChange, JsonGenerator generator) throws IOException { - writeFiledNames(schemaChange.fieldNames(), generator); - } - - private static void writeUpdateColumnType( - SchemaChange.UpdateColumnType schemaChange, JsonGenerator generator) - throws IOException { - writeFiledNames(schemaChange.fieldNames(), generator); - generator.writeFieldName(FIELD_NEW_DATA_TYPE); - schemaChange.newDataType().serializeJson(generator); - generator.writeBooleanField(FIELD_KEEP_NULLABILITY, schemaChange.keepNullability()); - } - - private static void writeUpdateColumnNullability( - SchemaChange.UpdateColumnNullability schemaChange, JsonGenerator generator) - throws IOException { - writeFiledNames(schemaChange.fieldNames(), generator); - generator.writeBooleanField(FIELD_NEW_NULLABILITY, schemaChange.newNullability()); - } - - private static void writeUpdateColumnComment( - SchemaChange.UpdateColumnComment schemaChange, JsonGenerator generator) - throws IOException { - writeFiledNames(schemaChange.fieldNames(), generator); - generator.writeStringField(FIELD_COMMENT, schemaChange.newDescription()); - } - - private static void writeUpdateColumnPosition( - SchemaChange.UpdateColumnPosition schemaChange, JsonGenerator generator) - throws IOException { - if (schemaChange.move() != null) { - generator.writeFieldName(FIELD_MOVE); - writeMove(schemaChange.move(), generator); - } - } - - private static SchemaChange readSetOption(JsonNode node) { - Iterator fieldNames = node.fieldNames(); - String key = null; - String value = null; - while (fieldNames.hasNext()) { - String fieldName = fieldNames.next(); - if (ACTION.equals(fieldName)) { - continue; - } - key = fieldName; - value = node.get(fieldName).asText(); - } - return SchemaChange.setOption(key, value); - } - - private static SchemaChange readRemoveOption(JsonNode node) { - return SchemaChange.removeOption(node.get(FIELD_REMOVE_OPTION).asText()); - } - - private static SchemaChange readUpdateComment(JsonNode node) { - String comment = node.has(FIELD_COMMENT) ? node.get(FIELD_COMMENT).asText() : null; - return SchemaChange.updateComment(comment); - } - - private static SchemaChange readAddColumn(JsonNode node) { - String[] fieldNames = getFieldNames(node); - JsonNode dataTypeJson = node.get(FIELD_DATA_TYPE); - DataType dataType = DataTypeJsonParser.parseDataType(dataTypeJson); - String comment = node.has(FIELD_COMMENT) ? node.get(FIELD_COMMENT).asText() : null; - SchemaChange.Move move = node.has(FIELD_MOVE) ? readMove(node.get(FIELD_MOVE)) : null; - return SchemaChange.addColumn(fieldNames, dataType, comment, move); - } - - private static SchemaChange.Move readMove(JsonNode node) { - String filedName = node.get(FIELD_FILED_NAME).asText(); - String referenceFieldName = node.get(FIELD_REFERENCE_FIELD_NAME).asText(); - SchemaChange.Move.MoveType type = - SchemaChange.Move.MoveType.valueOf(node.get(FIELD_TYPE).asText()); - return new SchemaChange.Move(filedName, referenceFieldName, type); - } - - private static SchemaChange readRenameColumn(JsonNode node) { - String[] fieldNames = getFieldNames(node); - String newName = node.get(FIELD_NEW_NAME).asText(); - return SchemaChange.renameColumn(fieldNames, newName); - } - - private static SchemaChange readDropColumn(JsonNode node) { - String[] fieldNames = getFieldNames(node); - return SchemaChange.dropColumn(fieldNames); - } - - private static SchemaChange readUpdateColumnType(JsonNode node) { - String[] fieldNames = getFieldNames(node); - JsonNode dataTypeJson = node.get(FIELD_NEW_DATA_TYPE); - DataType newDataType = DataTypeJsonParser.parseDataType(dataTypeJson); - boolean keepNullability = - node.has(FIELD_KEEP_NULLABILITY) - ? node.get(FIELD_KEEP_NULLABILITY).asBoolean() - : false; - return SchemaChange.updateColumnType(fieldNames, newDataType, keepNullability); - } - - private static SchemaChange readUpdateColumnNullability(JsonNode node) { - String[] fieldNames = getFieldNames(node); - boolean newNullability = node.get(FIELD_NEW_NULLABILITY).asBoolean(); - return SchemaChange.updateColumnNullability(fieldNames, newNullability); - } - - private static SchemaChange readUpdateColumnComment(JsonNode node) { - String[] fieldNames = getFieldNames(node); - String comment = node.get(FIELD_COMMENT).asText(); - return SchemaChange.updateColumnComment(fieldNames, comment); - } - - private static SchemaChange readUpdateColumnPosition(JsonNode node) { - SchemaChange.Move move = node.has(FIELD_MOVE) ? readMove(node.get(FIELD_MOVE)) : null; - return SchemaChange.updateColumnPosition(move); - } - - private static String[] getFieldNames(JsonNode jsonNode) { - Iterator fieldNamesJson = jsonNode.get(FIELD_FILED_NAMES).elements(); - List filedNames = new ArrayList<>(); - while (fieldNamesJson.hasNext()) { - filedNames.add(fieldNamesJson.next().asText()); - } - return filedNames.toArray(new String[0]); - } -} diff --git a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java index 358f9b041769..4d50c5e116ea 100644 --- a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java +++ b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java @@ -19,10 +19,15 @@ package org.apache.paimon.schema; import org.apache.paimon.annotation.Public; +import org.apache.paimon.rest.SchemaChangeActions; import org.apache.paimon.types.DataType; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonGetter; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonSubTypes; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonTypeInfo; import javax.annotation.Nullable; @@ -36,6 +41,42 @@ * @since 0.4.0 */ @Public +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = SchemaChangeActions.FIELD_ACTION) +@JsonSubTypes({ + @JsonSubTypes.Type( + value = SchemaChange.SetOption.class, + name = SchemaChangeActions.SET_OPTION_ACTION), + @JsonSubTypes.Type( + value = SchemaChange.RemoveOption.class, + name = SchemaChangeActions.REMOVE_OPTION_ACTION), + @JsonSubTypes.Type( + value = SchemaChange.UpdateComment.class, + name = SchemaChangeActions.UPDATE_COMMENT_ACTION), + @JsonSubTypes.Type( + value = SchemaChange.AddColumn.class, + name = SchemaChangeActions.ADD_COLUMN_ACTION), + @JsonSubTypes.Type( + value = SchemaChange.RenameColumn.class, + name = SchemaChangeActions.RENAME_COLUMN_ACTION), + @JsonSubTypes.Type( + value = SchemaChange.DropColumn.class, + name = SchemaChangeActions.DROP_COLUMN_ACTION), + @JsonSubTypes.Type( + value = SchemaChange.UpdateColumnType.class, + name = SchemaChangeActions.UPDATE_COLUMN_TYPE_ACTION), + @JsonSubTypes.Type( + value = SchemaChange.UpdateColumnNullability.class, + name = SchemaChangeActions.UPDATE_COLUMN_NULLABILITY_ACTION), + @JsonSubTypes.Type( + value = SchemaChange.UpdateColumnComment.class, + name = SchemaChangeActions.UPDATE_COLUMN_COMMENT_ACTION), + @JsonSubTypes.Type( + value = SchemaChange.UpdateColumnPosition.class, + name = SchemaChangeActions.UPDATE_COLUMN_POSITION_ACTION), +}) public interface SchemaChange extends Serializable { static SchemaChange setOption(String key, String value) { @@ -122,18 +163,28 @@ final class SetOption implements SchemaChange { private static final long serialVersionUID = 1L; + private static final String FIELD_KEY = "key"; + private static final String FIELD_VALUE = "value"; + + @JsonProperty(FIELD_KEY) private final String key; + + @JsonProperty(FIELD_VALUE) private final String value; - private SetOption(String key, String value) { + @JsonCreator + private SetOption( + @JsonProperty(FIELD_KEY) String key, @JsonProperty(FIELD_VALUE) String value) { this.key = key; this.value = value; } + @JsonGetter(FIELD_KEY) public String key() { return key; } + @JsonGetter(FIELD_VALUE) public String value() { return value; } @@ -161,12 +212,16 @@ final class RemoveOption implements SchemaChange { private static final long serialVersionUID = 1L; + private static final String FIELD_KEY = "key"; + + @JsonProperty(FIELD_KEY) private final String key; - private RemoveOption(String key) { + private RemoveOption(@JsonProperty(FIELD_KEY) String key) { this.key = key; } + @JsonGetter(FIELD_KEY) public String key() { return key; } @@ -194,13 +249,17 @@ final class UpdateComment implements SchemaChange { private static final long serialVersionUID = 1L; + private static final String FIELD_COMMENT = "comment"; + // If comment is null, means to remove comment + @JsonProperty(FIELD_COMMENT) private final @Nullable String comment; - private UpdateComment(@Nullable String comment) { + private UpdateComment(@JsonProperty(FIELD_COMMENT) @Nullable String comment) { this.comment = comment; } + @JsonGetter(FIELD_COMMENT) public @Nullable String comment() { return comment; } @@ -224,39 +283,58 @@ public int hashCode() { } /** A SchemaChange to add a field. */ + @JsonIgnoreProperties(ignoreUnknown = true) final class AddColumn implements SchemaChange { private static final long serialVersionUID = 1L; + private static final String FIELD_FILED_NAMES = "fieldNames"; + private static final String FIELD_DATA_TYPE = "dataType"; + private static final String FIELD_COMMENT = "comment"; + private static final String FIELD_MOVE = "move"; + + @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; + @JsonProperty(FIELD_DATA_TYPE) private final DataType dataType; + @JsonProperty(FIELD_COMMENT) private final String description; + @JsonProperty(FIELD_MOVE) private final Move move; - private AddColumn(String[] fieldNames, DataType dataType, String description, Move move) { + @JsonCreator + private AddColumn( + @JsonProperty(FIELD_FILED_NAMES) String[] fieldNames, + @JsonProperty(FIELD_DATA_TYPE) DataType dataType, + @JsonProperty(FIELD_COMMENT) String description, + @JsonProperty(FIELD_MOVE) Move move) { this.fieldNames = fieldNames; this.dataType = dataType; this.description = description; this.move = move; } + @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } + @JsonGetter(FIELD_DATA_TYPE) public DataType dataType() { return dataType; } @Nullable + @JsonGetter(FIELD_COMMENT) public String description() { return description; } @Nullable + @JsonGetter(FIELD_MOVE) public Move move() { return move; } @@ -291,20 +369,29 @@ final class RenameColumn implements SchemaChange { private static final long serialVersionUID = 1L; + private static final String FIELD_FILED_NAMES = "fieldNames"; + private static final String FIELD_NEW_NAME = "newName"; + + @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; + @JsonProperty(FIELD_NEW_NAME) private final String newName; @JsonCreator - private RenameColumn(String[] fieldNames, String newName) { + private RenameColumn( + @JsonProperty(FIELD_FILED_NAMES) String[] fieldNames, + @JsonProperty(FIELD_NEW_NAME) String newName) { this.fieldNames = fieldNames; this.newName = newName; } + @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } + @JsonGetter(FIELD_NEW_NAME) public String newName() { return newName; } @@ -336,13 +423,17 @@ final class DropColumn implements SchemaChange { private static final long serialVersionUID = 1L; + private static final String FIELD_FILED_NAMES = "fieldNames"; + + @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; @JsonCreator - private DropColumn(String[] fieldNames) { + private DropColumn(@JsonProperty(FIELD_FILED_NAMES) String[] fieldNames) { this.fieldNames = fieldNames; } + @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } @@ -366,32 +457,44 @@ public int hashCode() { } /** A SchemaChange to update the field type. */ + @JsonIgnoreProperties(ignoreUnknown = true) final class UpdateColumnType implements SchemaChange { private static final long serialVersionUID = 1L; + private static final String FIELD_FILED_NAMES = "fieldNames"; + private static final String FIELD_NEW_DATA_TYPE = "newDataType"; + private static final String FIELD_KEEP_NULLABILITY = "keepNullability"; + @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; + @JsonProperty(FIELD_NEW_DATA_TYPE) private final DataType newDataType; // If true, do not change the target field nullability + @JsonProperty(FIELD_KEEP_NULLABILITY) private final boolean keepNullability; @JsonCreator private UpdateColumnType( - String[] fieldNames, DataType newDataType, boolean keepNullability) { + @JsonProperty(FIELD_FILED_NAMES) String[] fieldNames, + @JsonProperty(FIELD_NEW_DATA_TYPE) DataType newDataType, + @JsonProperty(FIELD_KEEP_NULLABILITY) boolean keepNullability) { this.fieldNames = fieldNames; this.newDataType = newDataType; this.keepNullability = keepNullability; } + @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } + @JsonGetter(FIELD_NEW_DATA_TYPE) public DataType newDataType() { return newDataType; } + @JsonGetter(FIELD_KEEP_NULLABILITY) public boolean keepNullability() { return keepNullability; } @@ -421,13 +524,17 @@ public int hashCode() { final class UpdateColumnPosition implements SchemaChange { private static final long serialVersionUID = 1L; + private static final String FIELD_MOVE = "move"; + @JsonProperty(FIELD_MOVE) private final Move move; - private UpdateColumnPosition(Move move) { + @JsonCreator + private UpdateColumnPosition(@JsonProperty(FIELD_MOVE) Move move) { this.move = move; } + @JsonGetter(FIELD_MOVE) public Move move() { return move; } @@ -451,6 +558,7 @@ public int hashCode() { } /** Represents a requested column move in a struct. */ + @JsonIgnoreProperties(ignoreUnknown = true) class Move implements Serializable { public enum MoveType { @@ -478,26 +586,40 @@ public static Move last(String fieldName) { private static final long serialVersionUID = 1L; + private static final String FIELD_FILED_NAME = "fieldName"; + private static final String FIELD_REFERENCE_FIELD_NAME = "referenceFieldName"; + private static final String FIELD_TYPE = "type"; + + @JsonProperty(FIELD_FILED_NAME) private final String fieldName; + @JsonProperty(FIELD_REFERENCE_FIELD_NAME) private final String referenceFieldName; + @JsonProperty(FIELD_TYPE) private final MoveType type; - public Move(String fieldName, String referenceFieldName, MoveType type) { + @JsonCreator + public Move( + @JsonProperty(FIELD_FILED_NAME) String fieldName, + @JsonProperty(FIELD_REFERENCE_FIELD_NAME) String referenceFieldName, + @JsonProperty(FIELD_TYPE) MoveType type) { this.fieldName = fieldName; this.referenceFieldName = referenceFieldName; this.type = type; } + @JsonGetter(FIELD_FILED_NAME) public String fieldName() { return fieldName; } + @JsonGetter(FIELD_REFERENCE_FIELD_NAME) public String referenceFieldName() { return referenceFieldName; } + @JsonGetter(FIELD_TYPE) public MoveType type() { return type; } @@ -528,20 +650,29 @@ final class UpdateColumnNullability implements SchemaChange { private static final long serialVersionUID = 1L; + private static final String FIELD_FILED_NAMES = "fieldNames"; + private static final String FIELD_NEW_NULLABILITY = "newNullability"; + + @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; + @JsonProperty(FIELD_NEW_NULLABILITY) private final boolean newNullability; @JsonCreator - public UpdateColumnNullability(String[] fieldNames, boolean newNullability) { + public UpdateColumnNullability( + @JsonProperty(FIELD_FILED_NAMES) String[] fieldNames, + @JsonProperty(FIELD_NEW_NULLABILITY) boolean newNullability) { this.fieldNames = fieldNames; this.newNullability = newNullability; } + @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } + @JsonGetter(FIELD_NEW_NULLABILITY) public boolean newNullability() { return newNullability; } @@ -573,20 +704,29 @@ final class UpdateColumnComment implements SchemaChange { private static final long serialVersionUID = 1L; + private static final String FIELD_FILED_NAMES = "fieldNames"; + private static final String FIELD_NEW_COMMENT = "newComment"; + + @JsonProperty(FIELD_FILED_NAMES) private final String[] fieldNames; + @JsonProperty(FIELD_NEW_COMMENT) private final String newDescription; @JsonCreator - public UpdateColumnComment(String[] fieldNames, String newDescription) { + public UpdateColumnComment( + @JsonProperty(FIELD_FILED_NAMES) String[] fieldNames, + @JsonProperty(FIELD_NEW_COMMENT) String newDescription) { this.fieldNames = fieldNames; this.newDescription = newDescription; } + @JsonGetter(FIELD_FILED_NAMES) public String[] fieldNames() { return fieldNames; } + @JsonGetter(FIELD_NEW_COMMENT) public String newDescription() { return newDescription; } diff --git a/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java b/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java index 8539ee8133a4..edc6dac5f992 100644 --- a/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java +++ b/paimon-core/src/main/java/org/apache/paimon/utils/JsonSerdeUtil.java @@ -138,22 +138,6 @@ public static T fromJson(String json, Class clazz) { } } - /** - * Helper for parsing JSON from a String. - * - * @param json a JSON string - * @param parser a function that converts a JsonNode to a Java object - * @param type of objects created by the parser - * @return the parsed Java object - */ - public static T fromJson(String json, FromJson parser) { - try { - return parser.parse(OBJECT_MAPPER_INSTANCE.readValue(json, JsonNode.class)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - public static String toJson(T t) { try { return OBJECT_MAPPER_INSTANCE.writerWithDefaultPrettyPrinter().writeValueAsString(t); @@ -279,15 +263,5 @@ public static boolean isNull(JsonNode jsonNode) { return jsonNode == null || jsonNode.isNull(); } - /** - * A functional interface for parsing JSON data into a specific object type. - * - * @param The type of the object to be parsed from JSON. - */ - @FunctionalInterface - public interface FromJson { - T parse(JsonNode node) throws JsonProcessingException; - } - private JsonSerdeUtil() {} } diff --git a/paimon-open-api/rest-catalog-open-api.yaml b/paimon-open-api/rest-catalog-open-api.yaml index e729265ea114..c39e710f6367 100644 --- a/paimon-open-api/rest-catalog-open-api.yaml +++ b/paimon-open-api/rest-catalog-open-api.yaml @@ -206,6 +206,18 @@ paths: application/json: schema: $ref: '#/components/schemas/GetTableResponse' + "404": + description: Resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + "409": + description: Resource has exist + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' "500": description: Internal Server Error delete: @@ -267,6 +279,18 @@ paths: application/json: schema: $ref: '#/components/schemas/GetTableResponse' + "404": + description: Resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + "409": + description: Resource has exist + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' "500": description: Internal Server Error /v1/{prefix}/databases/{database}/properties: @@ -497,23 +521,25 @@ components: properties: action: type: string - enum: ["set_option"] - additionalProperties: + enum: ["setOption"] + key: + type: string + value: type: string RemoveOption: type: object properties: action: type: string - enum: ["remove_option"] - removeOptionKey: + enum: ["removeOption"] + key: type: string UpdateComment: type: object properties: action: type: string - enum: ["update_comment"] + enum: ["updateComment"] comment: type: string AddColumn: @@ -521,7 +547,7 @@ components: properties: action: type: string - enum: ["add_column"] + enum: ["addColumn"] fieldNames: type: array items: @@ -537,7 +563,7 @@ components: properties: action: type: string - enum: ["rename_column"] + enum: ["renameColumn"] fieldNames: type: array items: @@ -549,7 +575,7 @@ components: properties: action: type: string - enum: ["drop_column"] + enum: ["dropColumn"] fieldNames: type: array items: @@ -559,24 +585,24 @@ components: properties: action: type: string - enum: [ "update_column_comment" ] + enum: [ "updateColumnComment" ] fieldNames: type: array items: type: string - comment: + newComment: type: string UpdateColumnType: type: object properties: action: type: string - enum: [ "update_column_type" ] + enum: [ "updateColumnType" ] fieldNames: type: array items: type: string - dataType: + newDataType: $ref: '#/components/schemas/DataType' keepNullability: type: boolean @@ -585,7 +611,7 @@ components: properties: action: type: string - enum: [ "update_column_position" ] + enum: [ "updateColumnPosition" ] move: $ref: '#/components/schemas/Move' UpdateColumnNullability: diff --git a/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java b/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java index 495ac19e3b49..179c23ce46f8 100644 --- a/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java +++ b/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java @@ -105,11 +105,7 @@ public ListDatabasesResponse listDatabases(@PathVariable String prefix) { @ApiResponse( responseCode = "409", description = "Resource has exist", - content = { - @Content( - schema = @Schema(implementation = ErrorResponse.class), - mediaType = "application/json") - }), + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), @ApiResponse( responseCode = "500", content = {@Content(schema = @Schema())}) @@ -171,6 +167,10 @@ public void dropDatabase(@PathVariable String prefix, @PathVariable String datab responseCode = "404", description = "Resource not found", content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), + @ApiResponse( + responseCode = "409", + description = "Resource has exist", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), @ApiResponse( responseCode = "500", content = {@Content(schema = @Schema())}) @@ -214,6 +214,10 @@ public ListTablesResponse listTables( responseCode = "404", description = "Resource not found", content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), + @ApiResponse( + responseCode = "409", + description = "Resource has exist", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), @ApiResponse( responseCode = "500", content = {@Content(schema = @Schema())}) @@ -268,6 +272,10 @@ public GetTableResponse createTable( @ApiResponse( responseCode = "200", content = {@Content(schema = @Schema(implementation = GetTableResponse.class))}), + @ApiResponse( + responseCode = "404", + description = "Resource not found", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), @ApiResponse( responseCode = "500", content = {@Content(schema = @Schema())}) @@ -314,6 +322,10 @@ public void dropTable( @ApiResponse( responseCode = "200", content = {@Content(schema = @Schema(implementation = GetTableResponse.class))}), + @ApiResponse( + responseCode = "404", + description = "Resource not found", + content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), @ApiResponse( responseCode = "500", content = {@Content(schema = @Schema())}) From 10b39ad23cf655451052db94cf36544a030cd869 Mon Sep 17 00:00:00 2001 From: yantian Date: Thu, 26 Dec 2024 11:07:13 +0800 Subject: [PATCH 5/7] move SchemaChangeActions into SchemaChang --- .../paimon/rest/SchemaChangeActions.java | 33 ---------------- .../apache/paimon/schema/SchemaChange.java | 38 +++++++++++++------ 2 files changed, 26 insertions(+), 45 deletions(-) delete mode 100644 paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeActions.java diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeActions.java b/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeActions.java deleted file mode 100644 index cbf7612ff307..000000000000 --- a/paimon-core/src/main/java/org/apache/paimon/rest/SchemaChangeActions.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.paimon.rest; - -public class SchemaChangeActions { - public static final String FIELD_ACTION = "action"; - public static final String SET_OPTION_ACTION = "setOption"; - public static final String REMOVE_OPTION_ACTION = "removeOption"; - public static final String UPDATE_COMMENT_ACTION = "updateComment"; - public static final String ADD_COLUMN_ACTION = "addColumn"; - public static final String RENAME_COLUMN_ACTION = "renameColumn"; - public static final String DROP_COLUMN_ACTION = "dropColumn"; - public static final String UPDATE_COLUMN_TYPE_ACTION = "updateColumnType"; - public static final String UPDATE_COLUMN_NULLABILITY_ACTION = "updateColumnNullability"; - public static final String UPDATE_COLUMN_COMMENT_ACTION = "updateColumnComment"; - public static final String UPDATE_COLUMN_POSITION_ACTION = "updateColumnPosition"; -} diff --git a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java index 4d50c5e116ea..0be17ed32c8d 100644 --- a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java +++ b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java @@ -19,7 +19,6 @@ package org.apache.paimon.schema; import org.apache.paimon.annotation.Public; -import org.apache.paimon.rest.SchemaChangeActions; import org.apache.paimon.types.DataType; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator; @@ -44,38 +43,38 @@ @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, - property = SchemaChangeActions.FIELD_ACTION) + property = SchemaChange.Actions.FIELD_ACTION) @JsonSubTypes({ @JsonSubTypes.Type( value = SchemaChange.SetOption.class, - name = SchemaChangeActions.SET_OPTION_ACTION), + name = SchemaChange.Actions.SET_OPTION_ACTION), @JsonSubTypes.Type( value = SchemaChange.RemoveOption.class, - name = SchemaChangeActions.REMOVE_OPTION_ACTION), + name = SchemaChange.Actions.REMOVE_OPTION_ACTION), @JsonSubTypes.Type( value = SchemaChange.UpdateComment.class, - name = SchemaChangeActions.UPDATE_COMMENT_ACTION), + name = SchemaChange.Actions.UPDATE_COMMENT_ACTION), @JsonSubTypes.Type( value = SchemaChange.AddColumn.class, - name = SchemaChangeActions.ADD_COLUMN_ACTION), + name = SchemaChange.Actions.ADD_COLUMN_ACTION), @JsonSubTypes.Type( value = SchemaChange.RenameColumn.class, - name = SchemaChangeActions.RENAME_COLUMN_ACTION), + name = SchemaChange.Actions.RENAME_COLUMN_ACTION), @JsonSubTypes.Type( value = SchemaChange.DropColumn.class, - name = SchemaChangeActions.DROP_COLUMN_ACTION), + name = SchemaChange.Actions.DROP_COLUMN_ACTION), @JsonSubTypes.Type( value = SchemaChange.UpdateColumnType.class, - name = SchemaChangeActions.UPDATE_COLUMN_TYPE_ACTION), + name = SchemaChange.Actions.UPDATE_COLUMN_TYPE_ACTION), @JsonSubTypes.Type( value = SchemaChange.UpdateColumnNullability.class, - name = SchemaChangeActions.UPDATE_COLUMN_NULLABILITY_ACTION), + name = SchemaChange.Actions.UPDATE_COLUMN_NULLABILITY_ACTION), @JsonSubTypes.Type( value = SchemaChange.UpdateColumnComment.class, - name = SchemaChangeActions.UPDATE_COLUMN_COMMENT_ACTION), + name = SchemaChange.Actions.UPDATE_COLUMN_COMMENT_ACTION), @JsonSubTypes.Type( value = SchemaChange.UpdateColumnPosition.class, - name = SchemaChangeActions.UPDATE_COLUMN_POSITION_ACTION), + name = SchemaChange.Actions.UPDATE_COLUMN_POSITION_ACTION), }) public interface SchemaChange extends Serializable { @@ -751,4 +750,19 @@ public int hashCode() { return result; } } + + /** Actions for schema changes: identify for schema change. */ + public static class Actions { + public static final String FIELD_ACTION = "action"; + public static final String SET_OPTION_ACTION = "setOption"; + public static final String REMOVE_OPTION_ACTION = "removeOption"; + public static final String UPDATE_COMMENT_ACTION = "updateComment"; + public static final String ADD_COLUMN_ACTION = "addColumn"; + public static final String RENAME_COLUMN_ACTION = "renameColumn"; + public static final String DROP_COLUMN_ACTION = "dropColumn"; + public static final String UPDATE_COLUMN_TYPE_ACTION = "updateColumnType"; + public static final String UPDATE_COLUMN_NULLABILITY_ACTION = "updateColumnNullability"; + public static final String UPDATE_COLUMN_COMMENT_ACTION = "updateColumnComment"; + public static final String UPDATE_COLUMN_POSITION_ACTION = "updateColumnPosition"; + } } From 0b9e5b9cc59e071b020fa9076efa258eaaa8610b Mon Sep 17 00:00:00 2001 From: yantian Date: Thu, 26 Dec 2024 11:31:18 +0800 Subject: [PATCH 6/7] fix checkstyle fail --- .../src/main/java/org/apache/paimon/schema/SchemaChange.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java index 0be17ed32c8d..90bc9abc0aae 100644 --- a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java +++ b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaChange.java @@ -752,7 +752,7 @@ public int hashCode() { } /** Actions for schema changes: identify for schema change. */ - public static class Actions { + class Actions { public static final String FIELD_ACTION = "action"; public static final String SET_OPTION_ACTION = "setOption"; public static final String REMOVE_OPTION_ACTION = "removeOption"; @@ -764,5 +764,7 @@ public static class Actions { public static final String UPDATE_COLUMN_NULLABILITY_ACTION = "updateColumnNullability"; public static final String UPDATE_COLUMN_COMMENT_ACTION = "updateColumnComment"; public static final String UPDATE_COLUMN_POSITION_ACTION = "updateColumnPosition"; + + private Actions() {} } } From 7233243834c1779e934252aec88b9a8c1ef18e91 Mon Sep 17 00:00:00 2001 From: yantian Date: Thu, 26 Dec 2024 15:07:46 +0800 Subject: [PATCH 7/7] add detail of DataType in open api yaml --- paimon-open-api/rest-catalog-open-api.yaml | 50 ++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/paimon-open-api/rest-catalog-open-api.yaml b/paimon-open-api/rest-catalog-open-api.yaml index c39e710f6367..7fefd0254b1b 100644 --- a/paimon-open-api/rest-catalog-open-api.yaml +++ b/paimon-open-api/rest-catalog-open-api.yaml @@ -454,9 +454,53 @@ components: description: type: string DataType: - anyOf: - - type: string - - type: object + oneOf: + - $ref: '#/components/schemas/PrimitiveType' + - $ref: '#/components/schemas/ArrayType' + - $ref: '#/components/schemas/MultisetType' + - $ref: '#/components/schemas/MapType' + - $ref: '#/components/schemas/RowType' + PrimitiveType: + type: string + ArrayType: + type: object + properties: + type: + type: string + pattern: ^ARRAY.* + element: + type: + $ref: '#/components/schemas/DataType' + MultisetType: + type: object + properties: + type: + type: string + pattern: ^MULTISET.* + element: + type: + $ref: '#/components/schemas/DataType' + MapType: + type: object + properties: + type: + type: string + pattern: ^MAP.* + key: + type: + $ref: '#/components/schemas/DataType' + value: + type: + $ref: '#/components/schemas/DataType' + RowType: + type: object + properties: + type: + type: string + pattern: ^ROW.* + fields: + type: + $ref: '#/components/schemas/DataField' Identifier: type: object properties: