This is an automated email from the ASF dual-hosted git repository. jshao pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push: new 4c6384ea27 [#6234] feat(core): Support rename operations for model alteration (#6765) 4c6384ea27 is described below commit 4c6384ea27822f68bbf3c9f65f547c40126ae588 Author: Lord of Abyss <103809695+abyss-l...@users.noreply.github.com> AuthorDate: Mon Mar 31 01:39:46 2025 +0800 [#6234] feat(core): Support rename operations for model alteration (#6765) ### What changes were proposed in this pull request? Add ModelChange API interface, Implement the rename logic in model catalog and JDBC backend logic. - [X] PR1: Add ModelChange API interface, Implement the rename logic in model catalog and JDBC backend logic. - [ ] PR2: Add REST endpoint to support model change. - [ ] PR3: Add Java client and Python client for model rename. - [ ] PR4: update CLI module and docs. - [x] PR5: update model-related event. ### Why are the changes needed? Fix: #6234 ### Does this PR introduce _any_ user-facing change? Yes, support alter model for rename. ### How was this patch tested? local test. --- .../org/apache/gravitino/model/ModelCatalog.java | 15 ++++ .../org/apache/gravitino/model/ModelChange.java | 100 +++++++++++++++++++++ .../apache/gravitino/model/TestModelChange.java | 63 +++++++++++++ .../catalog/model/ModelCatalogOperations.java | 68 ++++++++++++++ .../catalog/model/TestModelCatalogOperations.java | 33 +++++++ .../gravitino/catalog/hadoop/fs/Constants.java | 1 + .../gravitino/client/GenericModelCatalog.java | 8 ++ .../catalog/ModelNormalizeDispatcher.java | 8 ++ .../catalog/ModelOperationDispatcher.java | 27 ++++++ .../gravitino/listener/ModelEventDispatcher.java | 23 +++++ .../listener/api/event/AlterModelEvent.java | 80 +++++++++++++++++ .../listener/api/event/AlterModelFailureEvent.java | 65 ++++++++++++++ .../listener/api/event/AlterModelPreEvent.java | 53 +++++++++++ .../listener/api/event/OperationType.java | 1 + .../gravitino/storage/relational/JDBCBackend.java | 2 + .../storage/relational/mapper/ModelMetaMapper.java | 4 + .../mapper/ModelMetaSQLProviderFactory.java | 5 ++ .../provider/base/ModelMetaBaseSQLProvider.java | 25 ++++++ .../relational/service/ModelMetaService.java | 38 ++++++++ .../storage/relational/utils/POConverters.java | 19 ++++ .../catalog/TestModelOperationDispatcher.java | 24 +++++ .../gravitino/connector/TestCatalogOperations.java | 43 +++++++++ .../listener/api/event/TestModelEvent.java | 71 +++++++++++++++ .../relational/service/TestModelMetaService.java | 43 +++++++++ 24 files changed, 819 insertions(+) diff --git a/api/src/main/java/org/apache/gravitino/model/ModelCatalog.java b/api/src/main/java/org/apache/gravitino/model/ModelCatalog.java index 3fb39c18ae..8d24ae7efe 100644 --- a/api/src/main/java/org/apache/gravitino/model/ModelCatalog.java +++ b/api/src/main/java/org/apache/gravitino/model/ModelCatalog.java @@ -236,4 +236,19 @@ public interface ModelCatalog { * @return True if the model version is deleted, false if the model version does not exist. */ boolean deleteModelVersion(NameIdentifier ident, String alias); + + /** + * Applies the {@link ModelChange changes} to a model in the catalog. + * + * <p>Implementations may reject the changes. If any change is rejected, no changes should be + * applied to the model. + * + * @param ident the {@link NameIdentifier} instance of the model to alter + * @param changes the several {@link ModelChange} instances to apply to the model + * @return the updated {@link Model} instance + * @throws NoSuchModelException If the model does not exist + * @throws IllegalArgumentException If the change is rejected by the implementation + */ + Model alterModel(NameIdentifier ident, ModelChange... changes) + throws NoSuchModelException, IllegalArgumentException; } diff --git a/api/src/main/java/org/apache/gravitino/model/ModelChange.java b/api/src/main/java/org/apache/gravitino/model/ModelChange.java new file mode 100644 index 0000000000..5d40c78856 --- /dev/null +++ b/api/src/main/java/org/apache/gravitino/model/ModelChange.java @@ -0,0 +1,100 @@ +/* + * 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.gravitino.model; + +import org.apache.gravitino.annotation.Evolving; + +/** + * A model change is a change to a model. It can be used to rename a model, update the comment of a + * model, set a property and value pair for a model, or remove a property from a model. + */ +@Evolving +public interface ModelChange { + /** + * Create a ModelChange for renaming a model. + * + * @param newName The new model name. + * @return A ModelChange for the rename. + */ + static ModelChange rename(String newName) { + return new ModelChange.RenameModel(newName); + } + + /** A ModelChange to rename a model. */ + final class RenameModel implements ModelChange { + private final String newName; + + /** + * Constructs a new {@link RenameModel} instance with the specified new name. + * + * @param newName The new name of the model. + */ + public RenameModel(String newName) { + this.newName = newName; + } + + /** + * Retrieves the new name for the model. + * + * @return The new name of the model. + */ + public String newName() { + return newName; + } + + /** + * Compares this {@code RenameModel} instance with another object for equality. The comparison + * is based on the new name of the model. + * + * @param obj The object to compare with this instance. + * @return {@code true} if the given object represents the same model renaming; {@code false} + * otherwise. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || getClass() != obj.getClass()) return false; + RenameModel other = (RenameModel) obj; + return newName.equals(other.newName); + } + + /** + * Generates a hash code for this {@code RenameModel} instance. The hash code is based on the + * new name of the model. + * + * @return A hash code value for this model renaming operation. + */ + @Override + public int hashCode() { + return newName.hashCode(); + } + + /** + * Returns a string representation of the {@code RenameModel} instance. This string format + * includes the class name followed by the property name to be renamed. + * + * @return A string summary of the property rename instance. + */ + @Override + public String toString() { + return "RenameModel " + newName; + } + } +} diff --git a/api/src/test/java/org/apache/gravitino/model/TestModelChange.java b/api/src/test/java/org/apache/gravitino/model/TestModelChange.java new file mode 100644 index 0000000000..a906f8a75b --- /dev/null +++ b/api/src/test/java/org/apache/gravitino/model/TestModelChange.java @@ -0,0 +1,63 @@ +/* + * 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.gravitino.model; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TestModelChange { + + @Test + void testCreateModelChangeUseStaticMethod() { + String newName = "newName"; + ModelChange modelChange = ModelChange.rename(newName); + Assertions.assertEquals(ModelChange.RenameModel.class, modelChange.getClass()); + + ModelChange.RenameModel renameModel = (ModelChange.RenameModel) modelChange; + Assertions.assertEquals(newName, renameModel.newName()); + Assertions.assertEquals("RenameModel newName", renameModel.toString()); + } + + @Test + void testCreateModelChangeUseConstructor() { + String newName = "newName"; + ModelChange.RenameModel renameModel = new ModelChange.RenameModel(newName); + Assertions.assertEquals(newName, renameModel.newName()); + Assertions.assertEquals("RenameModel newName", renameModel.toString()); + } + + @Test + void testModelChangeEquals() { + String newName1 = "demo_model"; + String newName2 = "test_model"; + String newName3 = "demo_model"; + + ModelChange.RenameModel renameModel1 = new ModelChange.RenameModel(newName1); + ModelChange.RenameModel renameModel2 = new ModelChange.RenameModel(newName2); + ModelChange.RenameModel renameModel3 = new ModelChange.RenameModel(newName3); + + Assertions.assertEquals(renameModel1, renameModel3); + Assertions.assertNotEquals(renameModel1, renameModel2); + Assertions.assertEquals(renameModel1, renameModel3); + + Assertions.assertNotEquals(renameModel1.hashCode(), renameModel2.hashCode()); + Assertions.assertEquals(renameModel1.hashCode(), renameModel3.hashCode()); + } +} diff --git a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java index 7683180f78..4a47880812 100644 --- a/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java +++ b/catalogs/catalog-model/src/main/java/org/apache/gravitino/catalog/model/ModelCatalogOperations.java @@ -20,6 +20,7 @@ package org.apache.gravitino.catalog.model; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import java.io.IOException; import java.time.Instant; import java.util.List; @@ -46,6 +47,7 @@ import org.apache.gravitino.meta.ModelEntity; import org.apache.gravitino.meta.ModelVersionEntity; import org.apache.gravitino.model.Model; import org.apache.gravitino.model.ModelCatalog; +import org.apache.gravitino.model.ModelChange; import org.apache.gravitino.model.ModelVersion; import org.apache.gravitino.utils.NameIdentifierUtil; import org.apache.gravitino.utils.NamespaceUtil; @@ -259,6 +261,72 @@ public class ModelCatalogOperations extends ManagedSchemaOperations return internalDeleteModelVersion(modelVersionIdent); } + /** {@inheritDoc} */ + @Override + public Model alterModel(NameIdentifier ident, ModelChange... changes) + throws NoSuchModelException, IllegalArgumentException { + try { + if (!store.exists(ident, Entity.EntityType.MODEL)) { + throw new NoSuchModelException("Model %s does not exist", ident); + } + } catch (IOException ioe) { + throw new RuntimeException("Failed to alter model " + ident, ioe); + } + + try { + ModelEntity updatedModelEntity = + store.update( + ident, + ModelEntity.class, + Entity.EntityType.MODEL, + e -> updateModelEntity(ident, e, changes)); + + return toModelImpl(updatedModelEntity); + + } catch (IOException ioe) { + throw new RuntimeException("Failed to load model " + ident, ioe); + } catch (NoSuchEntityException nsee) { + throw new NoSuchModelException(nsee, "Model %s does not exist", ident); + } catch (EntityAlreadyExistsException eaee) { + // This is happened when renaming a model to an existing model name. + throw new RuntimeException("Model already exist " + ident.name(), eaee); + } + } + + private ModelEntity updateModelEntity( + NameIdentifier ident, ModelEntity modelEntity, ModelChange... changes) { + + Map<String, String> entityProperties = + modelEntity.properties() == null + ? Maps.newHashMap() + : Maps.newHashMap(modelEntity.properties()); + String entityName = ident.name(); + String entityComment = modelEntity.comment(); + Long entityId = modelEntity.id(); + AuditInfo entityAuditInfo = modelEntity.auditInfo(); + Namespace entityNamespace = modelEntity.namespace(); + Integer entityLatestVersion = modelEntity.latestVersion(); + + for (ModelChange change : changes) { + if (change instanceof ModelChange.RenameModel) { + entityName = ((ModelChange.RenameModel) change).newName(); + } else { + throw new IllegalArgumentException( + "Unsupported model change: " + change.getClass().getSimpleName()); + } + } + + return ModelEntity.builder() + .withName(entityName) + .withId(entityId) + .withComment(entityComment) + .withAuditInfo(entityAuditInfo) + .withNamespace(entityNamespace) + .withProperties(entityProperties) + .withLatestVersion(entityLatestVersion) + .build(); + } + private ModelImpl toModelImpl(ModelEntity model) { return ModelImpl.builder() .withName(model.name()) diff --git a/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/TestModelCatalogOperations.java b/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/TestModelCatalogOperations.java index e8321e4ed2..83fcc1505f 100644 --- a/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/TestModelCatalogOperations.java +++ b/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/TestModelCatalogOperations.java @@ -67,6 +67,7 @@ import org.apache.gravitino.meta.BaseMetalake; import org.apache.gravitino.meta.CatalogEntity; import org.apache.gravitino.meta.SchemaVersion; import org.apache.gravitino.model.Model; +import org.apache.gravitino.model.ModelChange; import org.apache.gravitino.model.ModelVersion; import org.apache.gravitino.storage.IdGenerator; import org.apache.gravitino.storage.RandomIdGenerator; @@ -656,6 +657,38 @@ public class TestModelCatalogOperations { Assertions.assertThrows(NoSuchModelException.class, () -> ops.listModelVersions(modelIdent)); } + @Test + public void testRename() { + String schemaName = randomSchemaName(); + createSchema(schemaName); + + String modelName = "model"; + String newModelName = "new_model"; + String comment = "comment"; + + NameIdentifier modelIdent = + NameIdentifierUtil.ofModel(METALAKE_NAME, CATALOG_NAME, schemaName, modelName); + StringIdentifier stringId = StringIdentifier.fromId(idGenerator.nextId()); + Map<String, String> properties = StringIdentifier.newPropertiesWithId(stringId, null); + + Model registeredModel = ops.registerModel(modelIdent, comment, properties); + Assertions.assertEquals(modelName, registeredModel.name()); + Assertions.assertEquals(comment, registeredModel.comment()); + Assertions.assertEquals(properties, registeredModel.properties()); + + Model loadedModel = ops.getModel(modelIdent); + Assertions.assertEquals(modelName, loadedModel.name()); + Assertions.assertEquals(comment, loadedModel.comment()); + Assertions.assertEquals(properties, loadedModel.properties()); + + ModelChange change = ModelChange.rename(newModelName); + Model alteredModel = ops.alterModel(modelIdent, change); + + Assertions.assertEquals(newModelName, alteredModel.name()); + Assertions.assertEquals(comment, alteredModel.comment()); + Assertions.assertEquals(properties, alteredModel.properties()); + } + private String randomSchemaName() { return "schema_" + UUID.randomUUID().toString().replace("-", ""); } diff --git a/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/Constants.java b/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/Constants.java index 5bbbbd591c..96a4c060bf 100644 --- a/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/Constants.java +++ b/catalogs/hadoop-common/src/main/java/org/apache/gravitino/catalog/hadoop/fs/Constants.java @@ -19,6 +19,7 @@ package org.apache.gravitino.catalog.hadoop.fs; +/** Constants used by the Hadoop file system catalog. */ public class Constants { // Name of the built-in local file system provider diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericModelCatalog.java b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericModelCatalog.java index 8658a97259..f579a79900 100644 --- a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericModelCatalog.java +++ b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericModelCatalog.java @@ -44,6 +44,7 @@ import org.apache.gravitino.exceptions.NoSuchModelVersionException; import org.apache.gravitino.exceptions.NoSuchSchemaException; import org.apache.gravitino.model.Model; import org.apache.gravitino.model.ModelCatalog; +import org.apache.gravitino.model.ModelChange; import org.apache.gravitino.model.ModelVersion; import org.apache.gravitino.rest.RESTUtils; @@ -249,6 +250,13 @@ class GenericModelCatalog extends BaseSchemaCatalog implements ModelCatalog { return resp.dropped(); } + @Override + public Model alterModel(NameIdentifier ident, ModelChange... changes) + throws NoSuchModelException, IllegalArgumentException { + // TODO: implement alterModel + return null; + } + /** @return A new builder instance for {@link GenericModelCatalog}. */ public static Builder builder() { return new Builder(); diff --git a/core/src/main/java/org/apache/gravitino/catalog/ModelNormalizeDispatcher.java b/core/src/main/java/org/apache/gravitino/catalog/ModelNormalizeDispatcher.java index 10683f6e9c..c73816f9a2 100644 --- a/core/src/main/java/org/apache/gravitino/catalog/ModelNormalizeDispatcher.java +++ b/core/src/main/java/org/apache/gravitino/catalog/ModelNormalizeDispatcher.java @@ -33,6 +33,7 @@ import org.apache.gravitino.exceptions.NoSuchModelException; import org.apache.gravitino.exceptions.NoSuchModelVersionException; import org.apache.gravitino.exceptions.NoSuchSchemaException; import org.apache.gravitino.model.Model; +import org.apache.gravitino.model.ModelChange; import org.apache.gravitino.model.ModelVersion; public class ModelNormalizeDispatcher implements ModelDispatcher { @@ -128,6 +129,13 @@ public class ModelNormalizeDispatcher implements ModelDispatcher { return dispatcher.deleteModelVersion(normalizeCaseSensitive(ident), alias); } + /** {@inheritDoc} */ + @Override + public Model alterModel(NameIdentifier ident, ModelChange... changes) + throws NoSuchModelException, IllegalArgumentException { + return dispatcher.alterModel(normalizeCaseSensitive(ident), changes); + } + private Namespace normalizeCaseSensitive(Namespace namespace) { Capability capabilities = getCapability(NameIdentifier.of(namespace.levels()), catalogManager); return applyCaseSensitive(namespace, Capability.Scope.MODEL, capabilities); diff --git a/core/src/main/java/org/apache/gravitino/catalog/ModelOperationDispatcher.java b/core/src/main/java/org/apache/gravitino/catalog/ModelOperationDispatcher.java index 1c5291d51a..88376ee5fd 100644 --- a/core/src/main/java/org/apache/gravitino/catalog/ModelOperationDispatcher.java +++ b/core/src/main/java/org/apache/gravitino/catalog/ModelOperationDispatcher.java @@ -36,6 +36,7 @@ import org.apache.gravitino.exceptions.NoSuchSchemaException; import org.apache.gravitino.lock.LockType; import org.apache.gravitino.lock.TreeLockUtils; import org.apache.gravitino.model.Model; +import org.apache.gravitino.model.ModelChange; import org.apache.gravitino.model.ModelVersion; import org.apache.gravitino.storage.IdGenerator; @@ -209,6 +210,32 @@ public class ModelOperationDispatcher extends OperationDispatcher implements Mod RuntimeException.class)); } + /** {@inheritDoc} */ + @Override + public Model alterModel(NameIdentifier ident, ModelChange... changes) + throws NoSuchModelException, IllegalArgumentException { + validateAlterProperties(ident, HasPropertyMetadata::modelPropertiesMetadata, changes); + NameIdentifier catalogIdent = getCatalogIdentifier(ident); + + Model alteredModel = + TreeLockUtils.doWithTreeLock( + ident, + LockType.WRITE, + () -> + doWithCatalog( + catalogIdent, + c -> c.doWithModelOps(f -> f.alterModel(ident, changes)), + NoSuchModelException.class, + IllegalArgumentException.class)); + + return EntityCombinedModel.of(alteredModel) + .withHiddenProperties( + getHiddenPropertyNames( + catalogIdent, + HasPropertyMetadata::modelPropertiesMetadata, + alteredModel.properties())); + } + private ModelVersion internalGetModelVersion( NameIdentifier ident, Supplier<ModelVersion> supplier) { NameIdentifier catalogIdent = getCatalogIdentifier(ident); diff --git a/core/src/main/java/org/apache/gravitino/listener/ModelEventDispatcher.java b/core/src/main/java/org/apache/gravitino/listener/ModelEventDispatcher.java index 243657df1c..e5d91928fb 100644 --- a/core/src/main/java/org/apache/gravitino/listener/ModelEventDispatcher.java +++ b/core/src/main/java/org/apache/gravitino/listener/ModelEventDispatcher.java @@ -28,6 +28,9 @@ import org.apache.gravitino.exceptions.ModelVersionAliasesAlreadyExistException; import org.apache.gravitino.exceptions.NoSuchModelException; import org.apache.gravitino.exceptions.NoSuchModelVersionException; import org.apache.gravitino.exceptions.NoSuchSchemaException; +import org.apache.gravitino.listener.api.event.AlterModelEvent; +import org.apache.gravitino.listener.api.event.AlterModelFailureEvent; +import org.apache.gravitino.listener.api.event.AlterModelPreEvent; import org.apache.gravitino.listener.api.event.DeleteModelEvent; import org.apache.gravitino.listener.api.event.DeleteModelFailureEvent; import org.apache.gravitino.listener.api.event.DeleteModelPreEvent; @@ -58,6 +61,7 @@ import org.apache.gravitino.listener.api.event.RegisterModelPreEvent; import org.apache.gravitino.listener.api.info.ModelInfo; import org.apache.gravitino.listener.api.info.ModelVersionInfo; import org.apache.gravitino.model.Model; +import org.apache.gravitino.model.ModelChange; import org.apache.gravitino.model.ModelVersion; import org.apache.gravitino.utils.PrincipalUtils; @@ -275,6 +279,25 @@ public class ModelEventDispatcher implements ModelDispatcher { } } + /** {@inheritDoc} */ + @Override + public Model alterModel(NameIdentifier ident, ModelChange... changes) + throws NoSuchModelException, IllegalArgumentException { + String user = PrincipalUtils.getCurrentUserName(); + + eventBus.dispatchEvent(new AlterModelPreEvent(user, ident, changes)); + try { + Model modelObject = dispatcher.alterModel(ident, changes); + ModelInfo modelInfo = new ModelInfo(modelObject); + eventBus.dispatchEvent(new AlterModelEvent(user, ident, modelInfo, changes)); + + return modelObject; + } catch (Exception e) { + eventBus.dispatchEvent(new AlterModelFailureEvent(user, ident, e, changes)); + throw e; + } + } + /** {@inheritDoc} */ @Override public int[] listModelVersions(NameIdentifier ident) throws NoSuchModelException { diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/AlterModelEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterModelEvent.java new file mode 100644 index 0000000000..5b3c80ae8e --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterModelEvent.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.gravitino.listener.api.event; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.listener.api.info.ModelInfo; +import org.apache.gravitino.model.ModelChange; + +/** Represents an event fired when a model is successfully altered. */ +public class AlterModelEvent extends ModelEvent { + private final ModelInfo updatedModelInfo; + private final ModelChange[] modelChanges; + + /** + * Constructs a new {@link AlterModelEvent} instance with specified user, identifier, + * updatedModelInfo, and modelChanges. + * + * @param user The username of the individual responsible for initiating the model alteration. + * @param identifier The unique identifier of the altered model, serving as a clear reference + * point for the model in question. + * @param updatedModelInfo The post-alteration state of the model. + * @param modelChanges An array of {@link ModelChange} objects representing the specific changes + * applied to the model during the alteration process. + */ + public AlterModelEvent( + String user, + NameIdentifier identifier, + ModelInfo updatedModelInfo, + ModelChange[] modelChanges) { + super(user, identifier); + this.updatedModelInfo = updatedModelInfo; + this.modelChanges = modelChanges; + } + + /** + * Retrieves the updated state of the model after the successful alteration. + * + * @return A {@link ModelInfo} instance encapsulating the details of the altered model. + */ + public ModelInfo updatedModelInfo() { + return updatedModelInfo; + } + + /** + * Retrieves the specific changes that were made to the model during the alteration process. + * + * @return An array of {@link ModelChange} objects detailing each modification applied to the + * model. + */ + public ModelChange[] modelChanges() { + return modelChanges; + } + + /** + * Returns the type of operation. + * + * @return the operation type. + */ + @Override + public OperationType operationType() { + return OperationType.ALTER_MODEL; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/AlterModelFailureEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterModelFailureEvent.java new file mode 100644 index 0000000000..2aeab89587 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterModelFailureEvent.java @@ -0,0 +1,65 @@ +/* + * 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.gravitino.listener.api.event; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.model.ModelChange; + +/** + * Represents an event that is triggered when an attempt to alter a model fails due to an exception. + */ +public class AlterModelFailureEvent extends FailureEvent { + private final ModelChange[] modelChanges; + + /** + * Constructs a new {@link AlterModelFailureEvent} instance with the specified user, identifier, + * exception, and model changes. + * + * @param user The user who initiated the model alteration operation. + * @param identifier The identifier of the model that was attempted to be altered. + * @param exception The exception that was thrown during the model alteration operation. + * @param modelChanges The changes that were attempted on the model. + */ + public AlterModelFailureEvent( + String user, NameIdentifier identifier, Exception exception, ModelChange[] modelChanges) { + super(user, identifier, exception); + this.modelChanges = modelChanges; + } + + /** + * Retrieves the changes that were attempted on the model. + * + * @return An array of {@link ModelChange} objects detailing each modification applied to the + * model. + */ + public ModelChange[] modelChanges() { + return modelChanges; + } + + /** + * Returns the type of operation. + * + * @return the operation type. + */ + @Override + public OperationType operationType() { + return OperationType.ALTER_MODEL; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/AlterModelPreEvent.java b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterModelPreEvent.java new file mode 100644 index 0000000000..1ad66e6369 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterModelPreEvent.java @@ -0,0 +1,53 @@ +/* + * 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.gravitino.listener.api.event; + +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.model.ModelChange; + +/** Represents an event triggered before altering a model. */ +public class AlterModelPreEvent extends PreEvent { + private final ModelChange[] modelChanges; + + public AlterModelPreEvent(String user, NameIdentifier identifier, ModelChange[] modelChanges) { + super(user, identifier); + this.modelChanges = modelChanges; + } + + /** + * Retrieves the specific changes that were made to the model during the alteration process. + * + * @return An array of {@link ModelChange} objects detailing each modification applied to the + * model. + */ + public ModelChange[] modelChanges() { + return modelChanges; + } + + /** + * Returns the type of operation. + * + * @return the operation type. + */ + @Override + public OperationType operationType() { + return OperationType.ALTER_MODEL; + } +} diff --git a/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java b/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java index a7ff5da95b..0f7a0d2098 100644 --- a/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java +++ b/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java @@ -104,6 +104,7 @@ public enum OperationType { DELETE_MODEL, GET_MODEL, LIST_MODEL, + ALTER_MODEL, // Model Version LINK_MODEL_VERSION, diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java index a089251297..3ce1c75f20 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java @@ -203,6 +203,8 @@ public class JDBCBackend implements RelationalBackend { return (E) RoleMetaService.getInstance().updateRole(ident, updater); case TAG: return (E) TagMetaService.getInstance().updateTag(ident, updater); + case MODEL: + return (E) ModelMetaService.getInstance().updateModel(ident, updater); default: throw new UnsupportedEntityTypeException( "Unsupported entity type: %s for update operation", entityType); diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelMetaMapper.java b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelMetaMapper.java index b16c556c71..eba3a0a43c 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelMetaMapper.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelMetaMapper.java @@ -87,4 +87,8 @@ public interface ModelMetaMapper { @UpdateProvider(type = ModelMetaSQLProviderFactory.class, method = "updateModelLatestVersion") Integer updateModelLatestVersion(@Param("modelId") Long modelId); + + @UpdateProvider(type = ModelMetaSQLProviderFactory.class, method = "updateModelMeta") + Integer updateModelMeta( + @Param("newModelMeta") ModelPO newModelPO, @Param("oldModelMeta") ModelPO oldModelPO); } diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelMetaSQLProviderFactory.java b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelMetaSQLProviderFactory.java index 796009737b..e4f7fe8161 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelMetaSQLProviderFactory.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelMetaSQLProviderFactory.java @@ -106,4 +106,9 @@ public class ModelMetaSQLProviderFactory { public static String updateModelLatestVersion(@Param("modelId") Long modelId) { return getProvider().updateModelLatestVersion(modelId); } + + public static String updateModelMeta( + @Param("newModelMeta") ModelPO newModelPO, @Param("oldModelMeta") ModelPO oldModelPO) { + return getProvider().updateModelMeta(newModelPO, oldModelPO); + } } diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelMetaBaseSQLProvider.java b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelMetaBaseSQLProvider.java index aa0c42fe57..42a3a9d2f3 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelMetaBaseSQLProvider.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelMetaBaseSQLProvider.java @@ -159,4 +159,29 @@ public class ModelMetaBaseSQLProvider { + " SET model_latest_version = model_latest_version + 1" + " WHERE model_id = #{modelId} AND deleted_at = 0"; } + + public String updateModelMeta( + @Param("newModelMeta") ModelPO newModelPO, @Param("oldModelMeta") ModelPO oldModelPO) { + return "UPDATE " + + ModelMetaMapper.TABLE_NAME + + " SET model_name = #{newModelMeta.modelName}," + + " metalake_id = #{newModelMeta.metalakeId}," + + " catalog_id = #{newModelMeta.catalogId}," + + " schema_id = #{newModelMeta.schemaId}," + + " model_comment = #{newModelMeta.modelComment}," + + " model_properties = #{newModelMeta.modelProperties}," + + " model_latest_version = #{newModelMeta.modelLatestVersion}," + + " audit_info = #{newModelMeta.auditInfo}," + + " deleted_at = #{newModelMeta.deletedAt}" + + " WHERE model_id = #{oldModelMeta.modelId}" + + " AND model_name = #{oldModelMeta.modelName}" + + " AND metalake_id = #{oldModelMeta.metalakeId}" + + " AND catalog_id = #{oldModelMeta.catalogId}" + + " AND schema_id = #{oldModelMeta.schemaId}" + + " AND model_comment = #{oldModelMeta.modelComment}" + + " AND model_properties = #{oldModelMeta.modelProperties}" + + " AND model_latest_version = #{oldModelMeta.modelLatestVersion}" + + " AND audit_info = #{oldModelMeta.auditInfo}" + + " AND deleted_at = 0"; + } } diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/service/ModelMetaService.java b/core/src/main/java/org/apache/gravitino/storage/relational/service/ModelMetaService.java index 0197dfdd2d..7e32af2f4b 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/service/ModelMetaService.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/service/ModelMetaService.java @@ -19,12 +19,16 @@ package org.apache.gravitino.storage.relational.service; +import com.google.common.base.Preconditions; import java.io.IOException; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.stream.Collectors; import org.apache.gravitino.Entity; +import org.apache.gravitino.HasIdentifier; import org.apache.gravitino.NameIdentifier; import org.apache.gravitino.Namespace; import org.apache.gravitino.exceptions.NoSuchEntityException; @@ -196,4 +200,38 @@ public class ModelMetaService { } return modelPO; } + + public <E extends Entity & HasIdentifier> ModelEntity updateModel( + NameIdentifier identifier, Function<E, E> updater) throws IOException { + NameIdentifierUtil.checkModel(identifier); + + ModelPO oldModelPO = getModelPOByIdentifier(identifier); + ModelEntity oldModelEntity = POConverters.fromModelPO(oldModelPO, identifier.namespace()); + ModelEntity newEntity = (ModelEntity) updater.apply((E) oldModelEntity); + Preconditions.checkArgument( + Objects.equals(oldModelEntity.id(), newEntity.id()), + "The updated model entity id: %s should be same with the table entity id before: %s", + newEntity.id(), + oldModelEntity.id()); + + Integer updateResult; + try { + updateResult = + SessionUtils.doWithCommitAndFetchResult( + ModelMetaMapper.class, + mapper -> + mapper.updateModelMeta( + POConverters.updateModelPO(oldModelPO, newEntity), oldModelPO)); + } catch (RuntimeException re) { + ExceptionUtils.checkSQLException( + re, Entity.EntityType.CATALOG, newEntity.nameIdentifier().toString()); + throw re; + } + + if (updateResult > 0) { + return newEntity; + } else { + throw new IOException("Failed to update the entity: " + identifier); + } + } } diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java index 12737cd075..731f1c23a2 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java @@ -1356,6 +1356,25 @@ public class POConverters { } } + public static ModelPO updateModelPO(ModelPO oldModelPO, ModelEntity newModel) { + try { + return ModelPO.builder() + .withModelId(newModel.id()) + .withModelName(newModel.name()) + .withMetalakeId(oldModelPO.getMetalakeId()) + .withCatalogId(oldModelPO.getCatalogId()) + .withSchemaId(oldModelPO.getSchemaId()) + .withModelComment(newModel.comment()) + .withModelLatestVersion(newModel.latestVersion()) + .withModelProperties(JsonUtils.anyFieldMapper().writeValueAsString(newModel.properties())) + .withAuditInfo(JsonUtils.anyFieldMapper().writeValueAsString(newModel.auditInfo())) + .withDeletedAt(DEFAULT_DELETED_AT) + .build(); + } catch (JsonProcessingException e) { + throw new RuntimeException("Failed to serialize json object:", e); + } + } + public static ModelVersionPO initializeModelVersionPO( ModelVersionEntity modelVersionEntity, ModelVersionPO.Builder builder) { try { diff --git a/core/src/test/java/org/apache/gravitino/catalog/TestModelOperationDispatcher.java b/core/src/test/java/org/apache/gravitino/catalog/TestModelOperationDispatcher.java index 10bb85a1e1..b7aada3eef 100644 --- a/core/src/test/java/org/apache/gravitino/catalog/TestModelOperationDispatcher.java +++ b/core/src/test/java/org/apache/gravitino/catalog/TestModelOperationDispatcher.java @@ -39,6 +39,7 @@ import org.apache.gravitino.exceptions.NoSuchModelException; import org.apache.gravitino.exceptions.NoSuchModelVersionException; import org.apache.gravitino.lock.LockManager; import org.apache.gravitino.model.Model; +import org.apache.gravitino.model.ModelChange; import org.apache.gravitino.model.ModelVersion; import org.apache.gravitino.utils.NameIdentifierUtil; import org.junit.jupiter.api.Assertions; @@ -254,6 +255,29 @@ public class TestModelOperationDispatcher extends TestOperationDispatcher { () -> modelOperationDispatcher.getModelVersion(modelIdent, "alias2")); } + @Test + public void testRenameModel() { + String schemaName = "test_rename_model_schema"; + String newModelName = "new_model_name"; + String modelComment = "model which tests rename"; + NameIdentifier schemaIdent = NameIdentifier.of(metalake, catalog, schemaName); + schemaOperationDispatcher.createSchema( + schemaIdent, "comment", ImmutableMap.of("k1", "v1", "k2", "v2")); + + String modelName = "test_rename_model"; + NameIdentifier modelIdent = + NameIdentifierUtil.ofModel(metalake, catalog, schemaName, modelName); + Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2"); + Model model = modelOperationDispatcher.registerModel(modelIdent, modelComment, props); + + ModelChange[] changeComment = new ModelChange[] {ModelChange.rename(newModelName)}; + Model alteredModel = modelOperationDispatcher.alterModel(modelIdent, changeComment); + + Assertions.assertEquals(newModelName, alteredModel.name()); + Assertions.assertEquals(modelComment, alteredModel.comment()); + Assertions.assertEquals(model.properties(), alteredModel.properties()); + } + private String randomSchemaName() { return "schema_" + UUID.randomUUID().toString().replace("-", ""); } diff --git a/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java b/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java index f7775ef32e..c0a3f87588 100644 --- a/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java +++ b/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java @@ -74,6 +74,7 @@ import org.apache.gravitino.messaging.TopicChange; import org.apache.gravitino.meta.AuditInfo; import org.apache.gravitino.model.Model; import org.apache.gravitino.model.ModelCatalog; +import org.apache.gravitino.model.ModelChange; import org.apache.gravitino.model.ModelVersion; import org.apache.gravitino.rel.Column; import org.apache.gravitino.rel.Table; @@ -895,6 +896,48 @@ public class TestCatalogOperations return true; } + @Override + public Model alterModel(NameIdentifier ident, ModelChange... changes) + throws NoSuchModelException, IllegalArgumentException { + if (!models.containsKey(ident)) { + throw new NoSuchModelException("Model %s does not exist", ident); + } + + AuditInfo updatedAuditInfo = + AuditInfo.builder() + .withCreator("test") + .withCreateTime(Instant.now()) + .withLastModifier("test") + .withLastModifiedTime(Instant.now()) + .build(); + + TestModel model = models.get(ident); + Map<String, String> newProps = + model.properties() != null ? Maps.newHashMap(model.properties()) : Maps.newHashMap(); + + NameIdentifier newIdent = ident; + for (ModelChange change : changes) { + if (change instanceof ModelChange.RenameModel) { + String newName = ((ModelChange.RenameModel) change).newName(); + newIdent = NameIdentifier.of(ident.namespace(), newName); + if (models.containsKey(newIdent)) { + throw new ModelAlreadyExistsException("Model %s already exists", ident); + } + } + } + TestModel updatedModel = + TestModel.builder() + .withName(newIdent.name()) + .withComment(model.comment()) + .withProperties(new HashMap<>(newProps)) + .withAuditInfo(updatedAuditInfo) + .withLatestVersion(model.latestVersion()) + .build(); + + models.put(ident, updatedModel); + return updatedModel; + } + private boolean hasCallerContext() { return CallerContext.CallerContextHolder.get() != null && CallerContext.CallerContextHolder.get().context() != null diff --git a/core/src/test/java/org/apache/gravitino/listener/api/event/TestModelEvent.java b/core/src/test/java/org/apache/gravitino/listener/api/event/TestModelEvent.java index a23b82e31c..c86b83040f 100644 --- a/core/src/test/java/org/apache/gravitino/listener/api/event/TestModelEvent.java +++ b/core/src/test/java/org/apache/gravitino/listener/api/event/TestModelEvent.java @@ -38,6 +38,7 @@ import org.apache.gravitino.listener.ModelEventDispatcher; import org.apache.gravitino.listener.api.info.ModelInfo; import org.apache.gravitino.listener.api.info.ModelVersionInfo; import org.apache.gravitino.model.Model; +import org.apache.gravitino.model.ModelChange; import org.apache.gravitino.model.ModelVersion; import org.apache.gravitino.utils.NameIdentifierUtil; import org.junit.jupiter.api.Assertions; @@ -52,6 +53,8 @@ public class TestModelEvent { private DummyEventListener dummyEventListener; private Model modelA; private Model modelB; + private Model alterNameModel; + private ModelChange modelRenameChange; private NameIdentifier existingIdentA; private NameIdentifier existingIdentB; private NameIdentifier notExistingIdent; @@ -67,6 +70,8 @@ public class TestModelEvent { this.modelA = getMockModel("modelA", "commentA"); this.modelB = getMockModel("modelB", "commentB"); + this.alterNameModel = getMockModel("modelA_rename", "commentA"); + this.modelRenameChange = getMockModelChange("modelA_rename"); this.firstModelVersion = mockModelVersion("uriA", new String[] {"aliasProduction"}, "versionInfoA"); @@ -716,6 +721,63 @@ public class TestModelEvent { Assertions.assertEquals(existingIdentA, listModelVersionsFailureEvent.identifier()); } + @Test + void testAlterModelPreEvent() { + dispatcher.alterModel(existingIdentA, modelRenameChange); + + // validate pre-event + PreEvent preEvent = dummyEventListener.popPreEvent(); + Assertions.assertEquals(AlterModelPreEvent.class, preEvent.getClass()); + Assertions.assertEquals(OperationType.ALTER_MODEL, preEvent.operationType()); + Assertions.assertEquals(OperationStatus.UNPROCESSED, preEvent.operationStatus()); + + AlterModelPreEvent alterModelPreEvent = (AlterModelPreEvent) preEvent; + Assertions.assertEquals(existingIdentA, alterModelPreEvent.identifier()); + ModelChange[] changes = alterModelPreEvent.modelChanges(); + Assertions.assertEquals(1, changes.length); + Assertions.assertEquals(modelRenameChange, changes[0]); + } + + @Test + void testAlterModelEvent() { + dispatcher.alterModel(existingIdentA, modelRenameChange); + + // validate post-event + Event postEvent = dummyEventListener.popPostEvent(); + Assertions.assertEquals(AlterModelEvent.class, postEvent.getClass()); + Assertions.assertEquals(OperationType.ALTER_MODEL, postEvent.operationType()); + Assertions.assertEquals(OperationStatus.SUCCESS, postEvent.operationStatus()); + + AlterModelEvent alterModelEvent = (AlterModelEvent) postEvent; + ModelChange[] changes = alterModelEvent.modelChanges(); + Assertions.assertEquals(1, changes.length); + Assertions.assertEquals(modelRenameChange, changes[0]); + ModelInfo modelInfo = alterModelEvent.updatedModelInfo(); + + checkModelInfo(modelInfo, alterNameModel); + } + + @Test + void testAlterModelFailureEvent() { + Assertions.assertThrowsExactly( + GravitinoRuntimeException.class, + () -> failureDispatcher.alterModel(existingIdentA, modelRenameChange)); + + // validate failure event + Event event = dummyEventListener.popPostEvent(); + + Assertions.assertEquals(AlterModelFailureEvent.class, event.getClass()); + Assertions.assertEquals( + GravitinoRuntimeException.class, ((AlterModelFailureEvent) event).exception().getClass()); + Assertions.assertEquals(OperationType.ALTER_MODEL, event.operationType()); + Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus()); + + AlterModelFailureEvent alterModelFailureEvent = (AlterModelFailureEvent) event; + ModelChange[] changes = alterModelFailureEvent.modelChanges(); + Assertions.assertEquals(1, changes.length); + Assertions.assertEquals(modelRenameChange, changes[0]); + } + private ModelDispatcher mockExceptionModelDispatcher() { return mock( ModelDispatcher.class, @@ -756,6 +818,8 @@ public class TestModelEvent { when(dispatcher.deleteModelVersion(existingIdentA, 3)).thenReturn(false); when(dispatcher.listModelVersions(existingIdentA)).thenReturn(new int[] {1, 2}); + when(dispatcher.alterModel(existingIdentA, new ModelChange[] {modelRenameChange})) + .thenReturn(alterNameModel); return dispatcher; } @@ -869,4 +933,11 @@ public class TestModelEvent { Assertions.assertEquals(expected.length, actual.length); Assertions.assertArrayEquals(expected, actual); } + + private ModelChange getMockModelChange(String newName) { + ModelChange.RenameModel mockObject = mock(ModelChange.RenameModel.class); + when(mockObject.newName()).thenReturn(newName); + + return mockObject; + } } diff --git a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelMetaService.java b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelMetaService.java index 067edd5a26..5f4ac8c3cb 100644 --- a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelMetaService.java +++ b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelMetaService.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.time.Instant; import java.util.List; import java.util.Map; +import java.util.function.Function; import org.apache.gravitino.EntityAlreadyExistsException; import org.apache.gravitino.NameIdentifier; import org.apache.gravitino.Namespace; @@ -199,4 +200,46 @@ public class TestModelMetaService extends TestJDBCBackend { ModelMetaService.getInstance() .deleteModel(NameIdentifier.of(METALAKE_NAME, CATALOG_NAME, "inexistent", "model1"))); } + + @Test + void testAlterModel() throws IOException { + createParentEntities(METALAKE_NAME, CATALOG_NAME, SCHEMA_NAME, auditInfo); + Map<String, String> properties = ImmutableMap.of("k1", "v1"); + String newName = "new_model_name"; + + ModelEntity modelEntity = + createModelEntity( + RandomIdGenerator.INSTANCE.nextId(), + MODEL_NS, + "model1", + "model1 comment", + 0, + properties, + auditInfo); + + Assertions.assertDoesNotThrow( + () -> ModelMetaService.getInstance().insertModel(modelEntity, false)); + + ModelEntity updatedModel = + ModelEntity.builder() + .withId(modelEntity.id()) + .withName(newName) + .withNamespace(modelEntity.namespace()) + .withLatestVersion(modelEntity.latestVersion()) + .withAuditInfo(modelEntity.auditInfo()) + .withComment(modelEntity.comment()) + .build(); + + Function<ModelEntity, ModelEntity> renameUpdater = oldModel -> updatedModel; + ModelEntity alteredModel = + ModelMetaService.getInstance().updateModel(modelEntity.nameIdentifier(), renameUpdater); + + Assertions.assertEquals(alteredModel, updatedModel); + // Test update an in-existent model + Assertions.assertThrows( + NoSuchEntityException.class, + () -> + ModelMetaService.getInstance() + .updateModel(NameIdentifier.of(MODEL_NS, "model3"), renameUpdater)); + } }