This is an automated email from the ASF dual-hosted git repository.

jshao pushed a commit to branch branch-0.9
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-0.9 by this push:
     new c1c3c06fe0 [#6815] feat(server): Support update comment for model 
version (#6987)
c1c3c06fe0 is described below

commit c1c3c06fe0408d8c5b4beab0a616b29484b28518
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Apr 17 16:20:52 2025 +0800

    [#6815] feat(server): Support update comment for model version (#6987)
    
    ### What changes were proposed in this pull request?
    
    Support update comment for model version
    - [x] PR1: Add ModelVersionChange API interface, Implement the update
    comment logic in model catalog and JDBC backend logic, update related
    event.
    - [X] PR2: Add REST endpoint to support model version change, add Java
    client and Python client for model version comment update.
    
    ### Why are the changes needed?
    
    Fix: #6815
    
    ### Does this PR introduce _any_ user-facing change?
    
    Add support for updating comments on model versions. Users can now
    update a model version's comment via the REST API.
    
    ### How was this patch tested?
    
    local test.
    original model version comment.
    <img width="1055" alt="image"
    
src="https://github.com/user-attachments/assets/c74c4dd1-f785-4c74-ae17-d666b06950f0";
    />
    
    `bin/gcli.sh model update -m demo_metalake --name
    model_catalog.schema.model2 --version 0 --comment 'new comment'`
    <img width="1057" alt="image"
    
src="https://github.com/user-attachments/assets/a866371e-df0b-4999-adb6-8543eaff73a3";
    />
    
    `bin/gcli.sh model update -m demo_metalake --name
    model_catalog.schema.model2 --alias test --comment 'new comment by
    alias'`
    
    <img width="1070" alt="image"
    
src="https://github.com/user-attachments/assets/2f838272-21a1-45b7-944d-014e45c7a511";
    />
    
    Co-authored-by: Lord of Abyss 
<[email protected]>
---
 .../apache/gravitino/model/ModelVersionChange.java |   4 +-
 .../catalog/model/ModelCatalogOperations.java      |   1 +
 .../integration/test/ModelCatalogOperationsIT.java |  65 +++++++++
 .../org/apache/gravitino/cli/ErrorMessages.java    |   2 +
 .../org/apache/gravitino/cli/GravitinoOptions.java |   4 +-
 .../apache/gravitino/cli/ModelCommandHandler.java  |  28 ++++
 .../apache/gravitino/cli/SimpleCommandHandler.java |   2 +-
 .../apache/gravitino/cli/TestableCommandLine.java  |  14 ++
 .../cli/commands/UpdateModelVersionComment.java    | 113 ++++++++++++++++
 .../apache/gravitino/cli/TestModelCommands.java    |  91 +++++++++++++
 .../apache/gravitino/cli/TestSimpleCommands.java   |   2 +-
 .../org/apache/gravitino/client/DTOConverters.java |  19 +++
 .../gravitino/client/GenericModelCatalog.java      |  49 ++++++-
 .../gravitino/client/TestGenericModelCatalog.java  | 150 +++++++++++++++++++++
 .../gravitino/api/model_version_change.py          |  78 +++++++++++
 .../gravitino/client/generic_model_catalog.py      |  98 ++++++++++++++
 .../dto/requests/model_version_update_request.py   |  63 +++++++++
 .../dto/requests/model_version_updates_request.py  |  50 +++++++
 .../tests/integration/test_model_catalog.py        |  48 +++++++
 .../tests/unittests/test_model_catalog_api.py      |  39 ++++++
 .../dto/requests/ModelVersionUpdateRequest.java    |  71 ++++++++++
 .../dto/requests/ModelVersionUpdatesRequest.java   |  46 +++++++
 .../catalog/TestModelOperationDispatcher.java      |   5 +-
 .../gravitino/connector/TestCatalogOperations.java |   1 +
 .../gravitino/server/web/rest/ModelOperations.java |  94 +++++++++++++
 .../server/web/rest/TestModelOperations.java       |  82 +++++++++++
 26 files changed, 1209 insertions(+), 10 deletions(-)

diff --git 
a/api/src/main/java/org/apache/gravitino/model/ModelVersionChange.java 
b/api/src/main/java/org/apache/gravitino/model/ModelVersionChange.java
index 281d1dcbb7..2b18bccb26 100644
--- a/api/src/main/java/org/apache/gravitino/model/ModelVersionChange.java
+++ b/api/src/main/java/org/apache/gravitino/model/ModelVersionChange.java
@@ -34,13 +34,13 @@ public interface ModelVersionChange {
    * Create a ModelVersionChange for updating the comment of a model version.
    *
    * @param newComment new comment to be set for the model version
-   * @return a new ModelVersionChange instance for updating the comment of a 
model version
+   * @return A new ModelVersionChange instance for updating the comment of a 
model version
    */
   static ModelVersionChange updateComment(String newComment) {
     return new ModelVersionChange.UpdateComment(newComment);
   }
 
-  /** A ModelVersionChange to update the modelve version comment. */
+  /** A ModelVersionChange to update the model version comment. */
   final class UpdateComment implements ModelVersionChange {
 
     private final String newComment;
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 21d8612b83..bba059308b 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
@@ -415,6 +415,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     for (ModelVersionChange change : changes) {
       if (change instanceof ModelVersionChange.UpdateComment) {
         entityComment = ((ModelVersionChange.UpdateComment) 
change).newComment();
+
       } else {
         throw new IllegalArgumentException(
             "Unsupported model version change: " + 
change.getClass().getSimpleName());
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 0cabc1495c..1b582672fb 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
@@ -40,6 +40,7 @@ import org.apache.gravitino.integration.test.util.BaseIT;
 import org.apache.gravitino.model.Model;
 import org.apache.gravitino.model.ModelChange;
 import org.apache.gravitino.model.ModelVersion;
+import org.apache.gravitino.model.ModelVersionChange;
 import org.apache.gravitino.utils.RandomNameUtils;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
@@ -329,6 +330,70 @@ public class ModelCatalogOperationsIT extends BaseIT {
     Assertions.assertEquals(0, modelVersionsAfterDeleteAll.length);
   }
 
+  @Test
+  void testLinkAndUpdateModelVersionCommentViaVersion() {
+    String modelName = RandomNameUtils.genRandomName("model1");
+    Map<String, String> properties = ImmutableMap.of("key1", "val1", "key2", 
"val2");
+    NameIdentifier modelIdent = NameIdentifier.of(schemaName, modelName);
+    String versionNewComment = "new comment";
+
+    gravitinoCatalog.asModelCatalog().registerModel(modelIdent, null, null);
+
+    gravitinoCatalog
+        .asModelCatalog()
+        .linkModelVersion(modelIdent, "uri", new String[] {"alias1"}, 
"comment", properties);
+
+    ModelVersion modelVersion = 
gravitinoCatalog.asModelCatalog().getModelVersion(modelIdent, 0);
+
+    Assertions.assertEquals(0, modelVersion.version());
+    Assertions.assertEquals("uri", modelVersion.uri());
+    Assertions.assertArrayEquals(new String[] {"alias1"}, 
modelVersion.aliases());
+    Assertions.assertEquals("comment", modelVersion.comment());
+    Assertions.assertEquals(properties, modelVersion.properties());
+
+    ModelVersionChange updateComment = 
ModelVersionChange.updateComment(versionNewComment);
+    ModelVersion updatedModelVersion =
+        gravitinoCatalog.asModelCatalog().alterModelVersion(modelIdent, 0, 
updateComment);
+
+    Assertions.assertEquals(modelVersion.version(), 
updatedModelVersion.version());
+    Assertions.assertEquals(modelVersion.uri(), updatedModelVersion.uri());
+    Assertions.assertArrayEquals(modelVersion.aliases(), 
updatedModelVersion.aliases());
+    Assertions.assertEquals(versionNewComment, updatedModelVersion.comment());
+    Assertions.assertEquals(modelVersion.properties(), 
updatedModelVersion.properties());
+  }
+
+  @Test
+  void testLinkAndUpdateModelVersionCommentViaAlias() {
+    String modelName = RandomNameUtils.genRandomName("model1");
+    Map<String, String> properties = ImmutableMap.of("key1", "val1", "key2", 
"val2");
+    NameIdentifier modelIdent = NameIdentifier.of(schemaName, modelName);
+    String aliasNewComment = "new comment";
+
+    gravitinoCatalog.asModelCatalog().registerModel(modelIdent, null, null);
+
+    gravitinoCatalog
+        .asModelCatalog()
+        .linkModelVersion(modelIdent, "uri", new String[] {"alias1"}, 
"comment", properties);
+
+    ModelVersion modelVersion =
+        gravitinoCatalog.asModelCatalog().getModelVersion(modelIdent, 
"alias1");
+    Assertions.assertEquals(0, modelVersion.version());
+    Assertions.assertEquals("uri", modelVersion.uri());
+    Assertions.assertArrayEquals(new String[] {"alias1"}, 
modelVersion.aliases());
+    Assertions.assertEquals("comment", modelVersion.comment());
+    Assertions.assertEquals(properties, modelVersion.properties());
+
+    ModelVersionChange updateComment = 
ModelVersionChange.updateComment(aliasNewComment);
+    ModelVersion updatedModelVersion =
+        gravitinoCatalog.asModelCatalog().alterModelVersion(modelIdent, 
"alias1", updateComment);
+
+    Assertions.assertEquals(modelVersion.version(), 
updatedModelVersion.version());
+    Assertions.assertEquals(modelVersion.uri(), updatedModelVersion.uri());
+    Assertions.assertArrayEquals(modelVersion.aliases(), 
updatedModelVersion.aliases());
+    Assertions.assertEquals(aliasNewComment, updatedModelVersion.comment());
+    Assertions.assertEquals(modelVersion.properties(), 
updatedModelVersion.properties());
+  }
+
   @Test
   public void testRegisterAndUpdateModel() {
     String comment = "comment";
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java
index 0ba98c0c77..cc36dd66b7 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java
@@ -66,6 +66,8 @@ public class ErrorMessages {
   public static final String MISSING_USER = "Missing --user option.";
   public static final String MISSING_VALUE = "Missing --value option.";
 
+  public static final String MULTIPLE_ALIASES_COMMAND_ERROR =
+      "This command only supports one --alias option.";
   public static final String MULTIPLE_ROLE_COMMAND_ERROR =
       "This command only supports one --role option.";
   public static final String MULTIPLE_TAG_COMMAND_ERROR =
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java
index 9d304c8775..52c15ad80b 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java
@@ -26,6 +26,7 @@ import org.apache.commons.cli.Options;
 public class GravitinoOptions {
   public static final String AUDIT = "audit";
   public static final String AUTO = "auto";
+  public static final String CLIENT = "client";
   public static final String COLUMNFILE = "columnfile";
   public static final String COMMENT = "comment";
   public static final String DATATYPE = "datatype";
@@ -76,7 +77,7 @@ public class GravitinoOptions {
 
     // Add options using helper method to avoid repetition
     options.addOption(createSimpleOption("h", HELP, "command help 
information"));
-    options.addOption(createSimpleOption("v", VERSION, "Gravitino client 
version"));
+    options.addOption(createSimpleOption(null, CLIENT, "Gravitino client 
version"));
     options.addOption(createSimpleOption("s", SERVER, "Gravitino server 
version"));
     options.addOption(createArgOption("u", URL, "Gravitino URL (default: 
http://localhost:8090)"));
     options.addOption(createArgOption("n", NAME, "full entity name (dot 
separated)"));
@@ -116,6 +117,7 @@ public class GravitinoOptions {
     // model options
     options.addOption(createArgOption(null, URI, "model version artifact"));
     options.addOption(createArgsOption(null, ALIAS, "model aliases"));
+    options.addOption(createArgOption(null, VERSION, "Gravitino client 
version"));
 
     // Options that support multiple values
     options.addOption(createArgsOption("p", PROPERTIES, "property name/value 
pairs"));
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 6bbe796dba..28cb743186 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
@@ -180,6 +180,26 @@ public class ModelCommandHandler extends CommandHandler {
           .validate()
           .handle();
     }
+
+    if (!line.hasOption(GravitinoOptions.URI)
+        && line.hasOption(GravitinoOptions.COMMENT)
+        && (line.hasOption(GravitinoOptions.ALIAS) || 
line.hasOption(GravitinoOptions.VERSION))) {
+      String comment = line.getOptionValue(GravitinoOptions.COMMENT);
+      Integer version =
+          line.hasOption(GravitinoOptions.VERSION)
+              ? Integer.parseInt(line.getOptionValue(GravitinoOptions.VERSION))
+              : null;
+      String alias =
+          line.hasOption(GravitinoOptions.ALIAS)
+              ? getOneAlias(line.getOptionValues(GravitinoOptions.ALIAS))
+              : null;
+
+      gravitinoCommandLine
+          .newUpdateModelVersionComment(
+              context, metalake, catalog, schema, model, version, alias, 
comment)
+          .validate()
+          .handle();
+    }
   }
 
   /** Handles the "LIST" command. */
@@ -204,4 +224,12 @@ public class ModelCommandHandler extends CommandHandler {
         .validate()
         .handle();
   }
+
+  private String getOneAlias(String[] aliases) {
+    if (aliases == null || aliases.length > 1) {
+      System.err.println(ErrorMessages.MULTIPLE_ALIASES_COMMAND_ERROR);
+      Main.exit(-1);
+    }
+    return aliases[0];
+  }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/SimpleCommandHandler.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/SimpleCommandHandler.java
index 113be4f20b..b4baf4c223 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/SimpleCommandHandler.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/SimpleCommandHandler.java
@@ -44,7 +44,7 @@ public class SimpleCommandHandler extends CommandHandler {
   /** Handles the command execution logic based on the provided command. */
   @Override
   protected void handle() {
-    if (line.hasOption(GravitinoOptions.VERSION)) {
+    if (line.hasOption(GravitinoOptions.CLIENT)) {
       gravitinoCommandLine.newClientVersion(context).validate().handle();
     } else if (line.hasOption(GravitinoOptions.SERVER)) {
       gravitinoCommandLine.newServerVersion(context).validate().handle();
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 9bf17aacd5..86acd5b0de 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
@@ -135,6 +135,7 @@ 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.UpdateModelName;
+import org.apache.gravitino.cli.commands.UpdateModelVersionComment;
 import org.apache.gravitino.cli.commands.UpdateTableComment;
 import org.apache.gravitino.cli.commands.UpdateTableName;
 import org.apache.gravitino.cli.commands.UpdateTagComment;
@@ -885,6 +886,19 @@ public class TestableCommandLine {
     return new UpdateModelName(context, metalake, catalog, schema, model, 
rename);
   }
 
+  protected UpdateModelVersionComment newUpdateModelVersionComment(
+      CommandContext context,
+      String metalake,
+      String catalog,
+      String schema,
+      String model,
+      Integer version,
+      String alias,
+      String comment) {
+    return new UpdateModelVersionComment(
+        context, metalake, catalog, schema, model, version, alias, comment);
+  }
+
   protected SetModelProperty newSetModelProperty(
       CommandContext context,
       String metalake,
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateModelVersionComment.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateModelVersionComment.java
new file mode 100644
index 0000000000..bb280b189e
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateModelVersionComment.java
@@ -0,0 +1,113 @@
+/*
+ * 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.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.model.ModelVersionChange;
+
+/** Update the comment of a model version. */
+public class UpdateModelVersionComment extends Command {
+  protected final String metalake;
+  protected final String catalog;
+  protected final String schema;
+  protected final String model;
+  protected final Integer version;
+  private final String alias;
+  private final String comment;
+
+  /**
+   * Constructs a new {@link UpdateModelVersionComment} instance.
+   *
+   * @param context The command context.
+   * @param metalake The name of the metalake.
+   * @param catalog The name of the catalog.
+   * @param schema The name of the schema.
+   * @param model The name of the model.
+   * @param version The version of the model.
+   * @param alias The alias of the model version.
+   * @param comment The new comment for the model version.
+   */
+  public UpdateModelVersionComment(
+      CommandContext context,
+      String metalake,
+      String catalog,
+      String schema,
+      String model,
+      Integer version,
+      String alias,
+      String comment) {
+    super(context);
+    this.metalake = metalake;
+    this.catalog = catalog;
+    this.schema = schema;
+    this.model = model;
+    this.version = version;
+    this.alias = alias;
+    this.comment = comment;
+  }
+
+  /** Update the comment of a model version. */
+  @Override
+  public void handle() {
+    try {
+      NameIdentifier modelIdent = NameIdentifier.of(schema, model);
+      GravitinoClient client = buildClient(metalake);
+      ModelVersionChange change = ModelVersionChange.updateComment(comment);
+      if (alias != null) {
+        
client.loadCatalog(catalog).asModelCatalog().alterModelVersion(modelIdent, 
alias, change);
+      } else {
+        
client.loadCatalog(catalog).asModelCatalog().alterModelVersion(modelIdent, 
version, change);
+      }
+    } catch (NoSuchMetalakeException err) {
+      exitWithError(ErrorMessages.UNKNOWN_METALAKE);
+    } catch (NoSuchCatalogException err) {
+      exitWithError(ErrorMessages.UNKNOWN_CATALOG);
+    } catch (NoSuchSchemaException err) {
+      exitWithError(ErrorMessages.UNKNOWN_SCHEMA);
+    } catch (NoSuchTableException err) {
+      exitWithError(ErrorMessages.UNKNOWN_TABLE);
+    } catch (Exception exp) {
+      exitWithError(exp.getMessage());
+    }
+
+    if (alias != null) {
+      printInformation(model + " version " + alias + " comment changed.");
+    } else {
+      printInformation(model + " version " + version + " comment changed.");
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Command validate() {
+    if (alias != null && version != null) {
+      exitWithError("Cannot specify both alias and version");
+    }
+
+    return this;
+  }
+}
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 9f03769093..35b9f62698 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
@@ -49,7 +49,9 @@ import org.apache.gravitino.cli.commands.RegisterModel;
 import org.apache.gravitino.cli.commands.RemoveModelProperty;
 import org.apache.gravitino.cli.commands.SetModelProperty;
 import org.apache.gravitino.cli.commands.UpdateModelName;
+import org.apache.gravitino.cli.commands.UpdateModelVersionComment;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.testcontainers.shaded.com.google.common.base.Joiner;
@@ -661,4 +663,93 @@ public class TestModelCommands {
     commandLine.handleCommandLine();
     verify(mockRemoveProperty).handle();
   }
+
+  @Test
+  void testUpdateModelVersionCommentCommand() {
+    UpdateModelVersionComment mockUpdate = 
mock(UpdateModelVersionComment.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.VERSION)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.VERSION)).thenReturn("1");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment");
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.MODEL, 
CommandActions.UPDATE));
+
+    doReturn(mockUpdate)
+        .when(commandLine)
+        .newUpdateModelVersionComment(
+            any(CommandContext.class),
+            eq("metalake_demo"),
+            eq("catalog"),
+            eq("schema"),
+            eq("model"),
+            any(),
+            any(),
+            eq("comment"));
+    doReturn(mockUpdate).when(mockUpdate).validate();
+    commandLine.handleCommandLine();
+    verify(mockUpdate).handle();
+  }
+
+  @Test
+  void testUpdateModelVersionCommentCommandByAlias() {
+    Main.useExit = false;
+    UpdateModelVersionComment mockUpdate = 
mock(UpdateModelVersionComment.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.ALIAS)).thenReturn(true);
+    when(mockCommandLine.getOptionValues(GravitinoOptions.ALIAS))
+        .thenReturn(new String[] {"aliasA"});
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment");
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.MODEL, 
CommandActions.UPDATE));
+
+    doReturn(mockUpdate)
+        .when(commandLine)
+        .newUpdateModelVersionComment(
+            any(CommandContext.class),
+            eq("metalake_demo"),
+            eq("catalog"),
+            eq("schema"),
+            eq("model"),
+            any(),
+            any(),
+            eq("comment"));
+    doReturn(mockUpdate).when(mockUpdate).validate();
+    commandLine.handleCommandLine();
+    verify(mockUpdate).handle();
+  }
+
+  @Test
+  void testUpdateModelVersionCommentCommandByAliasAndVersion() {
+    Main.useExit = false;
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.ALIAS)).thenReturn(true);
+    when(mockCommandLine.getOptionValues(GravitinoOptions.ALIAS))
+        .thenReturn(new String[] {"aliasA"});
+    when(mockCommandLine.hasOption(GravitinoOptions.VERSION)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.VERSION)).thenReturn("1");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment");
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.MODEL, 
CommandActions.UPDATE));
+
+    Assertions.assertThrows(RuntimeException.class, 
commandLine::handleCommandLine);
+  }
 }
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestSimpleCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestSimpleCommands.java
index 8024189112..9342b98cc2 100644
--- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestSimpleCommands.java
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestSimpleCommands.java
@@ -60,7 +60,7 @@ public class TestSimpleCommands {
   @Test
   void testClientVersion() {
     ClientVersion mockClientVersion = mock(ClientVersion.class);
-    when(mockCommandLine.hasOption(GravitinoOptions.VERSION)).thenReturn(true);
+    when(mockCommandLine.hasOption(GravitinoOptions.CLIENT)).thenReturn(true);
     GravitinoCommandLine commandLine =
         spy(new GravitinoCommandLine(mockCommandLine, mockOptions, null, 
null));
 
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 e76b1a0b1d..ceffd38a94 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
@@ -39,6 +39,7 @@ import org.apache.gravitino.dto.requests.CatalogUpdateRequest;
 import org.apache.gravitino.dto.requests.FilesetUpdateRequest;
 import org.apache.gravitino.dto.requests.MetalakeUpdateRequest;
 import org.apache.gravitino.dto.requests.ModelUpdateRequest;
+import org.apache.gravitino.dto.requests.ModelVersionUpdateRequest;
 import org.apache.gravitino.dto.requests.SchemaUpdateRequest;
 import org.apache.gravitino.dto.requests.TableUpdateRequest;
 import org.apache.gravitino.dto.requests.TagUpdateRequest;
@@ -46,6 +47,7 @@ import org.apache.gravitino.dto.requests.TopicUpdateRequest;
 import org.apache.gravitino.file.FilesetChange;
 import org.apache.gravitino.messaging.TopicChange;
 import org.apache.gravitino.model.ModelChange;
+import org.apache.gravitino.model.ModelVersionChange;
 import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.TableChange;
 import org.apache.gravitino.rel.expressions.Expression;
@@ -377,4 +379,21 @@ class DTOConverters {
           "Unknown model change type: " + change.getClass().getSimpleName());
     }
   }
+
+  /**
+   * Converts a {@link ModelVersionChange} to a {@link 
ModelVersionUpdateRequest}.
+   *
+   * @param change The change to convert.
+   * @return The converted request.
+   */
+  static ModelVersionUpdateRequest 
toModelVersionUpdateRequest(ModelVersionChange change) {
+    if (change instanceof ModelVersionChange.UpdateComment) {
+      return new ModelVersionUpdateRequest.UpdateModelVersionComment(
+          ((ModelVersionChange.UpdateComment) change).newComment());
+
+    } else {
+      throw new IllegalArgumentException(
+          "Unknown model version change type: " + 
change.getClass().getSimpleName());
+    }
+  }
 }
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 64d6a91b00..520352d9af 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
@@ -35,6 +35,8 @@ import org.apache.gravitino.dto.requests.ModelRegisterRequest;
 import org.apache.gravitino.dto.requests.ModelUpdateRequest;
 import org.apache.gravitino.dto.requests.ModelUpdatesRequest;
 import org.apache.gravitino.dto.requests.ModelVersionLinkRequest;
+import org.apache.gravitino.dto.requests.ModelVersionUpdateRequest;
+import org.apache.gravitino.dto.requests.ModelVersionUpdatesRequest;
 import org.apache.gravitino.dto.responses.BaseResponse;
 import org.apache.gravitino.dto.responses.DropResponse;
 import org.apache.gravitino.dto.responses.EntityListResponse;
@@ -286,16 +288,55 @@ class GenericModelCatalog extends BaseSchemaCatalog 
implements ModelCatalog {
   public ModelVersion alterModelVersion(
       NameIdentifier ident, int version, ModelVersionChange... changes)
       throws NoSuchModelException, NoSuchModelVersionException, 
IllegalArgumentException {
-    // TODO implement
-    return null;
+    checkModelNameIdentifier(ident);
+
+    List<ModelVersionUpdateRequest> updateRequests =
+        Arrays.stream(changes)
+            .map(DTOConverters::toModelVersionUpdateRequest)
+            .collect(Collectors.toList());
+
+    ModelVersionUpdatesRequest req = new 
ModelVersionUpdatesRequest(updateRequests);
+    req.validate();
+
+    NameIdentifier modelFullIdent = modelFullNameIdentifier(ident);
+    ModelVersionResponse resp =
+        restClient.put(
+            formatModelVersionRequestPath(modelFullIdent) + "/versions/" + 
version,
+            req,
+            ModelVersionResponse.class,
+            Collections.emptyMap(),
+            ErrorHandlers.modelErrorHandler());
+
+    resp.validate();
+    return new GenericModelVersion(resp.getModelVersion());
   }
 
   @Override
   public ModelVersion alterModelVersion(
       NameIdentifier ident, String alias, ModelVersionChange... changes)
       throws NoSuchModelException, IllegalArgumentException {
-    // TODO implement
-    return null;
+    checkModelNameIdentifier(ident);
+    Preconditions.checkArgument(StringUtils.isNotBlank(alias), "Model alias 
must be non-empty");
+
+    List<ModelVersionUpdateRequest> updateRequests =
+        Arrays.stream(changes)
+            .map(DTOConverters::toModelVersionUpdateRequest)
+            .collect(Collectors.toList());
+
+    ModelVersionUpdatesRequest req = new 
ModelVersionUpdatesRequest(updateRequests);
+    req.validate();
+
+    NameIdentifier modelFullIdent = modelFullNameIdentifier(ident);
+    ModelVersionResponse resp =
+        restClient.put(
+            formatModelVersionRequestPath(modelFullIdent) + "/aliases/" + 
alias,
+            req,
+            ModelVersionResponse.class,
+            Collections.emptyMap(),
+            ErrorHandlers.modelErrorHandler());
+
+    resp.validate();
+    return new GenericModelVersion(resp.getModelVersion());
   }
 
   /** @return A new builder instance for {@link GenericModelCatalog}. */
diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestGenericModelCatalog.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestGenericModelCatalog.java
index 583ff3a257..1fdea36228 100644
--- 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestGenericModelCatalog.java
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestGenericModelCatalog.java
@@ -36,6 +36,8 @@ import org.apache.gravitino.dto.requests.ModelRegisterRequest;
 import org.apache.gravitino.dto.requests.ModelUpdateRequest;
 import org.apache.gravitino.dto.requests.ModelUpdatesRequest;
 import org.apache.gravitino.dto.requests.ModelVersionLinkRequest;
+import org.apache.gravitino.dto.requests.ModelVersionUpdateRequest;
+import org.apache.gravitino.dto.requests.ModelVersionUpdatesRequest;
 import org.apache.gravitino.dto.responses.BaseResponse;
 import org.apache.gravitino.dto.responses.CatalogResponse;
 import org.apache.gravitino.dto.responses.DropResponse;
@@ -570,6 +572,154 @@ public class TestGenericModelCatalog extends TestBase {
         "internal error");
   }
 
+  @Test
+  void testUpdateModelVersionComment() throws JsonProcessingException {
+    String newComment = "new comment";
+    String uri = "uri";
+    int version = 0;
+    String[] aliases = new String[] {"alias1", "alias2"};
+
+    NameIdentifier modelId = NameIdentifier.of("schema1", "model1");
+    String modelVersionPath =
+        withSlash(
+            GenericModelCatalog.formatModelVersionRequestPath(
+                    NameIdentifier.of(METALAKE_NAME, CATALOG_NAME, "schema1", 
"model1"))
+                + "/versions/0");
+
+    ModelVersionDTO mockModelVersion =
+        mockModelVersion(version, uri, aliases, newComment, 
Collections.emptyMap());
+    ModelVersionResponse resp = new ModelVersionResponse(mockModelVersion);
+    ModelVersionUpdateRequest.UpdateModelVersionComment updateCommentReq =
+        new ModelVersionUpdateRequest.UpdateModelVersionComment(newComment);
+
+    buildMockResource(
+        Method.PUT,
+        modelVersionPath,
+        new ModelVersionUpdatesRequest(ImmutableList.of(updateCommentReq)),
+        resp,
+        HttpStatus.SC_OK);
+
+    ModelVersion updatedModelVersion =
+        catalog
+            .asModelCatalog()
+            .alterModelVersion(modelId, version, 
updateCommentReq.modelVersionChange());
+    compareModelVersion(mockModelVersion, updatedModelVersion);
+
+    Assertions.assertEquals(uri, updatedModelVersion.uri());
+    Assertions.assertEquals(newComment, updatedModelVersion.comment());
+    Assertions.assertEquals(Collections.emptyMap(), 
updatedModelVersion.properties());
+    Assertions.assertEquals(version, updatedModelVersion.version());
+    Assertions.assertArrayEquals(aliases, updatedModelVersion.aliases());
+
+    // Test NoSuchModelException
+    ErrorResponse notFoundResp =
+        ErrorResponse.notFound(
+            NoSuchModelVersionException.class.getSimpleName(), "model version 
not found");
+    buildMockResource(
+        Method.PUT,
+        modelVersionPath,
+        new ModelVersionUpdatesRequest(ImmutableList.of(updateCommentReq)),
+        notFoundResp,
+        HttpStatus.SC_NOT_FOUND);
+    Assertions.assertThrows(
+        NoSuchModelVersionException.class,
+        () ->
+            catalog
+                .asModelCatalog()
+                .alterModelVersion(modelId, version, 
updateCommentReq.modelVersionChange()),
+        "model not found");
+
+    // Test RuntimeException
+    ErrorResponse internalErrorResp = ErrorResponse.internalError("internal 
error");
+    buildMockResource(
+        Method.PUT,
+        modelVersionPath,
+        new ModelVersionUpdatesRequest(ImmutableList.of(updateCommentReq)),
+        internalErrorResp,
+        HttpStatus.SC_INTERNAL_SERVER_ERROR);
+    Assertions.assertThrows(
+        RuntimeException.class,
+        () ->
+            catalog
+                .asModelCatalog()
+                .alterModelVersion(modelId, version, 
updateCommentReq.modelVersionChange()),
+        "internal error");
+  }
+
+  @Test
+  void testUpdateModelVersionCommentByAlias() throws JsonProcessingException {
+    String newComment = "new comment";
+    String uri = "uri";
+    int version = 0;
+    String[] aliases = new String[] {"alias1", "alias2"};
+
+    NameIdentifier modelId = NameIdentifier.of("schema1", "model1");
+    String modelVersionPath =
+        withSlash(
+            GenericModelCatalog.formatModelVersionRequestPath(
+                    NameIdentifier.of(METALAKE_NAME, CATALOG_NAME, "schema1", 
"model1"))
+                + "/aliases/alias1");
+
+    ModelVersionDTO mockModelVersion =
+        mockModelVersion(version, uri, aliases, newComment, 
Collections.emptyMap());
+    ModelVersionResponse resp = new ModelVersionResponse(mockModelVersion);
+    ModelVersionUpdateRequest.UpdateModelVersionComment updateCommentReq =
+        new ModelVersionUpdateRequest.UpdateModelVersionComment(newComment);
+
+    buildMockResource(
+        Method.PUT,
+        modelVersionPath,
+        new ModelVersionUpdatesRequest(ImmutableList.of(updateCommentReq)),
+        resp,
+        HttpStatus.SC_OK);
+
+    ModelVersion updatedModelVersion =
+        catalog
+            .asModelCatalog()
+            .alterModelVersion(modelId, aliases[0], 
updateCommentReq.modelVersionChange());
+    compareModelVersion(mockModelVersion, updatedModelVersion);
+
+    Assertions.assertEquals(uri, updatedModelVersion.uri());
+    Assertions.assertEquals(newComment, updatedModelVersion.comment());
+    Assertions.assertEquals(Collections.emptyMap(), 
updatedModelVersion.properties());
+    Assertions.assertEquals(version, updatedModelVersion.version());
+    Assertions.assertArrayEquals(aliases, updatedModelVersion.aliases());
+
+    // Test NoSuchModelException
+    ErrorResponse notFoundResp =
+        ErrorResponse.notFound(
+            NoSuchModelVersionException.class.getSimpleName(), "model version 
not found");
+    buildMockResource(
+        Method.PUT,
+        modelVersionPath,
+        new ModelVersionUpdatesRequest(ImmutableList.of(updateCommentReq)),
+        notFoundResp,
+        HttpStatus.SC_NOT_FOUND);
+    Assertions.assertThrows(
+        NoSuchModelVersionException.class,
+        () ->
+            catalog
+                .asModelCatalog()
+                .alterModelVersion(modelId, aliases[0], 
updateCommentReq.modelVersionChange()),
+        "model not found");
+
+    // Test RuntimeException
+    ErrorResponse internalErrorResp = ErrorResponse.internalError("internal 
error");
+    buildMockResource(
+        Method.PUT,
+        modelVersionPath,
+        new ModelVersionUpdatesRequest(ImmutableList.of(updateCommentReq)),
+        internalErrorResp,
+        HttpStatus.SC_INTERNAL_SERVER_ERROR);
+    Assertions.assertThrows(
+        RuntimeException.class,
+        () ->
+            catalog
+                .asModelCatalog()
+                .alterModelVersion(modelId, aliases[0], 
updateCommentReq.modelVersionChange()),
+        "internal error");
+  }
+
   private ModelDTO mockModelDTO(
       String modelName, int latestVersion, String comment, Map<String, String> 
properties) {
     return ModelDTO.builder()
diff --git a/clients/client-python/gravitino/api/model_version_change.py 
b/clients/client-python/gravitino/api/model_version_change.py
new file mode 100644
index 0000000000..0c555e983b
--- /dev/null
+++ b/clients/client-python/gravitino/api/model_version_change.py
@@ -0,0 +1,78 @@
+# 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.
+
+from abc import ABC
+
+
+class ModelVersionChange(ABC):
+    """
+    A model version change is a change to a model version. It can be used to 
update the comment
+    of a model version, set a property and value pair for a model version, or 
remove a property from a model.
+    """
+
+    @staticmethod
+    def update_comment(comment: str):
+        """Creates a new model version change to update the comment of the 
model version.
+        Args:
+            comment: The new comment of the model version.
+        Returns:
+            The model version change.
+        """
+        return ModelVersionChange.UpdateComment(comment)
+
+    class UpdateComment:
+        """A model version change to update the comment of the model 
version."""
+
+        def __init__(self, new_comment: str):
+            self._new_comment = new_comment
+
+        def new_comment(self) -> str:
+            """Retrieves the new comment of the model version.
+            Returns:
+                The new comment of the model version.
+            """
+            return self._new_comment
+
+        def __eq__(self, other):
+            """Compares this UpdateComment instance with another object for 
equality. Two instances are
+            considered equal if they designate the same new comment for the 
model version.
+            Args:
+                other: The object to compare with this instance.
+            Returns:
+                true if the given object represents an identical model version 
comment update operation;
+                false otherwise.
+            """
+            if not isinstance(other, ModelVersionChange.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 new comment for the model version.
+            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 new comment of the model version.
+            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 79cfcdb2e9..554b788e54 100644
--- a/clients/client-python/gravitino/client/generic_model_catalog.py
+++ b/clients/client-python/gravitino/client/generic_model_catalog.py
@@ -21,6 +21,7 @@ from gravitino.api.catalog import Catalog
 from gravitino.api.model.model import Model
 from gravitino.api.model.model_version import ModelVersion
 from gravitino.api.model_change import ModelChange
+from gravitino.api.model_version_change import ModelVersionChange
 from gravitino.client.base_schema_catalog import BaseSchemaCatalog
 from gravitino.client.generic_model import GenericModel
 from gravitino.client.generic_model_version import GenericModelVersion
@@ -29,6 +30,12 @@ from gravitino.dto.requests.model_register_request import 
ModelRegisterRequest
 from gravitino.dto.requests.model_update_request import ModelUpdateRequest
 from gravitino.dto.requests.model_updates_request import ModelUpdatesRequest
 from gravitino.dto.requests.model_version_link_request import 
ModelVersionLinkRequest
+from gravitino.dto.requests.model_version_update_request import (
+    ModelVersionUpdateRequest,
+)
+from gravitino.dto.requests.model_version_updates_request import (
+    ModelVersionUpdatesRequest,
+)
 from gravitino.dto.responses.base_response import BaseResponse
 from gravitino.dto.responses.drop_response import DropResponse
 from gravitino.dto.responses.entity_list_response import EntityListResponse
@@ -302,6 +309,85 @@ class GenericModelCatalog(BaseSchemaCatalog):
         model_response.validate()
         return GenericModel(model_response.model())
 
+    def alter_model_version(
+        self, model_ident: NameIdentifier, version: int, *changes: 
ModelVersionChange
+    ):
+        """
+        Alter the model version by applying the changes.
+        Args:
+            model_ident: The identifier of the model.
+            version: The version of the model version.
+            changes: The changes to apply to the model version.
+        Raises:
+            NoSuchModelVersionException: If the model version does not exist.
+            IllegalArgumentException: If the changes are invalid.
+        Returns:
+            The updated model version object.
+        """
+        self._check_model_ident(model_ident)
+
+        model_full_ident = self._model_full_identifier(model_ident)
+
+        update_requests = [
+            GenericModelCatalog.to_model_version_update_request(change)
+            for change in changes
+        ]
+
+        req = ModelVersionUpdatesRequest(update_requests)
+        req.validate()
+
+        resp = self.rest_client.put(
+            
f"{self._format_model_version_request_path(model_full_ident)}/versions/{version}",
+            req,
+            error_handler=MODEL_ERROR_HANDLER,
+        )
+
+        model_version_response = ModelVersionResponse.from_json(
+            resp.body, infer_missing=True
+        )
+        model_version_response.validate()
+        return GenericModelVersion(model_version_response.model_version())
+
+    def alter_model_version_by_alias(
+        self, model_ident: NameIdentifier, alias: str, *changes: 
ModelVersionChange
+    ):
+        """
+        Alter the model version by applying the changes.
+        Args:
+            model_ident: The identifier of the model.
+            alias: The alias of the model version.
+            changes: The changes to apply to the model version.
+        Raises:
+            NoSuchModelVersionException: If the model version does not exist.
+            IllegalArgumentException: If the changes are invalid.
+        Returns:
+            The updated model version object.
+        """
+        self._check_model_ident(model_ident)
+
+        model_full_ident = self._model_full_identifier(model_ident)
+
+        update_requests = [
+            GenericModelCatalog.to_model_version_update_request(change)
+            for change in changes
+        ]
+
+        req = ModelVersionUpdatesRequest(update_requests)
+        req.validate()
+
+        resp = self.rest_client.put(
+            
f"{self._format_model_version_request_path(model_full_ident)}/aliases/"
+            f"{encode_string(alias)}",
+            req,
+            error_handler=MODEL_ERROR_HANDLER,
+        )
+
+        model_version_response = ModelVersionResponse.from_json(
+            resp.body, infer_missing=True
+        )
+        model_version_response.validate()
+        return GenericModelVersion(model_version_response.model_version())
+
     def link_model_version(
         self,
         model_ident: NameIdentifier,
@@ -439,6 +525,18 @@ class GenericModelCatalog(BaseSchemaCatalog):
 
         raise ValueError(f"Unknown change type: {type(change).__name__}")
 
+    @staticmethod
+    def to_model_version_update_request(change: ModelVersionChange):
+        if isinstance(change, ModelVersionChange.UpdateComment):
+            return ModelVersionUpdateRequest.UpdateModelVersionComment(
+                change.new_comment()
+            )
+
+        if isinstance(change, ModelVersionChange.RemoveComment):
+            return ModelVersionUpdateRequest.RemoveModelVersionComment()
+
+        raise ValueError(f"Unknown change type: {type(change).__name__}")
+
     def _check_model_namespace(self, namespace: Namespace):
         """Check the validity of the model namespace.
 
diff --git 
a/clients/client-python/gravitino/dto/requests/model_version_update_request.py 
b/clients/client-python/gravitino/dto/requests/model_version_update_request.py
new file mode 100644
index 0000000000..a9a5b9c1c4
--- /dev/null
+++ 
b/clients/client-python/gravitino/dto/requests/model_version_update_request.py
@@ -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.
+
+
+from abc import abstractmethod
+from dataclasses import dataclass, field
+from typing import Optional
+
+from dataclasses_json import config
+
+from gravitino.api.model_version_change import ModelVersionChange
+from gravitino.rest.rest_message import RESTRequest
+
+
+@dataclass
+class ModelVersionUpdateRequestBase(RESTRequest):
+    """Base class for all model version update requests."""
+
+    _type: str = field(metadata=config(field_name="@type"))
+
+    def __init__(self, action_type: str):
+        self._type = action_type
+
+    @abstractmethod
+    def model_version_change(self) -> ModelVersionChange:
+        """Convert to model version change operation"""
+        pass
+
+
+class ModelVersionUpdateRequest:
+    """Namespace for all model version update request types."""
+
+    @dataclass
+    class UpdateModelVersionComment(ModelVersionUpdateRequestBase):
+        """Request to update model version 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_version_change(self):
+            return ModelVersionChange.update_comment(self._new_comment)
diff --git 
a/clients/client-python/gravitino/dto/requests/model_version_updates_request.py 
b/clients/client-python/gravitino/dto/requests/model_version_updates_request.py
new file mode 100644
index 0000000000..6695a6e106
--- /dev/null
+++ 
b/clients/client-python/gravitino/dto/requests/model_version_updates_request.py
@@ -0,0 +1,50 @@
+# 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.
+
+
+from dataclasses import dataclass, field
+from typing import List
+
+from dataclasses_json import config
+
+from gravitino.dto.requests.model_version_update_request import (
+    ModelVersionUpdateRequest,
+)
+from gravitino.rest.rest_message import RESTRequest
+
+
+@dataclass
+class ModelVersionUpdatesRequest(RESTRequest):
+    """Represents a collection of model Version update operations to be 
applied atomically."""
+
+    _updates: List[ModelVersionUpdateRequest] = field(
+        metadata=config(field_name="updates"), default_factory=list
+    )
+
+    def __init__(self, updates: List[ModelVersionUpdateRequest] = None):
+        self._updates = updates
+
+    def validate(self):
+        """Validates the update requests.
+        Raises:
+            ValueError: If the updates list is empty or contains invalid 
requests.
+        """
+        if not self._updates:
+            raise ValueError("At least one model update must be specified")
+
+        for update in self._updates:
+            update.validate()
diff --git a/clients/client-python/tests/integration/test_model_catalog.py 
b/clients/client-python/tests/integration/test_model_catalog.py
index 88ef813bd5..85211ccd65 100644
--- a/clients/client-python/tests/integration/test_model_catalog.py
+++ b/clients/client-python/tests/integration/test_model_catalog.py
@@ -18,6 +18,7 @@ from random import randint
 
 from gravitino import Catalog, GravitinoAdminClient, GravitinoClient, 
NameIdentifier
 from gravitino.api.model_change import ModelChange
+from gravitino.api.model_version_change import ModelVersionChange
 from gravitino.exceptions.base import (
     ModelAlreadyExistsException,
     ModelVersionAliasesAlreadyExistException,
@@ -280,6 +281,53 @@ 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):
+        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", {})
+
+        # Test link model version
+        self._catalog.as_model_catalog().link_model_version(
+            model_ident,
+            uri="uri",
+            aliases=["alias1", "alias2"],
+            comment="comment",
+            properties={"k1": "v1", "k2": "v2"},
+        )
+
+        model_version = self._catalog.as_model_catalog().get_model_version(
+            model_ident, 0
+        )
+        self.assertEqual(0, model_version.version())
+        self.assertEqual("uri", model_version.uri())
+        self.assertEqual(["alias1", "alias2"], model_version.aliases())
+        self.assertEqual("comment", model_version.comment())
+        self.assertEqual({"k1": "v1", "k2": "v2"}, model_version.properties())
+
+        model_version = 
self._catalog.as_model_catalog().get_model_version_by_alias(
+            model_ident, "alias1"
+        )
+        self.assertEqual(0, model_version.version())
+        self.assertEqual("uri", model_version.uri())
+
+        model_version = 
self._catalog.as_model_catalog().get_model_version_by_alias(
+            model_ident, "alias2"
+        )
+        self.assertEqual(0, model_version.version())
+        self.assertEqual("uri", model_version.uri())
+
+        changes = [ModelVersionChange.update_comment("new comment")]
+        self._catalog.as_model_catalog().alter_model_version(model_ident, 0, 
*changes)
+        updated_model_version = 
self._catalog.as_model_catalog().get_model_version(
+            model_ident, 0
+        )
+
+        self.assertEqual(0, updated_model_version.version())
+        self.assertEqual("new comment", updated_model_version.comment())
+        self.assertEqual(["alias1", "alias2"], updated_model_version.aliases())
+        self.assertEqual({"k1": "v1", "k2": "v2"}, 
updated_model_version.properties())
+        self.assertEqual("uri", updated_model_version.uri())
+
     def test_link_get_model_version(self):
         model_name = "model_it_model" + str(randint(0, 1000))
         model_ident = NameIdentifier.of(self._schema_name, model_name)
diff --git a/clients/client-python/tests/unittests/test_model_catalog_api.py 
b/clients/client-python/tests/unittests/test_model_catalog_api.py
index 721cc6756a..19b95ae4ee 100644
--- a/clients/client-python/tests/unittests/test_model_catalog_api.py
+++ b/clients/client-python/tests/unittests/test_model_catalog_api.py
@@ -23,6 +23,7 @@ from gravitino import GravitinoClient, NameIdentifier
 from gravitino.api.model.model import Model
 from gravitino.api.model.model_version import ModelVersion
 from gravitino.api.model_change import ModelChange
+from gravitino.api.model_version_change import ModelVersionChange
 from gravitino.dto.audit_dto import AuditDTO
 from gravitino.dto.model_dto import ModelDTO
 from gravitino.dto.model_version_dto import ModelVersionDTO
@@ -259,6 +260,44 @@ class TestModelCatalogApi(unittest.TestCase):
             model_versions = 
catalog.as_model_catalog().list_model_versions(model_ident)
             self.assertEqual([], model_versions)
 
+    def test_alter_model_version_comment(self, *mock_method):
+        gravitino_client = GravitinoClient(
+            uri="http://localhost:8090";, metalake_name=self._metalake_name
+        )
+        catalog = gravitino_client.load_catalog(self._catalog_name)
+
+        model_ident = NameIdentifier.of("schema", "model1")
+        version = 1
+        alias = "alias1"
+        new_comment = "new comment"
+
+        model_version_dto = ModelVersionDTO(
+            _version=1,
+            _uri="http://localhost:8090";,
+            _aliases=["alias1", "alias2"],
+            _comment="new comment",
+            _properties={"k": "v"},
+            _audit=AuditDTO(_creator="test", 
_create_time="2022-01-01T00:00:00Z"),
+        )
+
+        model_resp = ModelVersionResponse(_model_version=model_version_dto, 
_code=0)
+        json_str = model_resp.to_json()
+        mock_resp = self._mock_http_response(json_str)
+
+        with patch(
+            "gravitino.utils.http_client.HTTPClient.put",
+            return_value=mock_resp,
+        ):
+            model_version = catalog.as_model_catalog().alter_model_version(
+                model_ident, version, 
ModelVersionChange.update_comment(new_comment)
+            )
+            self._compare_model_versions(model_version_dto, model_version)
+
+            model_version = catalog.as_model_catalog().alter_model_version(
+                model_ident, alias, 
ModelVersionChange.update_comment(new_comment)
+            )
+            self._compare_model_versions(model_version_dto, model_version)
+
     def test_get_model_version(self, *mock_method):
         gravitino_client = GravitinoClient(
             uri="http://localhost:8090";, metalake_name=self._metalake_name
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/ModelVersionUpdateRequest.java
 
b/common/src/main/java/org/apache/gravitino/dto/requests/ModelVersionUpdateRequest.java
new file mode 100644
index 0000000000..1026bd82ae
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/ModelVersionUpdateRequest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.apache.gravitino.model.ModelVersionChange;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Request to update a model version. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
+@JsonSubTypes({
+  @JsonSubTypes.Type(
+      value = ModelVersionUpdateRequest.UpdateModelVersionComment.class,
+      name = "updateComment")
+})
+public interface ModelVersionUpdateRequest extends RESTRequest {
+
+  /**
+   * Returns the model version change.
+   *
+   * @return the model version change.
+   */
+  ModelVersionChange modelVersionChange();
+
+  /** Request to update comment of a model version */
+  @EqualsAndHashCode
+  @AllArgsConstructor
+  @NoArgsConstructor(force = true)
+  @ToString
+  @Getter
+  class UpdateModelVersionComment implements ModelVersionUpdateRequest {
+    @JsonProperty("newComment")
+    private final String newComment;
+
+    /** {@inheritDoc} */
+    @Override
+    public ModelVersionChange modelVersionChange() {
+      return ModelVersionChange.updateComment(newComment);
+    }
+
+    /** Validates the fields of the request. Always pass. */
+    @Override
+    public void validate() throws IllegalArgumentException {}
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/ModelVersionUpdatesRequest.java
 
b/common/src/main/java/org/apache/gravitino/dto/requests/ModelVersionUpdatesRequest.java
new file mode 100644
index 0000000000..14e2675041
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/ModelVersionUpdatesRequest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a several request to update a model version. */
+@Getter
+@EqualsAndHashCode
+@AllArgsConstructor
+@NoArgsConstructor(force = true)
+@ToString
+public class ModelVersionUpdatesRequest implements RESTRequest {
+  @JsonProperty("updates")
+  private final List<ModelVersionUpdateRequest> updates;
+
+  /** {@inheritDoc} */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    updates.forEach(ModelVersionUpdateRequest::validate);
+  }
+}
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 40c3520bc5..115350dbcc 100644
--- 
a/core/src/test/java/org/apache/gravitino/catalog/TestModelOperationDispatcher.java
+++ 
b/core/src/test/java/org/apache/gravitino/catalog/TestModelOperationDispatcher.java
@@ -365,7 +365,7 @@ public class TestModelOperationDispatcher extends 
TestOperationDispatcher {
   }
 
   @Test
-  void testUpdateModelComment() {
+  void testUpdateModelVersionComment() {
     String schemaName = randomSchemaName();
     String schemaComment = "schema which tests update";
 
@@ -393,6 +393,9 @@ public class TestModelOperationDispatcher extends 
TestOperationDispatcher {
         modelOperationDispatcher.alterModelVersion(modelIdent, "alias1", 
changeComment);
 
     Assertions.assertEquals(modelVersion.uri(), alteredModelVersion.uri());
+    Assertions.assertEquals(modelVersion.aliases(), 
alteredModelVersion.aliases());
+    Assertions.assertEquals(versionNewComment, alteredModelVersion.comment());
+    Assertions.assertEquals(modelVersion.properties(), 
alteredModelVersion.properties());
   }
 
   private String 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 cb4b55833a..1588ba0e27 100644
--- 
a/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
+++ 
b/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
@@ -1045,6 +1045,7 @@ public class TestCatalogOperations
     for (ModelVersionChange change : changes) {
       if (change instanceof ModelVersionChange.UpdateComment) {
         newComment = ((ModelVersionChange.UpdateComment) change).newComment();
+
       } else {
         throw new IllegalArgumentException("Unsupported model change: " + 
change);
       }
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ModelOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ModelOperations.java
index 7c4ff37c58..e2ebc58302 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ModelOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ModelOperations.java
@@ -38,6 +38,8 @@ import org.apache.gravitino.dto.requests.ModelRegisterRequest;
 import org.apache.gravitino.dto.requests.ModelUpdateRequest;
 import org.apache.gravitino.dto.requests.ModelUpdatesRequest;
 import org.apache.gravitino.dto.requests.ModelVersionLinkRequest;
+import org.apache.gravitino.dto.requests.ModelVersionUpdateRequest;
+import org.apache.gravitino.dto.requests.ModelVersionUpdatesRequest;
 import org.apache.gravitino.dto.responses.BaseResponse;
 import org.apache.gravitino.dto.responses.DropResponse;
 import org.apache.gravitino.dto.responses.EntityListResponse;
@@ -49,6 +51,7 @@ import org.apache.gravitino.metrics.MetricNames;
 import org.apache.gravitino.model.Model;
 import org.apache.gravitino.model.ModelChange;
 import org.apache.gravitino.model.ModelVersion;
+import org.apache.gravitino.model.ModelVersionChange;
 import org.apache.gravitino.server.web.Utils;
 import org.apache.gravitino.utils.NameIdentifierUtil;
 import org.apache.gravitino.utils.NamespaceUtil;
@@ -405,6 +408,97 @@ public class ModelOperations {
     }
   }
 
+  @PUT
+  @Path("{model}/versions/{version}")
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "alter-model-version." + MetricNames.HTTP_PROCESS_DURATION, 
absolute = true)
+  @ResponseMetered(name = "alter-model-version", absolute = true)
+  public Response alterModelVersion(
+      @PathParam("metalake") String metalake,
+      @PathParam("catalog") String catalog,
+      @PathParam("schema") String schema,
+      @PathParam("model") String model,
+      @PathParam("version") int version,
+      ModelVersionUpdatesRequest request) {
+    LOG.info(
+        "Received alter model version request: {}.{}.{}.{}.{}",
+        metalake,
+        catalog,
+        schema,
+        model,
+        version);
+    request.validate();
+
+    try {
+      NameIdentifier modelId = NameIdentifierUtil.ofModel(metalake, catalog, 
schema, model);
+
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            request.validate();
+            ModelVersionChange[] changes =
+                request.getUpdates().stream()
+                    .map(ModelVersionUpdateRequest::modelVersionChange)
+                    .toArray(ModelVersionChange[]::new);
+
+            ModelVersion modelVersion =
+                modelDispatcher.alterModelVersion(modelId, version, changes);
+            Response response =
+                Utils.ok(new 
ModelVersionResponse(DTOConverters.toDTO(modelVersion)));
+            LOG.info("Model version altered: {}.{}", modelId, version);
+            return response;
+          });
+    } catch (Exception e) {
+      return ExceptionHandlers.handleModelException(
+          OperationType.ALTER, versionString(model, version), schema, e);
+    }
+  }
+
+  @PUT
+  @Path("{model}/aliases/{alias}")
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "alter-model-alias." + MetricNames.HTTP_PROCESS_DURATION, 
absolute = true)
+  @ResponseMetered(name = "alter-model-alias", absolute = true)
+  public Response alterModelVersionByAlias(
+      @PathParam("metalake") String metalake,
+      @PathParam("catalog") String catalog,
+      @PathParam("schema") String schema,
+      @PathParam("model") String model,
+      @PathParam("alias") String alias,
+      ModelVersionUpdatesRequest request) {
+    LOG.info(
+        "Received alter model version request: {}.{}.{}.{}.{}",
+        metalake,
+        catalog,
+        schema,
+        model,
+        alias);
+    request.validate();
+
+    try {
+      NameIdentifier modelId = NameIdentifierUtil.ofModel(metalake, catalog, 
schema, model);
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            request.validate();
+            ModelVersionChange[] changes =
+                request.getUpdates().stream()
+                    .map(ModelVersionUpdateRequest::modelVersionChange)
+                    .toArray(ModelVersionChange[]::new);
+
+            ModelVersion modelVersion = 
modelDispatcher.alterModelVersion(modelId, alias, changes);
+            Response response =
+                Utils.ok(new 
ModelVersionResponse(DTOConverters.toDTO(modelVersion)));
+            LOG.info("Model version altered: {}.{}", modelId, alias);
+            return response;
+          });
+
+    } catch (Exception e) {
+      return ExceptionHandlers.handleModelException(
+          OperationType.ALTER, aliasString(model, alias), schema, e);
+    }
+  }
+
   @PUT
   @Path("{model}")
   @Produces("application/vnd.gravitino.v1+json")
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestModelOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestModelOperations.java
index c3040a5acc..ebb852cc99 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestModelOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestModelOperations.java
@@ -36,10 +36,13 @@ import javax.ws.rs.core.Response;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.catalog.ModelDispatcher;
+import org.apache.gravitino.dto.model.ModelVersionDTO;
 import org.apache.gravitino.dto.requests.ModelRegisterRequest;
 import org.apache.gravitino.dto.requests.ModelUpdateRequest;
 import org.apache.gravitino.dto.requests.ModelUpdatesRequest;
 import org.apache.gravitino.dto.requests.ModelVersionLinkRequest;
+import org.apache.gravitino.dto.requests.ModelVersionUpdateRequest;
+import org.apache.gravitino.dto.requests.ModelVersionUpdatesRequest;
 import org.apache.gravitino.dto.responses.BaseResponse;
 import org.apache.gravitino.dto.responses.DropResponse;
 import org.apache.gravitino.dto.responses.EntityListResponse;
@@ -55,6 +58,7 @@ import org.apache.gravitino.meta.AuditInfo;
 import org.apache.gravitino.model.Model;
 import org.apache.gravitino.model.ModelChange;
 import org.apache.gravitino.model.ModelVersion;
+import org.apache.gravitino.model.ModelVersionChange;
 import org.apache.gravitino.rest.RESTUtils;
 import org.apache.gravitino.utils.NameIdentifierUtil;
 import org.apache.gravitino.utils.NamespaceUtil;
@@ -967,6 +971,84 @@ public class TestModelOperations extends JerseyTest {
     
Assertions.assertFalse(modelResp.getModel().properties().containsKey("key1"));
   }
 
+  @Test
+  void testUpdateModelVersionComment() {
+    String modelName = "model1";
+    String newComment = "new comment";
+    String uri = "uri1";
+    int version = 0;
+    String[] alias = new String[] {"alias1"};
+
+    NameIdentifier modelId = NameIdentifierUtil.ofModel(metalake, catalog, 
schema, modelName);
+    ModelVersion mockModelVersion = mockModelVersion(version, uri, alias, 
newComment);
+
+    when(modelDispatcher.alterModelVersion(
+            modelId, version, ModelVersionChange.updateComment(newComment)))
+        .thenReturn(mockModelVersion);
+
+    ModelVersionUpdatesRequest req =
+        new ModelVersionUpdatesRequest(
+            Collections.singletonList(
+                new 
ModelVersionUpdateRequest.UpdateModelVersionComment(newComment)));
+
+    Response resp =
+        target(modelPath())
+            .path("model1")
+            .path("versions")
+            .path("0")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity(req, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    ModelVersionResponse modelVersionResponse = 
resp.readEntity(ModelVersionResponse.class);
+    ModelVersionDTO modelVersion = modelVersionResponse.getModelVersion();
+
+    Assertions.assertEquals(newComment, modelVersion.comment());
+    Assertions.assertArrayEquals(alias, modelVersion.aliases());
+    Assertions.assertEquals(version, modelVersion.version());
+    Assertions.assertEquals(uri, modelVersion.uri());
+  }
+
+  @Test
+  void testUpdateModelVersionCommentByAlias() {
+    String modelName = "model1";
+    String newComment = "new comment";
+    String uri = "uri1";
+    int version = 0;
+    String[] alias = new String[] {"alias1"};
+
+    NameIdentifier modelId = NameIdentifierUtil.ofModel(metalake, catalog, 
schema, modelName);
+    ModelVersion mockModelVersion = mockModelVersion(version, uri, alias, 
newComment);
+
+    when(modelDispatcher.alterModelVersion(
+            modelId, alias[0], ModelVersionChange.updateComment(newComment)))
+        .thenReturn(mockModelVersion);
+
+    ModelVersionUpdatesRequest req =
+        new ModelVersionUpdatesRequest(
+            Collections.singletonList(
+                new 
ModelVersionUpdateRequest.UpdateModelVersionComment(newComment)));
+
+    Response resp =
+        target(modelPath())
+            .path("model1")
+            .path("aliases")
+            .path(alias[0])
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity(req, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+    ModelVersionResponse modelVersionResponse = 
resp.readEntity(ModelVersionResponse.class);
+    ModelVersionDTO modelVersion = modelVersionResponse.getModelVersion();
+
+    Assertions.assertEquals(newComment, modelVersion.comment());
+    Assertions.assertArrayEquals(alias, modelVersion.aliases());
+    Assertions.assertEquals(version, modelVersion.version());
+    Assertions.assertEquals(uri, modelVersion.uri());
+  }
+
   private String modelPath() {
     return "/metalakes/" + metalake + "/catalogs/" + catalog + "/schemas/" + 
schema + "/models";
   }

Reply via email to