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 450b1788d8 [#6525] feature(core): Support update comment operations 
for model alteration. (#7041)
450b1788d8 is described below

commit 450b1788d849a314199166efb824a52693e721d0
Author: Lord of Abyss <[email protected]>
AuthorDate: Wed Apr 23 14:08:34 2025 +0800

    [#6525] feature(core): Support update comment operations for model 
alteration. (#7041)
    
    ### What changes were proposed in this pull request?
    
    Support update comment operations for model alteration.
    
    ### Why are the changes needed?
    
    Fix: #6525
    
    ### Does this PR introduce _any_ user-facing change?
    
    User can update a comment of a model.
    
    ### How was this patch tested?
    
    local test + ut + it.
    
    `bin/gcli.sh model update -m demo_metalake --name
    model_catalog.schema.model2 --comment 'new_comment'`
    
    
    
![image](https://github.com/user-attachments/assets/d4f64e33-e307-4b2a-a225-0b598deac994)
    
    `bin/gcli.sh model update -m demo_metalake --name
    model_catalog.schema.model2 --comment ''`
    
    
    
![image](https://github.com/user-attachments/assets/bfe3872b-9167-412c-8b84-7c0b003fb5a6)
---
 .../org/apache/gravitino/model/ModelChange.java    | 71 +++++++++++++++++
 .../apache/gravitino/model/TestModelChange.java    | 38 +++++++++
 .../catalog/model/ModelCatalogOperations.java      |  6 ++
 .../catalog/model/TestModelCatalogOperations.java  | 36 +++++++++
 .../integration/test/ModelCatalogOperationsIT.java | 20 ++++-
 .../apache/gravitino/cli/ModelCommandHandler.java  |  9 +++
 .../apache/gravitino/cli/TestableCommandLine.java  | 11 +++
 .../gravitino/cli/commands/UpdateModelComment.java | 90 ++++++++++++++++++++++
 .../apache/gravitino/cli/TestModelCommands.java    | 30 ++++++++
 .../org/apache/gravitino/client/DTOConverters.java |  4 +
 .../client-python/gravitino/api/model_change.py    | 53 +++++++++++++
 .../gravitino/client/generic_model_catalog.py      |  3 +
 .../gravitino/dto/requests/model_update_request.py | 18 +++++
 .../tests/integration/test_model_catalog.py        | 34 +++++++-
 .../gravitino/dto/requests/ModelUpdateRequest.java | 28 ++++++-
 .../catalog/TestModelOperationDispatcher.java      | 33 +++++++-
 .../gravitino/connector/TestCatalogOperations.java | 11 ++-
 .../relational/service/TestModelMetaService.java   | 89 ++++++++++++++++++++-
 docs/manage-model-metadata-using-gravitino.md      | 19 +++--
 19 files changed, 588 insertions(+), 15 deletions(-)

diff --git a/api/src/main/java/org/apache/gravitino/model/ModelChange.java 
b/api/src/main/java/org/apache/gravitino/model/ModelChange.java
index 1534b9369a..e0622a2313 100644
--- a/api/src/main/java/org/apache/gravitino/model/ModelChange.java
+++ b/api/src/main/java/org/apache/gravitino/model/ModelChange.java
@@ -59,6 +59,16 @@ public interface ModelChange {
     return new ModelChange.RemoveProperty(property);
   }
 
+  /**
+   * Create a ModelChange for updating the comment of a model.
+   *
+   * @param newComment The new comment of the model.
+   * @return A ModelChange for the comment update.
+   */
+  static ModelChange updateComment(String newComment) {
+    return new ModelChange.UpdateComment(newComment);
+  }
+
   /** A ModelChange to rename a model. */
   final class RenameModel implements ModelChange {
     private final String newName;
@@ -253,4 +263,65 @@ public interface ModelChange {
       return "RemoveProperty " + property;
     }
   }
+
+  /** A ModelChange to update the comment of a model. */
+  final class UpdateComment implements ModelChange {
+    private final String newComment;
+
+    /**
+     * Constructs a new {@link UpdateComment} instance with the specified new 
comment.
+     *
+     * @param newComment The new comment of the model.
+     */
+    public UpdateComment(String newComment) {
+      this.newComment = newComment;
+    }
+
+    /**
+     * Retrieves the new comment for the model.
+     *
+     * @return The new comment of the model.
+     */
+    public String newComment() {
+      return newComment;
+    }
+
+    /**
+     * Compares this UpdateComment instance with another object for equality. 
The comparison is
+     * based on the new comment of the model.
+     *
+     * @param obj The object to compare with this instance.
+     * @return {@code true} if the given object represents the same model 
comment update; {@code
+     *     false} otherwise.
+     */
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == this) return true;
+      if (!(obj instanceof UpdateComment)) return false;
+      UpdateComment other = (UpdateComment) obj;
+      return Objects.equals(newComment, other.newComment);
+    }
+
+    /**
+     * Generates a hash code for this UpdateComment instance. The hash code is 
based on the new
+     * comment of the model.
+     *
+     * @return A hash code value for this model comment update operation.
+     */
+    @Override
+    public int hashCode() {
+      return Objects.hash(newComment);
+    }
+
+    /**
+     * Provides a string representation of the UpdateComment instance. This 
string format includes
+     * the class name followed by the new comment of the model.
+     *
+     * @return A string summary of the model comment update operation.
+     */
+    @Override
+    public String toString() {
+      return "UpdateComment " + newComment;
+    }
+  }
 }
diff --git a/api/src/test/java/org/apache/gravitino/model/TestModelChange.java 
b/api/src/test/java/org/apache/gravitino/model/TestModelChange.java
index a906f8a75b..f79fbc9fe2 100644
--- a/api/src/test/java/org/apache/gravitino/model/TestModelChange.java
+++ b/api/src/test/java/org/apache/gravitino/model/TestModelChange.java
@@ -60,4 +60,42 @@ public class TestModelChange {
     Assertions.assertNotEquals(renameModel1.hashCode(), 
renameModel2.hashCode());
     Assertions.assertEquals(renameModel1.hashCode(), renameModel3.hashCode());
   }
+
+  @Test
+  void testUpdateModelCommentWithStaticMethod() {
+    String newComment = "newComment";
+    ModelChange modelChange = ModelChange.updateComment(newComment);
+    Assertions.assertEquals(ModelChange.UpdateComment.class, 
modelChange.getClass());
+
+    ModelChange.UpdateComment updateComment = (ModelChange.UpdateComment) 
modelChange;
+    Assertions.assertEquals(newComment, updateComment.newComment());
+    Assertions.assertEquals("UpdateComment newComment", 
updateComment.toString());
+  }
+
+  @Test
+  void testUpdateModelCommentWithConstructor() {
+    String newComment = "newComment";
+    ModelChange.UpdateComment updateComment = new 
ModelChange.UpdateComment(newComment);
+    Assertions.assertEquals(newComment, updateComment.newComment());
+    Assertions.assertEquals("UpdateComment newComment", 
updateComment.toString());
+  }
+
+  @Test
+  void testUpdateModelCommentEquals() {
+    String newComment1 = "This is a demo model";
+    String newComment2 = "This is a test model";
+    String newComment3 = "This is a demo model";
+
+    ModelChange.UpdateComment updateComment1 = new 
ModelChange.UpdateComment(newComment1);
+    ModelChange.UpdateComment updateComment2 = new 
ModelChange.UpdateComment(newComment2);
+    ModelChange.UpdateComment updateComment3 = new 
ModelChange.UpdateComment(newComment3);
+
+    Assertions.assertEquals(updateComment1, updateComment3);
+    Assertions.assertNotEquals(updateComment1, updateComment2);
+    Assertions.assertEquals(updateComment1, updateComment3);
+
+    Assertions.assertNotEquals(updateComment1.hashCode(), 
updateComment2.hashCode());
+    Assertions.assertEquals(updateComment1.hashCode(), 
updateComment3.hashCode());
+    Assertions.assertEquals(updateComment1.toString(), 
updateComment3.toString());
+  }
 }
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 87b0d12d45..47b709d957 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
@@ -334,12 +334,18 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     for (ModelChange change : changes) {
       if (change instanceof ModelChange.RenameModel) {
         entityName = ((ModelChange.RenameModel) change).newName();
+
       } else if (change instanceof ModelChange.SetProperty) {
         ModelChange.SetProperty setPropertyChange = (ModelChange.SetProperty) 
change;
         doSetProperty(entityProperties, setPropertyChange);
+
       } else if (change instanceof ModelChange.RemoveProperty) {
         ModelChange.RemoveProperty removePropertyChange = 
(ModelChange.RemoveProperty) change;
         doRemoveProperty(entityProperties, removePropertyChange);
+
+      } else if (change instanceof ModelChange.UpdateComment) {
+        entityComment = ((ModelChange.UpdateComment) change).newComment();
+
       } else {
         throw new IllegalArgumentException(
             "Unsupported model change: " + change.getClass().getSimpleName());
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 77331cf673..882b1ec798 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
@@ -800,6 +800,42 @@ public class TestModelCatalogOperations {
     Assertions.assertEquals(newProperties, alteredModel.properties());
   }
 
+  @Test
+  void testUpdateModelComment() {
+    String schemaName = randomSchemaName();
+    createSchema(schemaName);
+
+    String modelName = "model";
+    String comment = "comment";
+    String newComment = "new comment";
+
+    NameIdentifier modelIdent =
+        NameIdentifierUtil.ofModel(METALAKE_NAME, CATALOG_NAME, schemaName, 
modelName);
+    StringIdentifier stringId = StringIdentifier.fromId(idGenerator.nextId());
+    Map<String, String> properties =
+        StringIdentifier.newPropertiesWithId(stringId, ImmutableMap.of("key1", 
"value1"));
+
+    // validate registered model
+    Model registeredModel = ops.registerModel(modelIdent, comment, properties);
+    Assertions.assertEquals(modelName, registeredModel.name());
+    Assertions.assertEquals(comment, registeredModel.comment());
+    Assertions.assertEquals(properties, registeredModel.properties());
+
+    // validate loaded model
+    Model loadedModel = ops.getModel(modelIdent);
+    Assertions.assertEquals(modelName, loadedModel.name());
+    Assertions.assertEquals(comment, loadedModel.comment());
+    Assertions.assertEquals(properties, loadedModel.properties());
+
+    ModelChange change = ModelChange.updateComment(newComment);
+    Model alteredModel = ops.alterModel(modelIdent, change);
+
+    // validate altered model
+    Assertions.assertEquals(modelName, alteredModel.name());
+    Assertions.assertEquals(newComment, alteredModel.comment());
+    Assertions.assertEquals(properties, alteredModel.properties());
+  }
+
   @Test
   void testUpdateVersionCommentViaVersion() {
     String schemaName = randomSchemaName();
diff --git 
a/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/integration/test/ModelCatalogOperationsIT.java
 
b/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/integration/test/ModelCatalogOperationsIT.java
index 8590878d6d..ecda97cbad 100644
--- 
a/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/integration/test/ModelCatalogOperationsIT.java
+++ 
b/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/integration/test/ModelCatalogOperationsIT.java
@@ -395,7 +395,7 @@ public class ModelCatalogOperationsIT extends BaseIT {
   }
 
   @Test
-  public void testRegisterAndUpdateModel() {
+  public void testRegisterAndRenameModel() {
     String comment = "comment";
     String modelName = RandomNameUtils.genRandomName("alter_name_model");
     String newName = RandomNameUtils.genRandomName("new_name");
@@ -482,6 +482,24 @@ public class ModelCatalogOperationsIT extends BaseIT {
     Assertions.assertEquals(createdModel.comment(), alteredModel.comment());
   }
 
+  @Test
+  void testRegisterAndUpdateModelComment() {
+    String comment = "comment";
+    String newComment = "new comment";
+    String modelName = RandomNameUtils.genRandomName("alter_name_model");
+    NameIdentifier modelIdent = NameIdentifier.of(schemaName, modelName);
+    Map<String, String> properties = ImmutableMap.of("owner", "data-team", 
"key1", "val1");
+
+    Model createdModel =
+        gravitinoCatalog.asModelCatalog().registerModel(modelIdent, comment, 
properties);
+    ModelChange change = ModelChange.updateComment(newComment);
+    Model alteredModel = 
gravitinoCatalog.asModelCatalog().alterModel(modelIdent, change);
+
+    Assertions.assertEquals(createdModel.name(), alteredModel.name());
+    Assertions.assertEquals(createdModel.properties(), 
alteredModel.properties());
+    Assertions.assertEquals(newComment, alteredModel.comment());
+  }
+
   @Test
   void testLinkAndSetModelVersionProperties() {
     String modelName = RandomNameUtils.genRandomName("model1");
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/ModelCommandHandler.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/ModelCommandHandler.java
index ee15d3a88f..7dec8bfb05 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/ModelCommandHandler.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/ModelCommandHandler.java
@@ -181,6 +181,15 @@ public class ModelCommandHandler extends CommandHandler {
           .handle();
     }
 
+    if (line.hasOption(GravitinoOptions.COMMENT)
+        && !(line.hasOption(GravitinoOptions.ALIAS) || 
line.hasOption(GravitinoOptions.VERSION))) {
+      String newComment = line.getOptionValue(GravitinoOptions.COMMENT);
+      gravitinoCommandLine
+          .newUpdateModelComment(context, metalake, catalog, schema, model, 
newComment)
+          .validate()
+          .handle();
+    }
+
     if (!line.hasOption(GravitinoOptions.URI)
         && line.hasOption(GravitinoOptions.COMMENT)
         && (line.hasOption(GravitinoOptions.ALIAS) || 
line.hasOption(GravitinoOptions.VERSION))) {
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java
index b32a758ef6..81453fc305 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java
@@ -136,6 +136,7 @@ import 
org.apache.gravitino.cli.commands.UpdateFilesetComment;
 import org.apache.gravitino.cli.commands.UpdateFilesetName;
 import org.apache.gravitino.cli.commands.UpdateMetalakeComment;
 import org.apache.gravitino.cli.commands.UpdateMetalakeName;
+import org.apache.gravitino.cli.commands.UpdateModelComment;
 import org.apache.gravitino.cli.commands.UpdateModelName;
 import org.apache.gravitino.cli.commands.UpdateModelVersionComment;
 import org.apache.gravitino.cli.commands.UpdateModelVersionUri;
@@ -889,6 +890,16 @@ public class TestableCommandLine {
     return new UpdateModelName(context, metalake, catalog, schema, model, 
rename);
   }
 
+  protected UpdateModelComment newUpdateModelComment(
+      CommandContext context,
+      String metalake,
+      String catalog,
+      String schema,
+      String model,
+      String comment) {
+    return new UpdateModelComment(context, metalake, catalog, schema, model, 
comment);
+  }
+
   protected UpdateModelVersionComment newUpdateModelVersionComment(
       CommandContext context,
       String metalake,
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateModelComment.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateModelComment.java
new file mode 100644
index 0000000000..1e34189f97
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateModelComment.java
@@ -0,0 +1,90 @@
+/*
+ * 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.cli.commands;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.cli.CommandContext;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchModelException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.model.ModelChange;
+
+/** Update the comment of a model. */
+public class UpdateModelComment extends Command {
+  protected final String metalake;
+  protected final String catalog;
+  protected final String schema;
+  protected final String model;
+  protected final String comment;
+
+  /**
+   * Construct a new {@link UpdateModelComment} instance.
+   *
+   * @param context The command context.
+   * @param metalake The metalake name.
+   * @param catalog The catalog name.
+   * @param schema The schema name.
+   * @param model The model name.
+   * @param comment The new comment.
+   */
+  public UpdateModelComment(
+      CommandContext context,
+      String metalake,
+      String catalog,
+      String schema,
+      String model,
+      String comment) {
+    super(context);
+    this.metalake = metalake;
+    this.catalog = catalog;
+    this.schema = schema;
+    this.model = model;
+    this.comment = comment;
+  }
+
+  /** Update the comment of a model. */
+  @Override
+  public void handle() {
+    NameIdentifier modelIdent;
+
+    try {
+      modelIdent = NameIdentifier.of(schema, model);
+      GravitinoClient client = buildClient(metalake);
+      ModelChange updateCommentChange = ModelChange.updateComment(comment);
+
+      client.loadCatalog(catalog).asModelCatalog().alterModel(modelIdent, 
updateCommentChange);
+    } catch (NoSuchMetalakeException err) {
+      exitWithError(ErrorMessages.UNKNOWN_METALAKE);
+    } catch (NoSuchCatalogException err) {
+      exitWithError(ErrorMessages.UNKNOWN_CATALOG);
+    } catch (NoSuchSchemaException err) {
+      exitWithError(ErrorMessages.UNKNOWN_SCHEMA);
+    } catch (NoSuchModelException err) {
+      exitWithError(ErrorMessages.UNKNOWN_MODEL);
+    } catch (Exception exp) {
+      exitWithError(exp.getMessage());
+    }
+
+    printInformation(model + " comment changed.");
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommands.java
index 99b17bce23..7919e85200 100644
--- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommands.java
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommands.java
@@ -50,6 +50,7 @@ import org.apache.gravitino.cli.commands.RemoveModelProperty;
 import org.apache.gravitino.cli.commands.RemoveModelVersionProperty;
 import org.apache.gravitino.cli.commands.SetModelProperty;
 import org.apache.gravitino.cli.commands.SetModelVersionProperty;
+import org.apache.gravitino.cli.commands.UpdateModelComment;
 import org.apache.gravitino.cli.commands.UpdateModelName;
 import org.apache.gravitino.cli.commands.UpdateModelVersionComment;
 import org.apache.gravitino.cli.commands.UpdateModelVersionUri;
@@ -667,6 +668,35 @@ public class TestModelCommands {
     verify(mockRemoveProperty).handle();
   }
 
+  @Test
+  void testUpdateModelComment() {
+    UpdateModelComment mockUpdate = mock(UpdateModelComment.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.model");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("new 
comment");
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.MODEL, 
CommandActions.UPDATE));
+
+    doReturn(mockUpdate)
+        .when(commandLine)
+        .newUpdateModelComment(
+            any(CommandContext.class),
+            eq("metalake_demo"),
+            eq("catalog"),
+            eq("schema"),
+            eq("model"),
+            eq("new comment"));
+    doReturn(mockUpdate).when(mockUpdate).validate();
+    commandLine.handleCommandLine();
+    verify(mockUpdate).handle();
+  }
+
   @Test
   void testUpdateModelVersionCommentCommand() {
     UpdateModelVersionComment mockUpdate = 
mock(UpdateModelVersionComment.class);
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
index f683f1ad94..f911d4874e 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
@@ -374,6 +374,10 @@ class DTOConverters {
           ((ModelChange.SetProperty) change).property(),
           ((ModelChange.SetProperty) change).value());
 
+    } else if (change instanceof ModelChange.UpdateComment) {
+      return new ModelUpdateRequest.UpdateModelCommentRequest(
+          ((ModelChange.UpdateComment) change).newComment());
+
     } else {
       throw new IllegalArgumentException(
           "Unknown model change type: " + change.getClass().getSimpleName());
diff --git a/clients/client-python/gravitino/api/model_change.py 
b/clients/client-python/gravitino/api/model_change.py
index 99737d7823..16041cfe17 100644
--- a/clients/client-python/gravitino/api/model_change.py
+++ b/clients/client-python/gravitino/api/model_change.py
@@ -55,6 +55,16 @@ class ModelChange(ABC):
         """
         return ModelChange.RemoveProperty(pro)
 
+    @staticmethod
+    def update_comment(comment):
+        """Creates a new model change to update the comment of the model.
+        Args:
+            comment: The new comment of the model.
+        Returns:
+            The model change.
+        """
+        return ModelChange.UpdateComment(comment)
+
     class RenameModel:
         """A model change to rename the model."""
 
@@ -193,3 +203,46 @@ class ModelChange(ABC):
                 A string summary of this property removal operation.
             """
             return f"REMOVEPROPERTY {self.property()}"
+
+    class UpdateComment:
+        """
+        A model change to update the comment of the model.
+        """
+
+        def __init__(self, new_comment):
+            self._new_comment = new_comment
+
+        def new_comment(self):
+            """Retrieves the comment of the model.
+            Returns:
+                The comment of the model.
+            """
+            return self._new_comment
+
+        def __eq__(self, other) -> bool:
+            """Compares this UpdateComment instance with another object for 
equality. Two instances are
+            considered equal if they designate the same comment for the model.
+            Args:
+                other: The object to compare with this instance.
+            Returns:
+                true if the given object represents an identical model comment 
update operation; false otherwise.
+            """
+            if not isinstance(other, ModelChange.UpdateComment):
+                return False
+            return self.new_comment() == other.new_comment()
+
+        def __hash__(self):
+            """Generates a hash code for this UpdateComment instance. The hash 
code is primarily based on
+            the comment for the model.
+            Returns:
+                A hash code value for this comment update operation.
+            """
+            return hash(self.new_comment())
+
+        def __str__(self):
+            """Provides a string representation of the UpdateComment instance. 
This string includes the
+            class name followed by the comment of the model.
+            Returns:
+                A string summary of this comment update operation.
+            """
+            return f"UpdateComment {self.new_comment()}"
diff --git a/clients/client-python/gravitino/client/generic_model_catalog.py 
b/clients/client-python/gravitino/client/generic_model_catalog.py
index 21e63b755a..318748781a 100644
--- a/clients/client-python/gravitino/client/generic_model_catalog.py
+++ b/clients/client-python/gravitino/client/generic_model_catalog.py
@@ -523,6 +523,9 @@ class GenericModelCatalog(BaseSchemaCatalog):
         if isinstance(change, ModelChange.RemoveProperty):
             return 
ModelUpdateRequest.ModelRemovePropertyRequest(change.property())
 
+        if isinstance(change, ModelChange.UpdateComment):
+            return 
ModelUpdateRequest.UpdateModelCommentRequest(change.new_comment())
+
         raise ValueError(f"Unknown change type: {type(change).__name__}")
 
     @staticmethod
diff --git 
a/clients/client-python/gravitino/dto/requests/model_update_request.py 
b/clients/client-python/gravitino/dto/requests/model_update_request.py
index 5769f2015f..7e4fed0863 100644
--- a/clients/client-python/gravitino/dto/requests/model_update_request.py
+++ b/clients/client-python/gravitino/dto/requests/model_update_request.py
@@ -97,3 +97,21 @@ class ModelUpdateRequest:
 
         def model_change(self) -> ModelChange:
             return ModelChange.remove_property(self._property)
+
+    @dataclass
+    class UpdateModelCommentRequest(ModelUpdateRequestBase):
+        """Request to update model comment"""
+
+        _new_comment: Optional[str] = 
field(metadata=config(field_name="newComment"))
+        """Represents a request to update the comment on a Metalake."""
+
+        def __init__(self, new_comment: str):
+            super().__init__("updateComment")
+            self._new_comment = new_comment
+
+        def validate(self):
+            """Validates the fields of the request. Always pass."""
+            pass
+
+        def model_change(self):
+            return ModelChange.update_comment(self._new_comment)
diff --git a/clients/client-python/tests/integration/test_model_catalog.py 
b/clients/client-python/tests/integration/test_model_catalog.py
index 3ef9ea8b08..5d0eb9218a 100644
--- a/clients/client-python/tests/integration/test_model_catalog.py
+++ b/clients/client-python/tests/integration/test_model_catalog.py
@@ -201,7 +201,7 @@ class TestModelCatalog(IntegrationTestEnv):
             )
         )
 
-    def test_register_alter_model(self):
+    def test_register_rename_model(self):
         model_name = f"model_it_model{str(randint(0, 1000))}"
         model_new_name = f"model_it_model_new{str(randint(0, 1000))}"
         model_ident = NameIdentifier.of(self._schema_name, model_name)
@@ -228,7 +228,7 @@ class TestModelCatalog(IntegrationTestEnv):
         self.assertEqual(0, renamed_model.latest_version())
         self.assertEqual(properties, renamed_model.properties())
 
-    def test_register_alter_model_with_set_property(self):
+    def test_register_set_model_property(self):
         model_name = f"model_it_model{str(randint(0, 1000))}"
         model_ident = NameIdentifier.of(self._schema_name, model_name)
         comment = "comment"
@@ -258,7 +258,7 @@ class TestModelCatalog(IntegrationTestEnv):
             update_property_model.properties(), {"k1": "v11", "k2": "v2", 
"k3": "v3"}
         )
 
-    def test_register_alter_model_with_remove_property(self):
+    def test_register_remove_model_property(self):
         model_name = f"model_it_model{str(randint(0, 1000))}"
         model_ident = NameIdentifier.of(self._schema_name, model_name)
         comment = "comment"
@@ -281,7 +281,33 @@ class TestModelCatalog(IntegrationTestEnv):
         self.assertEqual(update_property_model.latest_version(), 0)
         self.assertEqual(update_property_model.properties(), {"k2": "v2"})
 
-    def test_register_alter_model_with_update_comment(self):
+    def test_register_update_model_comment(self):
+        model_name = f"model_it_model{str(randint(0, 1000))}"
+        model_ident = NameIdentifier.of(self._schema_name, model_name)
+        comment = "comment"
+        new_comment = "new comment"
+        properties = {"k1": "v1", "k2": "v2"}
+        self._catalog.as_model_catalog().register_model(
+            model_ident, comment, properties
+        )
+
+        # Retrieve the original model
+        origin_model = self._catalog.as_model_catalog().get_model(model_ident)
+        self.assertEqual(origin_model.name(), model_name)
+        self.assertEqual(origin_model.comment(), comment)
+        self.assertEqual(origin_model.latest_version(), 0)
+        self.assertEqual(origin_model.properties(), properties)
+
+        # Alter model and validate the updated model
+        changes = [ModelChange.update_comment(new_comment)]
+        self._catalog.as_model_catalog().alter_model(model_ident, *changes)
+        update_comment_model = 
self._catalog.as_model_catalog().get_model(model_ident)
+        self.assertEqual(update_comment_model.name(), model_name)
+        self.assertEqual(update_comment_model.comment(), new_comment)
+        self.assertEqual(update_comment_model.latest_version(), 0)
+        self.assertEqual(update_comment_model.properties(), properties)
+
+    def test_link_update_model_version_comment(self):
         model_name = f"model_it_model{str(randint(0, 1000))}"
         model_ident = NameIdentifier.of(self._schema_name, model_name)
         self._catalog.as_model_catalog().register_model(model_ident, 
"comment", {})
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/ModelUpdateRequest.java
 
b/common/src/main/java/org/apache/gravitino/dto/requests/ModelUpdateRequest.java
index ad06d75597..12459d2c1a 100644
--- 
a/common/src/main/java/org/apache/gravitino/dto/requests/ModelUpdateRequest.java
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/ModelUpdateRequest.java
@@ -41,7 +41,12 @@ import org.apache.gravitino.rest.RESTRequest;
   @JsonSubTypes.Type(
       value = ModelUpdateRequest.RemoveModelPropertyRequest.class,
       name = "removeProperty"),
-  @JsonSubTypes.Type(value = ModelUpdateRequest.SetModelPropertyRequest.class, 
name = "setProperty")
+  @JsonSubTypes.Type(
+      value = ModelUpdateRequest.SetModelPropertyRequest.class,
+      name = "setProperty"),
+  @JsonSubTypes.Type(
+      value = ModelUpdateRequest.UpdateModelCommentRequest.class,
+      name = "updateComment")
 })
 public interface ModelUpdateRequest extends RESTRequest {
 
@@ -157,4 +162,25 @@ public interface ModelUpdateRequest extends RESTRequest {
           StringUtils.isNotBlank(property), "\"property\" field is required 
and cannot be empty");
     }
   }
+
+  /** The model update request for update comment of model. */
+  @EqualsAndHashCode
+  @AllArgsConstructor
+  @NoArgsConstructor(force = true)
+  @ToString
+  @Getter
+  class UpdateModelCommentRequest implements ModelUpdateRequest {
+    @JsonProperty("newComment")
+    private final String newComment;
+
+    /** {@inheritDoc} */
+    @Override
+    public ModelChange modelChange() {
+      return ModelChange.updateComment(newComment);
+    }
+
+    /** Validates the fields of the request. Always pass. */
+    @Override
+    public void validate() throws IllegalArgumentException {}
+  }
 }
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 f059b857c1..eb086a4de3 100644
--- 
a/core/src/test/java/org/apache/gravitino/catalog/TestModelOperationDispatcher.java
+++ 
b/core/src/test/java/org/apache/gravitino/catalog/TestModelOperationDispatcher.java
@@ -339,7 +339,7 @@ public class TestModelOperationDispatcher extends 
TestOperationDispatcher {
   @Test
   void testRemoveModelProperty() {
     String schemaName = "test_remove_model_property_schema";
-    String modelName = "test_update_model_property";
+    String modelName = "test_remove_model_property";
     String modelComment = "model which tests update property";
     NameIdentifier schemaIdent = NameIdentifier.of(metalake, catalog, 
schemaName);
     schemaOperationDispatcher.createSchema(
@@ -364,6 +364,37 @@ public class TestModelOperationDispatcher extends 
TestOperationDispatcher {
     Assertions.assertEquals(ImmutableMap.of("k2", "v2"), 
alteredModel.properties());
   }
 
+  @Test
+  void testUpdateModelComment() {
+    String schemaName = "test_update_model_comment_schema";
+    String modelName = "test_update_model_comment";
+    String modelComment = "model which tests update property";
+    String newModelComment = "new model comment";
+    Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
+
+    NameIdentifier schemaIdent = NameIdentifier.of(metalake, catalog, 
schemaName);
+    schemaOperationDispatcher.createSchema(
+        schemaIdent, "schema comment", ImmutableMap.of("k1", "v1", "k2", 
"v2"));
+
+    NameIdentifier modelIdent =
+        NameIdentifierUtil.ofModel(metalake, catalog, schemaName, modelName);
+
+    Model model = modelOperationDispatcher.registerModel(modelIdent, 
modelComment, props);
+
+    // validate registered model
+    Assertions.assertEquals(modelName, model.name());
+    Assertions.assertEquals(modelComment, model.comment());
+    Assertions.assertEquals(props, model.properties());
+
+    ModelChange change = ModelChange.updateComment(newModelComment);
+    Model alteredModel = modelOperationDispatcher.alterModel(modelIdent, 
change);
+
+    // validate updated model
+    Assertions.assertEquals(modelName, alteredModel.name());
+    Assertions.assertEquals(newModelComment, alteredModel.comment());
+    Assertions.assertEquals(props, alteredModel.properties());
+  }
+
   @Test
   void testUpdateModelVersionComment() {
     String schemaName = randomSchemaName();
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 a0105e0878..8e91f21575 100644
--- 
a/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
+++ 
b/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
@@ -956,12 +956,21 @@ public class TestCatalogOperations
         if (models.containsKey(newIdent)) {
           throw new ModelAlreadyExistsException("Model %s already exists", 
ident);
         }
+
       } else if (change instanceof ModelChange.RemoveProperty) {
         ModelChange.RemoveProperty removeProperty = 
(ModelChange.RemoveProperty) change;
         newProps.remove(removeProperty.property());
+
       } else if (change instanceof ModelChange.SetProperty) {
         ModelChange.SetProperty setProperty = (ModelChange.SetProperty) change;
         newProps.put(setProperty.property(), setProperty.value());
+
+      } else if (change instanceof ModelChange.UpdateComment) {
+        ModelChange.UpdateComment updateComment = (ModelChange.UpdateComment) 
change;
+        newComment = updateComment.newComment();
+
+      } else {
+        throw new IllegalArgumentException("Unsupported model change: " + 
change);
       }
     }
     TestModel updatedModel =
@@ -1060,7 +1069,7 @@ public class TestCatalogOperations
         newUri = updateUriChange.newUri();
 
       } else {
-        throw new IllegalArgumentException("Unsupported model change: " + 
change);
+        throw new IllegalArgumentException("Unsupported model version change: 
" + change);
       }
     }
 
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 5f4ac8c3cb..b66601aae9 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
@@ -202,7 +202,7 @@ public class TestModelMetaService extends TestJDBCBackend {
   }
 
   @Test
-  void testAlterModel() throws IOException {
+  void testInsertAndRenameModel() throws IOException {
     createParentEntities(METALAKE_NAME, CATALOG_NAME, SCHEMA_NAME, auditInfo);
     Map<String, String> properties = ImmutableMap.of("k1", "v1");
     String newName = "new_model_name";
@@ -228,6 +228,93 @@ public class TestModelMetaService extends TestJDBCBackend {
             .withLatestVersion(modelEntity.latestVersion())
             .withAuditInfo(modelEntity.auditInfo())
             .withComment(modelEntity.comment())
+            .withProperties(modelEntity.properties())
+            .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));
+  }
+
+  @Test
+  void testInsertAndUpdateModelComment() throws IOException {
+    createParentEntities(METALAKE_NAME, CATALOG_NAME, SCHEMA_NAME, auditInfo);
+    Map<String, String> properties = ImmutableMap.of("k1", "v1");
+    String newComment = "new_model_comment";
+
+    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(newComment)
+            .withNamespace(modelEntity.namespace())
+            .withLatestVersion(modelEntity.latestVersion())
+            .withAuditInfo(modelEntity.auditInfo())
+            .withComment(modelEntity.comment())
+            .withProperties(modelEntity.properties())
+            .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));
+  }
+
+  @Test
+  void testInsertAndUpdateModelProperties() throws IOException {
+    createParentEntities(METALAKE_NAME, CATALOG_NAME, SCHEMA_NAME, auditInfo);
+    Map<String, String> properties = ImmutableMap.of("k1", "v1", "k2", "v2");
+    Map<String, String> newProps = ImmutableMap.of("k1", "v1", "k3", "v3");
+
+    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(modelEntity.comment())
+            .withNamespace(modelEntity.namespace())
+            .withLatestVersion(modelEntity.latestVersion())
+            .withAuditInfo(modelEntity.auditInfo())
+            .withComment(modelEntity.comment())
+            .withProperties(newProps)
             .build();
 
     Function<ModelEntity, ModelEntity> renameUpdater = oldModel -> 
updatedModel;
diff --git a/docs/manage-model-metadata-using-gravitino.md 
b/docs/manage-model-metadata-using-gravitino.md
index d0b8143cb7..46893fa443 100644
--- a/docs/manage-model-metadata-using-gravitino.md
+++ b/docs/manage-model-metadata-using-gravitino.md
@@ -304,7 +304,7 @@ model: Model = 
catalog.as_model_catalog().get_model(ident=NameIdentifier.of("mod
 
 ### Alter a model
 
-You can modify a model's metadata (e.g., rename or modify properties) by 
+You can modify a model's metadata (e.g. rename, update comment, or modify 
properties) by 
 sending a `PUT` request to the 
`/api/metalakes/{metalake_name}/catalogs/{catalog_name}/schemas/
 {schema_name}/models/{model_name}` endpoint or using the Gravitino Java/Python 
client. The following is an example of modifying a model:
 
@@ -315,6 +315,10 @@ sending a `PUT` request to the 
`/api/metalakes/{metalake_name}/catalogs/{catalog
 cat <<EOF >model.json
 {
   "updates": [
+    {
+      "@type": "updateComment",
+      "newComment": "Updated model comment"
+    },
     {
       "@type": "rename",
       "newName": "new_name"
@@ -356,6 +360,7 @@ curl -X PUT \
  // Define modifications
  ModelChange[] changes = {
      ModelChange.rename("example_model_renamed"),
+     ModelChange.updatComment("new comment"),
      ModelChange.setProperty("k2", "v2"),
      ModelChange.removeProperty("k1")
  };
@@ -379,6 +384,7 @@ catalog = 
client.load_catalog(name="mycatalog").as_model_catalog()
 # Define modifications
 changes = (
     ModelChange.rename("renamed"),
+    ModelChange.update_comment("new comment"),
     ModelChange.set_property("k2", "v2"),
     ModelChange.remove_property("k1"),
 )
@@ -396,11 +402,12 @@ updated_model = model_catalog.alter_model(
 The following operations are supported for altering a model:
 
 
-| Operation           | JSON Example                                           
    | Java Method                               | Python Method                 
             |
- 
|---------------------|------------------------------------------------------------|-------------------------------------------|--------------------------------------------|
-| **Rename model**    | `{"@type":"rename","newName":"new_name"}`              
    | `ModelChange.rename("new_name")`          | 
`ModelChange.rename("new_name")`           |
-| **Set property**    | 
`{"@type":"setProperty","property":"key","value":"value"}` | 
`ModelChange.setProperty("key", "value")` | `ModelChange.set_property("key", 
"value")` |
-| **Remove property** | `{"@type":"removeProperty","property":"key"}`          
    | `ModelChange.removeProperty("key")`       | 
`ModelChange.remove_property("key")`       |
+| Operation           | JSON Example                                           
    | Java Method                                | Python Method                
               |
+ 
|---------------------|------------------------------------------------------------|--------------------------------------------|---------------------------------------------|
+| **Rename model**    | `{"@type":"rename","newName":"new_name"}`              
    | `ModelChange.rename("new_name")`           | 
`ModelChange.rename("new_name")`            |
+| **Update comment**  | `{"@type":"updateComment","newComment":"new comment"}` 
    | `ModelChange.updateComment("new comment")` | 
`ModelChange.update_comment("new comment")` |
+| **Set property**    | 
`{"@type":"setProperty","property":"key","value":"value"}` | 
`ModelChange.setProperty("key", "value")`  | `ModelChange.set_property("key", 
"value")`  |
+| **Remove property** | `{"@type":"removeProperty","property":"key"}`          
    | `ModelChange.removeProperty("key")`        | 
`ModelChange.remove_property("key")`        |
 
 :::note
 - Multiple modifications can be applied in a single request.


Reply via email to