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));
+  }
 }

Reply via email to