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 0d73e4c2f8 [#7440] feat(model): Support for altering model version 
with multiple URIs (#7881)
0d73e4c2f8 is described below

commit 0d73e4c2f88b77bc8df69ae5f102f521972b99f1
Author: XiaoZ <[email protected]>
AuthorDate: Thu Aug 7 15:17:33 2025 +0800

    [#7440] feat(model): Support for altering model version with multiple URIs 
(#7881)
    
    ### What changes were proposed in this pull request?
    
    Support for altering model version with multiple URIs.
    
    ### Why are the changes needed?
    
    Fix: #7440
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    UT.
    
    ---------
    
    Co-authored-by: zhanghan <[email protected]>
---
 .../apache/gravitino/model/ModelVersionChange.java | 205 ++++++++++++++++++++-
 .../gravitino/model/TestModelVersionChange.java    | 136 +++++++++++++-
 .../catalog/model/ModelCatalogOperations.java      |  31 +++-
 .../catalog/model/TestModelCatalogOperations.java  | 204 +++++++++++++++++++-
 .../relational/mapper/ModelVersionMetaMapper.java  |  15 +-
 .../mapper/ModelVersionMetaSQLProviderFactory.java |  12 +-
 .../base/ModelVersionMetaBaseSQLProvider.java      |  45 +++--
 .../provider/h2/ModelVersionMetaH2Provider.java    |  28 +++
 .../service/ModelVersionMetaService.java           |  63 ++++---
 .../storage/relational/utils/POConverters.java     |   9 +-
 .../catalog/TestModelOperationDispatcher.java      | 168 ++++++++++++++++-
 .../gravitino/connector/TestCatalogOperations.java |  18 +-
 .../service/TestModelVersionMetaService.java       |   8 +-
 13 files changed, 839 insertions(+), 103 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 55fa015453..d79235564d 100644
--- a/api/src/main/java/org/apache/gravitino/model/ModelVersionChange.java
+++ b/api/src/main/java/org/apache/gravitino/model/ModelVersionChange.java
@@ -80,6 +80,38 @@ public interface ModelVersionChange {
     return new ModelVersionChange.UpdateUri(newUri);
   }
 
+  /**
+   * Create a ModelVersionChange for updating the uri of a model version.
+   *
+   * @param uriName The name of the uri to be updated for the model version.
+   * @param newUri The new uri to be set for the model version.
+   * @return A new ModelVersionChange instance for updating the uri of a model 
version.
+   */
+  static ModelVersionChange updateUri(String uriName, String newUri) {
+    return new ModelVersionChange.UpdateUri(uriName, newUri);
+  }
+
+  /**
+   * Create a ModelVersionChange for adding the uri of a model version.
+   *
+   * @param uriName The name of the uri to be added for the model version.
+   * @param uri The uri to be added for the model version.
+   * @return A new ModelVersionChange instance for adding the uri of a model 
version.
+   */
+  static ModelVersionChange addUri(String uriName, String uri) {
+    return new ModelVersionChange.AddUri(uriName, uri);
+  }
+
+  /**
+   * Create a ModelVersionChange for removing the uri of a model version.
+   *
+   * @param uriName The name of the uri to be removed for the model version.
+   * @return A new ModelVersionChange instance for removing the uri of a model 
version.
+   */
+  static ModelVersionChange removeUri(String uriName) {
+    return new ModelVersionChange.RemoveUri(uriName);
+  }
+
   /**
    * Create a ModelVersionChange for updating the aliases of a model version.
    *
@@ -139,7 +171,7 @@ public interface ModelVersionChange {
      * Generates a hash code for this {@link UpdateComment} instance. The hash 
code is based on the
      * new comment of the model.
      *
-     * @return A hash code value for this model renaming operation.
+     * @return A hash code value for this comment update operation.
      */
     @Override
     public int hashCode() {
@@ -294,6 +326,7 @@ public interface ModelVersionChange {
 
   /** A ModelVersionChange to update the uri of a model version. */
   final class UpdateUri implements ModelVersionChange {
+    private final String uriName;
     private final String newUri;
 
     /**
@@ -302,6 +335,17 @@ public interface ModelVersionChange {
      * @param newUri The new uri to be set for the model version.
      */
     public UpdateUri(String newUri) {
+      this(ModelVersion.URI_NAME_UNKNOWN, newUri);
+    }
+
+    /**
+     * Creates a new {@link UpdateUri} instance with the specified new uri and 
its name.
+     *
+     * @param uriName The name of the uri to be updated for the model version.
+     * @param newUri The new uri to be set for the model version.
+     */
+    public UpdateUri(String uriName, String newUri) {
+      this.uriName = uriName;
       this.newUri = newUri;
     }
 
@@ -314,9 +358,18 @@ public interface ModelVersionChange {
       return newUri;
     }
 
+    /**
+     * Returns the name of the uri to be updated for the model version.
+     *
+     * @return The name of the uri to be updated for the model version.
+     */
+    public String uriName() {
+      return uriName;
+    }
+
     /**
      * Compares this UpdateUri instance with another object for equality. The 
comparison is based on
-     * the new uri of the model version.
+     * the new uri and its name of the model version.
      *
      * @param obj The object to compare with this instance.
      * @return {@code true} if the given object represents the same model 
update operation; {@code
@@ -327,29 +380,163 @@ public interface ModelVersionChange {
       if (obj == this) return true;
       if (!(obj instanceof UpdateUri)) return false;
       UpdateUri other = (UpdateUri) obj;
-      return Objects.equals(newUri, other.newUri);
+      return Objects.equals(newUri, other.newUri) && Objects.equals(uriName, 
other.uriName);
     }
 
     /**
-     * Generates a hash code for this UpdateUri instance. The hash code is 
based on the new uri of
-     * the model.
+     * Generates a hash code for this UpdateUri instance. The hash code is 
based on the new uri and
+     * its name of the model version.
      *
-     * @return A hash code value for this model renaming operation.
+     * @return A hash code value for this URI update operation.
      */
     @Override
     public int hashCode() {
-      return Objects.hash(newUri);
+      return Objects.hash(newUri, uriName);
     }
 
     /**
      * Provides a string representation of the UpdateUri instance. This string 
format includes the
-     * class name followed by the new uri to be set.
+     * class name followed by the new uri and its name to be updated.
      *
      * @return A string summary of the UpdateUri instance.
      */
     @Override
     public String toString() {
-      return "UpdateUri " + newUri;
+      return "UpdateUri uriName: (" + uriName + ") newUri: (" + newUri + ")";
+    }
+  }
+
+  /** A ModelVersionChange to add a uri of a model version. */
+  final class AddUri implements ModelVersionChange {
+    private final String uriName;
+    private final String uri;
+
+    /**
+     * Creates a new {@link AddUri} instance with the specified uri and uri 
name.
+     *
+     * @param uriName The name of the uri to be added.
+     * @param uri The uri to be added for the model version.
+     */
+    public AddUri(String uriName, String uri) {
+      this.uriName = uriName;
+      this.uri = uri;
+    }
+
+    /**
+     * Returns the uri to be added for the model version.
+     *
+     * @return The uri to be added for the model version.
+     */
+    public String uri() {
+      return uri;
+    }
+
+    /**
+     * Returns the name of the uri to be added for the model version.
+     *
+     * @return The name of the uri to be added for the model version.
+     */
+    public String uriName() {
+      return uriName;
+    }
+
+    /**
+     * Compares this AddUri instance with another object for equality. The 
comparison is based on
+     * the uri and its name of the model version.
+     *
+     * @param obj The object to compare with this instance.
+     * @return {@code true} if the given object represents the same model 
update operation; {@code
+     *     false} otherwise.
+     */
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == this) return true;
+      if (!(obj instanceof AddUri)) return false;
+      AddUri other = (AddUri) obj;
+      return Objects.equals(uri, other.uri) && Objects.equals(uriName, 
other.uriName);
+    }
+
+    /**
+     * Generates a hash code for this AddUri instance. The hash code is based 
on the uri and its
+     * name of the model.
+     *
+     * @return A hash code value for this URI addition operation.
+     */
+    @Override
+    public int hashCode() {
+      return Objects.hash(uri, uriName);
+    }
+
+    /**
+     * Provides a string representation of the AddUri instance. This string 
format includes the
+     * class name followed by the uri and its name to be added.
+     *
+     * @return A string summary of the AddUri instance.
+     */
+    @Override
+    public String toString() {
+      return "AddUri uriName: (" + uriName + ") uri: (" + uri + ")";
+    }
+  }
+
+  /** A ModelVersionChange to remove a uri of a model version. */
+  final class RemoveUri implements ModelVersionChange {
+    private final String uriName;
+
+    /**
+     * Creates a new {@link RemoveUri} instance with the specified uri name.
+     *
+     * @param uriName The name of the uri to be removed.
+     */
+    public RemoveUri(String uriName) {
+      this.uriName = uriName;
+    }
+
+    /**
+     * Returns the name of the uri to be removed for the model version.
+     *
+     * @return The name of the uri to be removed for the model version.
+     */
+    public String uriName() {
+      return uriName;
+    }
+
+    /**
+     * Compares this RemoveUri instance with another object for equality. The 
comparison is based on
+     * the uri name of the model version.
+     *
+     * @param obj The object to compare with this instance.
+     * @return {@code true} if the given object represents the same model 
update operation; {@code
+     *     false} otherwise.
+     */
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == this) return true;
+      if (!(obj instanceof RemoveUri)) return false;
+      RemoveUri other = (RemoveUri) obj;
+      return Objects.equals(uriName, other.uriName);
+    }
+
+    /**
+     * Generates a hash code for this RemoveUri instance. The hash code is 
based on the uri and its
+     * name of the model.
+     *
+     * @return A hash code value for this URI removal operation.
+     */
+    @Override
+    public int hashCode() {
+      return Objects.hash(uriName);
+    }
+
+    /**
+     * Provides a string representation of the RemoveUri instance. This string 
format includes the
+     * class name followed by the uri name to be removed.
+     *
+     * @return A string summary of the RemoveUri instance.
+     */
+    @Override
+    public String toString() {
+      return "RemoveUri uriName: (" + uriName + ")";
     }
   }
 
diff --git 
a/api/src/test/java/org/apache/gravitino/model/TestModelVersionChange.java 
b/api/src/test/java/org/apache/gravitino/model/TestModelVersionChange.java
index cd64776390..ac79955fbd 100644
--- a/api/src/test/java/org/apache/gravitino/model/TestModelVersionChange.java
+++ b/api/src/test/java/org/apache/gravitino/model/TestModelVersionChange.java
@@ -169,7 +169,21 @@ public class TestModelVersionChange {
     ModelVersionChange.UpdateUri updateUriChange =
         (ModelVersionChange.UpdateUri) modelVersionChange;
     Assertions.assertEquals(newUri, updateUriChange.newUri());
-    Assertions.assertEquals("UpdateUri " + newUri, updateUriChange.toString());
+    Assertions.assertEquals(ModelVersion.URI_NAME_UNKNOWN, 
updateUriChange.uriName());
+    Assertions.assertEquals(
+        "UpdateUri uriName: (unknown) newUri: (" + newUri + ")", 
updateUriChange.toString());
+
+    String uriName = "n1";
+    modelVersionChange = ModelVersionChange.updateUri(uriName, newUri);
+
+    Assertions.assertEquals(ModelVersionChange.UpdateUri.class, 
modelVersionChange.getClass());
+
+    updateUriChange = (ModelVersionChange.UpdateUri) modelVersionChange;
+    Assertions.assertEquals(newUri, updateUriChange.newUri());
+    Assertions.assertEquals(uriName, updateUriChange.uriName());
+    Assertions.assertEquals(
+        "UpdateUri uriName: (" + uriName + ") newUri: (" + newUri + ")",
+        updateUriChange.toString());
   }
 
   @Test
@@ -182,7 +196,21 @@ public class TestModelVersionChange {
     ModelVersionChange.UpdateUri updateUriChange =
         (ModelVersionChange.UpdateUri) modelVersionChange;
     Assertions.assertEquals(newUri, updateUriChange.newUri());
-    Assertions.assertEquals("UpdateUri " + newUri, updateUriChange.toString());
+    Assertions.assertEquals(ModelVersion.URI_NAME_UNKNOWN, 
updateUriChange.uriName());
+    Assertions.assertEquals(
+        "UpdateUri uriName: (unknown) newUri: (" + newUri + ")", 
updateUriChange.toString());
+
+    String uriName = "n1";
+    modelVersionChange = new ModelVersionChange.UpdateUri(uriName, newUri);
+
+    Assertions.assertEquals(ModelVersionChange.UpdateUri.class, 
modelVersionChange.getClass());
+
+    updateUriChange = (ModelVersionChange.UpdateUri) modelVersionChange;
+    Assertions.assertEquals(newUri, updateUriChange.newUri());
+    Assertions.assertEquals(uriName, updateUriChange.uriName());
+    Assertions.assertEquals(
+        "UpdateUri uriName: (" + uriName + ") newUri: (" + newUri + ")",
+        updateUriChange.toString());
   }
 
   @Test
@@ -201,6 +229,110 @@ public class TestModelVersionChange {
     Assertions.assertEquals(modelVersionChange1.hashCode(), 
modelVersionChange2.hashCode());
     Assertions.assertNotEquals(modelVersionChange1.hashCode(), 
modelVersionChange3.hashCode());
     Assertions.assertNotEquals(modelVersionChange2.hashCode(), 
modelVersionChange3.hashCode());
+
+    modelVersionChange1 = ModelVersionChange.updateUri("n1", uri1);
+    modelVersionChange2 = ModelVersionChange.updateUri("n1", uri1);
+    modelVersionChange3 = ModelVersionChange.updateUri("n2", uri2);
+
+    Assertions.assertEquals(modelVersionChange1, modelVersionChange2);
+    Assertions.assertNotEquals(modelVersionChange1, modelVersionChange3);
+    Assertions.assertNotEquals(modelVersionChange2, modelVersionChange3);
+
+    Assertions.assertEquals(modelVersionChange1.hashCode(), 
modelVersionChange2.hashCode());
+    Assertions.assertNotEquals(modelVersionChange1.hashCode(), 
modelVersionChange3.hashCode());
+    Assertions.assertNotEquals(modelVersionChange2.hashCode(), 
modelVersionChange3.hashCode());
+  }
+
+  @Test
+  void testAddUriChangeUseStaticMethod() {
+    String uri = "S3://bucket/key";
+    String uriName = "n1";
+    ModelVersionChange modelVersionChange = ModelVersionChange.addUri(uriName, 
uri);
+
+    Assertions.assertEquals(ModelVersionChange.AddUri.class, 
modelVersionChange.getClass());
+
+    ModelVersionChange.AddUri change = (ModelVersionChange.AddUri) 
modelVersionChange;
+    Assertions.assertEquals(uri, change.uri());
+    Assertions.assertEquals(uriName, change.uriName());
+    Assertions.assertEquals(
+        "AddUri uriName: (" + uriName + ") uri: (" + uri + ")", 
change.toString());
+  }
+
+  @Test
+  void testAddUriChangeUseConstructor() {
+    String uri = "S3://bucket/key";
+    String uriName = "n1";
+    ModelVersionChange modelVersionChange = new 
ModelVersionChange.AddUri(uriName, uri);
+
+    Assertions.assertEquals(ModelVersionChange.AddUri.class, 
modelVersionChange.getClass());
+
+    ModelVersionChange.AddUri change = (ModelVersionChange.AddUri) 
modelVersionChange;
+    Assertions.assertEquals(uri, change.uri());
+    Assertions.assertEquals(uriName, change.uriName());
+    Assertions.assertEquals(
+        "AddUri uriName: (" + uriName + ") uri: (" + uri + ")", 
change.toString());
+  }
+
+  @Test
+  void testAddUriChangeEquals() {
+    String uri1 = "S3://bucket/key1";
+    String uri2 = "S3://bucket/key2";
+    String uriName1 = "n1";
+    String uriName2 = "n2";
+
+    ModelVersionChange modelVersionChange1 = 
ModelVersionChange.addUri(uriName1, uri1);
+    ModelVersionChange modelVersionChange2 = 
ModelVersionChange.addUri(uriName1, uri1);
+    ModelVersionChange modelVersionChange3 = 
ModelVersionChange.addUri(uriName2, uri2);
+
+    Assertions.assertEquals(modelVersionChange1, modelVersionChange2);
+    Assertions.assertNotEquals(modelVersionChange1, modelVersionChange3);
+    Assertions.assertNotEquals(modelVersionChange2, modelVersionChange3);
+
+    Assertions.assertEquals(modelVersionChange1.hashCode(), 
modelVersionChange2.hashCode());
+    Assertions.assertNotEquals(modelVersionChange1.hashCode(), 
modelVersionChange3.hashCode());
+    Assertions.assertNotEquals(modelVersionChange2.hashCode(), 
modelVersionChange3.hashCode());
+  }
+
+  @Test
+  void testRemoveUriChangeUseStaticMethod() {
+    String uriName = "n1";
+    ModelVersionChange modelVersionChange = 
ModelVersionChange.removeUri(uriName);
+
+    Assertions.assertEquals(ModelVersionChange.RemoveUri.class, 
modelVersionChange.getClass());
+
+    ModelVersionChange.RemoveUri change = (ModelVersionChange.RemoveUri) 
modelVersionChange;
+    Assertions.assertEquals(uriName, change.uriName());
+    Assertions.assertEquals("RemoveUri uriName: (" + uriName + ")", 
change.toString());
+  }
+
+  @Test
+  void testRemoveUriChangeUseConstructor() {
+    String uriName = "n1";
+    ModelVersionChange modelVersionChange = new 
ModelVersionChange.RemoveUri(uriName);
+
+    Assertions.assertEquals(ModelVersionChange.RemoveUri.class, 
modelVersionChange.getClass());
+
+    ModelVersionChange.RemoveUri change = (ModelVersionChange.RemoveUri) 
modelVersionChange;
+    Assertions.assertEquals(uriName, change.uriName());
+    Assertions.assertEquals("RemoveUri uriName: (" + uriName + ")", 
change.toString());
+  }
+
+  @Test
+  void testRemoveUriChangeEquals() {
+    String uriName1 = "n1";
+    String uriName2 = "n2";
+
+    ModelVersionChange modelVersionChange1 = 
ModelVersionChange.removeUri(uriName1);
+    ModelVersionChange modelVersionChange2 = 
ModelVersionChange.removeUri(uriName1);
+    ModelVersionChange modelVersionChange3 = 
ModelVersionChange.removeUri(uriName2);
+
+    Assertions.assertEquals(modelVersionChange1, modelVersionChange2);
+    Assertions.assertNotEquals(modelVersionChange1, modelVersionChange3);
+    Assertions.assertNotEquals(modelVersionChange2, modelVersionChange3);
+
+    Assertions.assertEquals(modelVersionChange1.hashCode(), 
modelVersionChange2.hashCode());
+    Assertions.assertNotEquals(modelVersionChange1.hashCode(), 
modelVersionChange3.hashCode());
+    Assertions.assertNotEquals(modelVersionChange2.hashCode(), 
modelVersionChange3.hashCode());
   }
 
   @Test
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 52172136f6..d89d2d9933 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
@@ -19,7 +19,6 @@
 package org.apache.gravitino.catalog.model;
 
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -451,7 +450,7 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
         modelVersionEntity.aliases() == null
             ? Lists.newArrayList()
             : Lists.newArrayList(modelVersionEntity.aliases());
-    String entityUri = 
modelVersionEntity.uris().get(ModelVersion.URI_NAME_UNKNOWN);
+    Map<String, String> entityUris = 
Maps.newHashMap(modelVersionEntity.uris());
     Map<String, String> entityProperties =
         modelVersionEntity.properties() == null
             ? Maps.newHashMap()
@@ -474,7 +473,15 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
 
       } else if (change instanceof ModelVersionChange.UpdateUri) {
         ModelVersionChange.UpdateUri updateUriChange = 
(ModelVersionChange.UpdateUri) change;
-        entityUri = updateUriChange.newUri();
+        doUpdateUri(entityUris, updateUriChange);
+
+      } else if (change instanceof ModelVersionChange.AddUri) {
+        ModelVersionChange.AddUri addUriChange = (ModelVersionChange.AddUri) 
change;
+        doAddUri(entityUris, addUriChange);
+
+      } else if (change instanceof ModelVersionChange.RemoveUri) {
+        ModelVersionChange.RemoveUri removeUriChange = 
(ModelVersionChange.RemoveUri) change;
+        doRemoveUri(entityUris, removeUriChange);
 
       } else if (change instanceof ModelVersionChange.UpdateAliases) {
         ModelVersionChange.UpdateAliases updateAliasesChange =
@@ -493,12 +500,16 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
       }
     }
 
+    if (entityUris.isEmpty()) {
+      throw new IllegalArgumentException("Model version URI cannot be empty");
+    }
+
     return ModelVersionEntity.builder()
         .withVersion(entityVersion)
         .withModelIdentifier(entityModelIdentifier)
         .withAliases(entityAliases)
         .withComment(entityComment)
-        .withUris(ImmutableMap.of(ModelVersion.URI_NAME_UNKNOWN, entityUri))
+        .withUris(entityUris)
         .withProperties(entityProperties)
         .withAuditInfo(
             AuditInfo.builder()
@@ -614,6 +625,18 @@ public class ModelCatalogOperations extends 
ManagedSchemaOperations
     entityProperties.remove(change.property());
   }
 
+  private void doUpdateUri(Map<String, String> entityUris, 
ModelVersionChange.UpdateUri change) {
+    entityUris.replace(change.uriName(), change.newUri());
+  }
+
+  private void doAddUri(Map<String, String> entityUris, 
ModelVersionChange.AddUri change) {
+    entityUris.putIfAbsent(change.uriName(), change.uri());
+  }
+
+  private void doRemoveUri(Map<String, String> entityUris, 
ModelVersionChange.RemoveUri change) {
+    entityUris.remove(change.uriName());
+  }
+
   private void doDeleteAlias(List<String> entityAliases, Set<String> 
deleteSet) {
     entityAliases.removeAll(deleteSet);
   }
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 dd3fcb830d..980568f769 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
@@ -1393,9 +1393,8 @@ public class TestModelCatalogOperations {
     String modelComment = "model1 comment";
 
     String versionComment = "version1 comment";
-    Map<String, String> versionUris = 
ImmutableMap.of(ModelVersion.URI_NAME_UNKNOWN, "uri");
+    Map<String, String> versionUris = ImmutableMap.of("n1", "u1", "n2", "u2");
     String[] versionAliases = new String[] {"alias1", "alias2"};
-    String newVersionUri = "s3://bucket/path/to/new/version";
 
     NameIdentifier modelIdent =
         NameIdentifierUtil.ofModel(METALAKE_NAME, CATALOG_NAME, schemaName, 
modelName);
@@ -1424,12 +1423,11 @@ public class TestModelCatalogOperations {
     Assertions.assertEquals(versionProperties, loadedVersion.properties());
 
     // validate update version uri
-    ModelVersionChange updateUriChange = 
ModelVersionChange.updateUri(newVersionUri);
+    ModelVersionChange updateUriChange = ModelVersionChange.updateUri("n1", 
"u3");
     ModelVersion updatedModelVersion = ops.alterModelVersion(modelIdent, 0, 
updateUriChange);
 
     Assertions.assertEquals(0, updatedModelVersion.version());
-    Assertions.assertEquals(
-        ImmutableMap.of(ModelVersion.URI_NAME_UNKNOWN, newVersionUri), 
updatedModelVersion.uris());
+    Assertions.assertEquals(ImmutableMap.of("n1", "u3", "n2", "u2"), 
updatedModelVersion.uris());
     Assertions.assertEquals(versionComment, updatedModelVersion.comment());
     Assertions.assertArrayEquals(versionAliases, 
updatedModelVersion.aliases());
     Assertions.assertEquals(versionProperties, 
updatedModelVersion.properties());
@@ -1487,6 +1485,202 @@ public class TestModelCatalogOperations {
     Assertions.assertEquals(versionProperties, 
updatedModelVersion.properties());
   }
 
+  @Test
+  void testAddModelVersionUri() {
+    String schemaName = randomSchemaName();
+    createSchema(schemaName);
+
+    String modelName = "model1";
+    String modelComment = "model1 comment";
+
+    String versionComment = "version1 comment";
+    Map<String, String> versionUris = ImmutableMap.of("n1", "u1");
+    String[] versionAliases = new String[] {"alias1", "alias2"};
+
+    NameIdentifier modelIdent =
+        NameIdentifierUtil.ofModel(METALAKE_NAME, CATALOG_NAME, schemaName, 
modelName);
+    StringIdentifier stringId = StringIdentifier.fromId(idGenerator.nextId());
+    Map<String, String> properties = 
StringIdentifier.newPropertiesWithId(stringId, null);
+
+    ops.registerModel(modelIdent, modelComment, properties);
+    StringIdentifier versionId = StringIdentifier.fromId(idGenerator.nextId());
+    Map<String, String> versionProperties =
+        StringIdentifier.newPropertiesWithId(
+            versionId, ImmutableMap.of("key1", "value1", "key2", "value2"));
+
+    ops.linkModelVersion(
+        modelIdent, versionUris, versionAliases, versionComment, 
versionProperties);
+
+    // validate loaded model
+    Model loadedModel = ops.getModel(modelIdent);
+    Assertions.assertEquals(1, loadedModel.latestVersion());
+
+    // validate loaded version
+    ModelVersion loadedVersion = ops.getModelVersion(modelIdent, 0);
+    Assertions.assertEquals(0, loadedVersion.version());
+    Assertions.assertArrayEquals(versionAliases, loadedVersion.aliases());
+    Assertions.assertEquals(versionComment, loadedVersion.comment());
+    Assertions.assertEquals(versionUris, loadedVersion.uris());
+    Assertions.assertEquals(versionProperties, loadedVersion.properties());
+
+    // validate add version uri
+    ModelVersionChange change = ModelVersionChange.addUri("n2", "u2");
+    ModelVersion updatedModelVersion = ops.alterModelVersion(modelIdent, 0, 
change);
+
+    Assertions.assertEquals(0, updatedModelVersion.version());
+    Assertions.assertEquals(ImmutableMap.of("n1", "u1", "n2", "u2"), 
updatedModelVersion.uris());
+    Assertions.assertEquals(versionComment, updatedModelVersion.comment());
+    Assertions.assertArrayEquals(versionAliases, 
updatedModelVersion.aliases());
+    Assertions.assertEquals(versionProperties, 
updatedModelVersion.properties());
+  }
+
+  @Test
+  void testAddModelVersionUriByAlias() {
+    String schemaName = randomSchemaName();
+    createSchema(schemaName);
+
+    String modelName = "model1";
+    String modelComment = "model1 comment";
+
+    String versionComment = "version1 comment";
+    Map<String, String> versionUris = ImmutableMap.of("n1", "u1");
+    String[] versionAliases = new String[] {"alias1", "alias2"};
+
+    NameIdentifier modelIdent =
+        NameIdentifierUtil.ofModel(METALAKE_NAME, CATALOG_NAME, schemaName, 
modelName);
+    StringIdentifier stringId = StringIdentifier.fromId(idGenerator.nextId());
+    Map<String, String> properties = 
StringIdentifier.newPropertiesWithId(stringId, null);
+
+    ops.registerModel(modelIdent, modelComment, properties);
+    StringIdentifier versionId = StringIdentifier.fromId(idGenerator.nextId());
+    Map<String, String> versionProperties =
+        StringIdentifier.newPropertiesWithId(
+            versionId, ImmutableMap.of("key1", "value1", "key2", "value2"));
+
+    ops.linkModelVersion(
+        modelIdent, versionUris, versionAliases, versionComment, 
versionProperties);
+
+    // validate loaded model
+    Model loadedModel = ops.getModel(modelIdent);
+    Assertions.assertEquals(1, loadedModel.latestVersion());
+
+    // validate loaded version
+    ModelVersion loadedVersion = ops.getModelVersion(modelIdent, 
versionAliases[0]);
+    Assertions.assertEquals(0, loadedVersion.version());
+    Assertions.assertArrayEquals(versionAliases, loadedVersion.aliases());
+    Assertions.assertEquals(versionComment, loadedVersion.comment());
+    Assertions.assertEquals(versionUris, loadedVersion.uris());
+    Assertions.assertEquals(versionProperties, loadedVersion.properties());
+
+    // validate add version uri
+    ModelVersionChange change = ModelVersionChange.addUri("n2", "u2");
+    ModelVersion updatedModelVersion = ops.alterModelVersion(modelIdent, 
versionAliases[0], change);
+
+    Assertions.assertEquals(0, updatedModelVersion.version());
+    Assertions.assertEquals(ImmutableMap.of("n1", "u1", "n2", "u2"), 
updatedModelVersion.uris());
+    Assertions.assertEquals(versionComment, updatedModelVersion.comment());
+    Assertions.assertArrayEquals(versionAliases, 
updatedModelVersion.aliases());
+    Assertions.assertEquals(versionProperties, 
updatedModelVersion.properties());
+  }
+
+  @Test
+  void testRemoveModelVersionUri() {
+    String schemaName = randomSchemaName();
+    createSchema(schemaName);
+
+    String modelName = "model1";
+    String modelComment = "model1 comment";
+
+    String versionComment = "version1 comment";
+    Map<String, String> versionUris = ImmutableMap.of("n1", "u1", "n2", "u2");
+    String[] versionAliases = new String[] {"alias1", "alias2"};
+
+    NameIdentifier modelIdent =
+        NameIdentifierUtil.ofModel(METALAKE_NAME, CATALOG_NAME, schemaName, 
modelName);
+    StringIdentifier stringId = StringIdentifier.fromId(idGenerator.nextId());
+    Map<String, String> properties = 
StringIdentifier.newPropertiesWithId(stringId, null);
+
+    ops.registerModel(modelIdent, modelComment, properties);
+    StringIdentifier versionId = StringIdentifier.fromId(idGenerator.nextId());
+    Map<String, String> versionProperties =
+        StringIdentifier.newPropertiesWithId(
+            versionId, ImmutableMap.of("key1", "value1", "key2", "value2"));
+
+    ops.linkModelVersion(
+        modelIdent, versionUris, versionAliases, versionComment, 
versionProperties);
+
+    // validate loaded model
+    Model loadedModel = ops.getModel(modelIdent);
+    Assertions.assertEquals(1, loadedModel.latestVersion());
+
+    // validate loaded version
+    ModelVersion loadedVersion = ops.getModelVersion(modelIdent, 0);
+    Assertions.assertEquals(0, loadedVersion.version());
+    Assertions.assertArrayEquals(versionAliases, loadedVersion.aliases());
+    Assertions.assertEquals(versionComment, loadedVersion.comment());
+    Assertions.assertEquals(versionUris, loadedVersion.uris());
+    Assertions.assertEquals(versionProperties, loadedVersion.properties());
+
+    // validate remove version uri
+    ModelVersionChange change = ModelVersionChange.removeUri("n1");
+    ModelVersion updatedModelVersion = ops.alterModelVersion(modelIdent, 0, 
change);
+
+    Assertions.assertEquals(0, updatedModelVersion.version());
+    Assertions.assertEquals(ImmutableMap.of("n2", "u2"), 
updatedModelVersion.uris());
+    Assertions.assertEquals(versionComment, updatedModelVersion.comment());
+    Assertions.assertArrayEquals(versionAliases, 
updatedModelVersion.aliases());
+    Assertions.assertEquals(versionProperties, 
updatedModelVersion.properties());
+  }
+
+  @Test
+  void testRemoveModelVersionUriByAlias() {
+    String schemaName = randomSchemaName();
+    createSchema(schemaName);
+
+    String modelName = "model1";
+    String modelComment = "model1 comment";
+
+    String versionComment = "version1 comment";
+    Map<String, String> versionUris = ImmutableMap.of("n1", "u1", "n2", "u2");
+    String[] versionAliases = new String[] {"alias1", "alias2"};
+
+    NameIdentifier modelIdent =
+        NameIdentifierUtil.ofModel(METALAKE_NAME, CATALOG_NAME, schemaName, 
modelName);
+    StringIdentifier stringId = StringIdentifier.fromId(idGenerator.nextId());
+    Map<String, String> properties = 
StringIdentifier.newPropertiesWithId(stringId, null);
+
+    ops.registerModel(modelIdent, modelComment, properties);
+    StringIdentifier versionId = StringIdentifier.fromId(idGenerator.nextId());
+    Map<String, String> versionProperties =
+        StringIdentifier.newPropertiesWithId(
+            versionId, ImmutableMap.of("key1", "value1", "key2", "value2"));
+
+    ops.linkModelVersion(
+        modelIdent, versionUris, versionAliases, versionComment, 
versionProperties);
+
+    // validate loaded model
+    Model loadedModel = ops.getModel(modelIdent);
+    Assertions.assertEquals(1, loadedModel.latestVersion());
+
+    // validate loaded version
+    ModelVersion loadedVersion = ops.getModelVersion(modelIdent, 
versionAliases[0]);
+    Assertions.assertEquals(0, loadedVersion.version());
+    Assertions.assertArrayEquals(versionAliases, loadedVersion.aliases());
+    Assertions.assertEquals(versionComment, loadedVersion.comment());
+    Assertions.assertEquals(versionUris, loadedVersion.uris());
+    Assertions.assertEquals(versionProperties, loadedVersion.properties());
+
+    // validate remove version uri
+    ModelVersionChange change = ModelVersionChange.removeUri("n2");
+    ModelVersion updatedModelVersion = ops.alterModelVersion(modelIdent, 
versionAliases[0], change);
+
+    Assertions.assertEquals(0, updatedModelVersion.version());
+    Assertions.assertEquals(ImmutableMap.of("n1", "u1"), 
updatedModelVersion.uris());
+    Assertions.assertEquals(versionComment, updatedModelVersion.comment());
+    Assertions.assertArrayEquals(versionAliases, 
updatedModelVersion.aliases());
+    Assertions.assertEquals(versionProperties, 
updatedModelVersion.properties());
+  }
+
   @Test
   void testUpdateModelAlias() {
     String schemaName = randomSchemaName();
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionMetaMapper.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionMetaMapper.java
index ccf6ea48d4..591f232c8b 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionMetaMapper.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionMetaMapper.java
@@ -19,7 +19,6 @@
 package org.apache.gravitino.storage.relational.mapper;
 
 import java.util.List;
-import java.util.Map;
 import org.apache.gravitino.storage.relational.po.ModelVersionPO;
 import org.apache.ibatis.annotations.DeleteProvider;
 import org.apache.ibatis.annotations.InsertProvider;
@@ -36,6 +35,12 @@ public interface ModelVersionMetaMapper {
       method = "insertModelVersionMetas")
   void insertModelVersionMetas(@Param("modelVersionMetas") 
List<ModelVersionPO> modelVersionPOs);
 
+  @InsertProvider(
+      type = ModelVersionMetaSQLProviderFactory.class,
+      method = "insertModelVersionMetasWithVersionNumber")
+  void insertModelVersionMetasWithVersionNumber(
+      @Param("modelVersionMetas") List<ModelVersionPO> modelVersionPOs);
+
   @SelectProvider(
       type = ModelVersionMetaSQLProviderFactory.class,
       method = "listModelVersionMetasByModelId")
@@ -98,12 +103,4 @@ public interface ModelVersionMetaMapper {
   Integer updateModelVersionMeta(
       @Param("newModelVersionMeta") ModelVersionPO newModelVersionPO,
       @Param("oldModelVersionMeta") ModelVersionPO oldModelVersionPO);
-
-  @UpdateProvider(
-      type = ModelVersionMetaSQLProviderFactory.class,
-      method = "updateModelVersionUris")
-  Integer updateModelVersionUris(
-      @Param("modelId") Long modelId,
-      @Param("modelVersion") Integer modelVersion,
-      @Param("uris") Map<String, String> uris);
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionMetaSQLProviderFactory.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionMetaSQLProviderFactory.java
index f0dce961b7..5e1a9e3308 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionMetaSQLProviderFactory.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionMetaSQLProviderFactory.java
@@ -56,6 +56,11 @@ public class ModelVersionMetaSQLProviderFactory {
     return getProvider().insertModelVersionMetas(modelVersionPOs);
   }
 
+  public static String insertModelVersionMetasWithVersionNumber(
+      @Param("modelVersionMetas") List<ModelVersionPO> modelVersionPOs) {
+    return 
getProvider().insertModelVersionMetasWithVersionNumber(modelVersionPOs);
+  }
+
   public static String listModelVersionMetasByModelId(@Param("modelId") Long 
modelId) {
     return getProvider().listModelVersionMetasByModelId(modelId);
   }
@@ -108,11 +113,4 @@ public class ModelVersionMetaSQLProviderFactory {
       @Param("oldModelVersionMeta") ModelVersionPO oldModelVersionPO) {
     return getProvider().updateModelVersionMeta(newModelVersionPO, 
oldModelVersionPO);
   }
-
-  public static String updateModelVersionUris(
-      @Param("modelId") Long modelId,
-      @Param("modelVersion") Integer modelVersion,
-      @Param("uris") Map<String, String> uris) {
-    return getProvider().updateModelVersionUris(modelId, modelVersion, uris);
-  }
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelVersionMetaBaseSQLProvider.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelVersionMetaBaseSQLProvider.java
index 7baf7bb3ac..09bce00eed 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelVersionMetaBaseSQLProvider.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelVersionMetaBaseSQLProvider.java
@@ -19,7 +19,6 @@
 package org.apache.gravitino.storage.relational.mapper.provider.base;
 
 import java.util.List;
-import java.util.Map;
 import org.apache.gravitino.storage.relational.mapper.ModelMetaMapper;
 import 
org.apache.gravitino.storage.relational.mapper.ModelVersionAliasRelMapper;
 import org.apache.gravitino.storage.relational.mapper.ModelVersionMetaMapper;
@@ -55,6 +54,33 @@ public class ModelVersionMetaBaseSQLProvider {
         + "</script>";
   }
 
+  public String insertModelVersionMetasWithVersionNumber(
+      @Param("modelVersionMetas") List<ModelVersionPO> modelVersionPOs) {
+    return "<script>"
+        + "INSERT INTO "
+        + ModelVersionMetaMapper.TABLE_NAME
+        + "(metalake_id, catalog_id, schema_id, model_id, version, 
model_version_comment,"
+        + " model_version_properties, model_version_uri_name, 
model_version_uri, audit_info, deleted_at)"
+        + " SELECT m.metalake_id, m.catalog_id, m.schema_id, m.model_id, 
v.model_version_number, v.model_version_comment,"
+        + " v.model_version_properties, v.model_version_uri_name, 
v.model_version_uri, v.audit_info, v.deleted_at"
+        + " FROM ("
+        + "<foreach collection='modelVersionMetas' item='version' 
separator='UNION ALL'>"
+        + " SELECT"
+        + " #{version.modelId} AS model_id, #{version.modelVersionComment} AS 
model_version_comment,"
+        + " #{version.modelVersionProperties} AS model_version_properties, 
#{version.auditInfo} AS audit_info,"
+        + " #{version.deletedAt} AS deleted_at, #{version.modelVersionUriName} 
AS model_version_uri_name,"
+        + " #{version.modelVersionUri} AS model_version_uri, 
#{version.modelVersion} AS model_version_number "
+        + "</foreach>"
+        + " ) v"
+        + " JOIN"
+        + " (SELECT metalake_id, catalog_id, schema_id, model_id"
+        + " FROM "
+        + ModelMetaMapper.TABLE_NAME
+        + " WHERE model_id = #{modelVersionMetas[0].modelId} AND deleted_at = 
0) m"
+        + " ON v.model_id = m.model_id"
+        + "</script>";
+  }
+
   public String listModelVersionMetasByModelId(@Param("modelId") Long modelId) 
{
     return "SELECT metalake_id AS metalakeId, catalog_id AS catalogId, 
schema_id AS schemaId,"
         + " model_id AS modelId, version AS modelVersion, 
model_version_comment AS modelVersionComment,"
@@ -185,21 +211,4 @@ public class ModelVersionMetaBaseSQLProvider {
         + "AND audit_info = #{oldModelVersionMeta.auditInfo} "
         + "AND deleted_at = 0";
   }
-
-  public String updateModelVersionUris(
-      @Param("modelId") Long modelId,
-      @Param("modelVersion") Integer modelVersion,
-      @Param("uris") Map<String, String> uris) {
-    return "<script>"
-        + "UPDATE "
-        + ModelVersionMetaMapper.TABLE_NAME
-        + " SET"
-        + " model_version_uri = CASE"
-        + "<foreach collection='uris' index='k' item='v' separator=''>"
-        + " WHEN model_version_uri_name = #{k} THEN #{v}"
-        + "</foreach>"
-        + " ELSE model_version_uri END"
-        + " WHERE model_id = #{modelId} AND version = #{modelVersion} AND 
deleted_at = 0"
-        + "</script>";
-  }
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/ModelVersionMetaH2Provider.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/ModelVersionMetaH2Provider.java
index 81d33e435e..13f8cff362 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/ModelVersionMetaH2Provider.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/ModelVersionMetaH2Provider.java
@@ -54,4 +54,32 @@ public class ModelVersionMetaH2Provider extends 
ModelVersionMetaBaseSQLProvider
         + " ON v.model_id = m.model_id"
         + "</script>";
   }
+
+  @Override
+  public String insertModelVersionMetasWithVersionNumber(
+      @Param("modelVersionMetas") List<ModelVersionPO> modelVersionPOs) {
+    return "<script>"
+        + "INSERT INTO "
+        + ModelVersionMetaMapper.TABLE_NAME
+        + "(metalake_id, catalog_id, schema_id, model_id, version, 
model_version_comment,"
+        + " model_version_properties, model_version_uri_name, 
model_version_uri, audit_info, deleted_at)"
+        + " SELECT m.metalake_id, m.catalog_id, m.schema_id, m.model_id, 
v.model_version_number, v.model_version_comment,"
+        + " v.model_version_properties, v.model_version_uri_name, 
v.model_version_uri, v.audit_info, v.deleted_at"
+        + " FROM ("
+        + "<foreach collection='modelVersionMetas' item='version' 
separator='UNION ALL'>"
+        + " SELECT"
+        + " CAST(#{version.modelId} AS BIGINT) AS model_id, 
CAST(#{version.modelVersionComment} AS TEXT) AS model_version_comment,"
+        + " CAST(#{version.modelVersionProperties} AS MEDIUMTEXT) AS 
model_version_properties, CAST(#{version.auditInfo} AS MEDIUMTEXT) AS 
audit_info,"
+        + " CAST(#{version.deletedAt} AS BIGINT) AS deleted_at, 
CAST(#{version.modelVersionUriName} AS VARCHAR(128)) AS model_version_uri_name,"
+        + " CAST(#{version.modelVersionUri} AS TEXT) AS model_version_uri, 
CAST(#{version.modelVersion} AS INT) AS model_version_number "
+        + "</foreach>"
+        + " ) v"
+        + " JOIN"
+        + " (SELECT metalake_id, catalog_id, schema_id, model_id"
+        + " FROM "
+        + ModelMetaMapper.TABLE_NAME
+        + " WHERE model_id = #{modelVersionMetas[0].modelId} AND deleted_at = 
0) m"
+        + " ON v.model_id = m.model_id"
+        + "</script>";
+  }
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/ModelVersionMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/ModelVersionMetaService.java
index cadd262059..085ae844dd 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/ModelVersionMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/ModelVersionMetaService.java
@@ -21,7 +21,6 @@ package org.apache.gravitino.storage.relational.service;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
 import java.io.IOException;
@@ -41,7 +40,6 @@ import org.apache.gravitino.Namespace;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
 import org.apache.gravitino.meta.ModelEntity;
 import org.apache.gravitino.meta.ModelVersionEntity;
-import org.apache.gravitino.model.ModelVersion;
 import org.apache.gravitino.storage.relational.mapper.ModelMetaMapper;
 import 
org.apache.gravitino.storage.relational.mapper.ModelVersionAliasRelMapper;
 import org.apache.gravitino.storage.relational.mapper.ModelVersionMetaMapper;
@@ -326,10 +324,36 @@ public class ModelVersionMetaService {
     List<ModelVersionAliasRelPO> newAliasRelPOs =
         POConverters.updateModelVersionAliasRelPO(oldAliasRelPOs, 
newModelVersionEntity);
 
+    boolean isModelVersionUriUpdated =
+        isModelVersionUriUpdated(oldModelVersionEntity, newModelVersionEntity);
+
     final AtomicInteger updateResult = new AtomicInteger(0);
     try {
       SessionUtils.doMultipleWithCommit(
-          () ->
+          () -> {
+            if (isModelVersionUriUpdated) {
+              // delete old model version POs first
+              updateResult.addAndGet(
+                  SessionUtils.doWithoutCommitAndFetchResult(
+                      ModelVersionMetaMapper.class,
+                      mapper -> {
+                        if (isVersionNumber) {
+                          return 
mapper.softDeleteModelVersionMetaByModelIdAndVersion(
+                              modelEntity.id(), Integer.valueOf(ident.name()));
+                        } else {
+                          return 
mapper.softDeleteModelVersionMetaByModelIdAndAlias(
+                              modelEntity.id(), ident.name());
+                        }
+                      }));
+
+              // insert model version POs with updated URIs
+              List<ModelVersionPO> modelVersionPOs =
+                  POConverters.initializeModelVersionPO(newModelVersionEntity, 
modelEntity.id());
+              SessionUtils.doWithoutCommit(
+                  ModelVersionMetaMapper.class,
+                  mapper -> 
mapper.insertModelVersionMetasWithVersionNumber(modelVersionPOs));
+            } else {
+              // update model version POs directly
               updateResult.addAndGet(
                   SessionUtils.doWithoutCommitAndFetchResult(
                       ModelVersionMetaMapper.class,
@@ -337,25 +361,9 @@ public class ModelVersionMetaService {
                           mapper.updateModelVersionMeta(
                               POConverters.updateModelVersionPO(
                                   oldModelVersionPOs.get(0), 
newModelVersionEntity),
-                              oldModelVersionPOs.get(0)))),
-          () ->
-              oldModelVersionPOs.stream()
-                  .filter(v -> isModelVersionUriUpdated(v, 
newModelVersionEntity.uris()))
-                  .findAny()
-                  .ifPresent(
-                      v ->
-                          updateResult.addAndGet(
-                              SessionUtils.doWithoutCommitAndFetchResult(
-                                  ModelVersionMetaMapper.class,
-                                  mapper ->
-                                      mapper.updateModelVersionUris(
-                                          v.getModelId(),
-                                          v.getModelVersion(),
-                                          ImmutableMap.of(
-                                              ModelVersion.URI_NAME_UNKNOWN,
-                                              newModelVersionEntity
-                                                  .uris()
-                                                  
.get(ModelVersion.URI_NAME_UNKNOWN)))))),
+                              oldModelVersionPOs.get(0))));
+            }
+          },
           () -> {
             if (isAliasChanged) {
               SessionUtils.doWithoutCommit(
@@ -399,13 +407,10 @@ public class ModelVersionMetaService {
     return !oldAliases.equals(newAliases);
   }
 
-  // TODO Only modifying the unknown URI is supported currently.
   private boolean isModelVersionUriUpdated(
-      ModelVersionPO oldModelVersionPO, Map<String, String> 
newModelVersionUris) {
-    return 
ModelVersion.URI_NAME_UNKNOWN.equals(oldModelVersionPO.getModelVersionUriName())
-        && newModelVersionUris.containsKey(ModelVersion.URI_NAME_UNKNOWN)
-        && !oldModelVersionPO
-            .getModelVersionUri()
-            .equals(newModelVersionUris.get(ModelVersion.URI_NAME_UNKNOWN));
+      ModelVersionEntity oldModelVersionEntity, ModelVersionEntity 
newModelVersionEntity) {
+    Map<String, String> oldUris = oldModelVersionEntity.uris();
+    Map<String, String> newUris = newModelVersionEntity.uris();
+    return !oldUris.equals(newUris);
   }
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
index 7d00cbd947..28d0388745 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
@@ -1617,11 +1617,10 @@ public class POConverters {
           .withMetalakeId(oldModelVersionPO.getMetalakeId())
           .withCatalogId(oldModelVersionPO.getCatalogId())
           .withSchemaId(oldModelVersionPO.getSchemaId())
-          // TODO The modelVersionUriName and modelVersionUri here are not 
actually used.
-          // They are only used for occupying positions to avoid verification 
failures. They will
-          // be removed when the model version with multiple URIs is supported 
to be modified later
-          .withModelVersionUriName("uriName")
-          .withModelVersionUri("uri")
+          // The modelVersionUriName and modelVersionUri here are not actually 
used.
+          // They are only used for occupying positions to avoid verification 
failures.
+          .withModelVersionUriName(oldModelVersionPO.getModelVersionUriName())
+          .withModelVersionUri(oldModelVersionPO.getModelVersionUri())
           .withModelVersion(oldModelVersionPO.getModelVersion())
           .withModelVersionComment(newModelVersion.comment())
           .withModelVersionProperties(
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 bbfd791096..c9f2cb069a 100644
--- 
a/core/src/test/java/org/apache/gravitino/catalog/TestModelOperationDispatcher.java
+++ 
b/core/src/test/java/org/apache/gravitino/catalog/TestModelOperationDispatcher.java
@@ -774,9 +774,8 @@ public class TestModelOperationDispatcher extends 
TestOperationDispatcher {
     String modelName = randomModelName();
     String modelComment = "model which tests update";
     Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
-    String newUri = "s3://new-bucket/new-path/new-model.json";
 
-    Map<String, String> versionUris = 
ImmutableMap.of(ModelVersion.URI_NAME_UNKNOWN, "uri");
+    Map<String, String> versionUris = ImmutableMap.of("n1", "u1");
     String[] versionAliases = {"alias1", "alias2"};
     String versionComment = "version which tests update";
 
@@ -790,12 +789,14 @@ public class TestModelOperationDispatcher extends 
TestOperationDispatcher {
     modelOperationDispatcher.linkModelVersion(
         modelIdent, versionUris, versionAliases, versionComment, props);
 
-    ModelVersionChange change = ModelVersionChange.updateUri(newUri);
+    String newUri = "u2";
+    Map<String, String> newVersionUris = ImmutableMap.of("n1", newUri);
+    ModelVersionChange change = ModelVersionChange.updateUri("n1", newUri);
     ModelVersion modelVersion = 
modelOperationDispatcher.getModelVersion(modelIdent, 0);
     ModelVersion alteredModelVersion =
         modelOperationDispatcher.alterModelVersion(modelIdent, 0, change);
 
-    Assertions.assertEquals(newUri, alteredModelVersion.uri());
+    Assertions.assertEquals(newVersionUris, alteredModelVersion.uris());
     Assertions.assertEquals(modelVersion.version(), 
alteredModelVersion.version());
     Assertions.assertEquals(modelVersion.aliases(), 
alteredModelVersion.aliases());
     Assertions.assertEquals(modelVersion.comment(), 
alteredModelVersion.comment());
@@ -810,9 +811,159 @@ public class TestModelOperationDispatcher extends 
TestOperationDispatcher {
     String modelName = randomModelName();
     String modelComment = "model which tests update";
     Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
-    String newUri = "s3://new-bucket/new-path/new-model.json";
 
-    Map<String, String> versionUris = 
ImmutableMap.of(ModelVersion.URI_NAME_UNKNOWN, "uri");
+    Map<String, String> versionUris = ImmutableMap.of("n1", "u1");
+    String[] versionAliases = {"alias1", "alias2"};
+    String versionComment = "version which tests update";
+
+    NameIdentifier schemaIdent = NameIdentifier.of(metalake, catalog, 
schemaName);
+    schemaOperationDispatcher.createSchema(schemaIdent, schemaComment, props);
+
+    NameIdentifier modelIdent =
+        NameIdentifierUtil.ofModel(metalake, catalog, schemaName, modelName);
+    modelOperationDispatcher.registerModel(modelIdent, modelComment, props);
+
+    modelOperationDispatcher.linkModelVersion(
+        modelIdent, versionUris, versionAliases, versionComment, props);
+
+    String newUri = "u2";
+    Map<String, String> newVersionUris = ImmutableMap.of("n1", newUri);
+    ModelVersionChange change = ModelVersionChange.updateUri("n1", newUri);
+    ModelVersion modelVersion =
+        modelOperationDispatcher.getModelVersion(modelIdent, 
versionAliases[0]);
+    ModelVersion alteredModelVersion =
+        modelOperationDispatcher.alterModelVersion(modelIdent, 
versionAliases[0], change);
+
+    Assertions.assertEquals(newVersionUris, alteredModelVersion.uris());
+    Assertions.assertEquals(modelVersion.version(), 
alteredModelVersion.version());
+    Assertions.assertEquals(modelVersion.aliases(), 
alteredModelVersion.aliases());
+    Assertions.assertEquals(modelVersion.comment(), 
alteredModelVersion.comment());
+    Assertions.assertEquals(modelVersion.properties(), 
alteredModelVersion.properties());
+  }
+
+  @Test
+  void testAddModelVersionUri() {
+    String schemaName = randomSchemaName();
+    String schemaComment = "schema which tests update";
+
+    String modelName = randomModelName();
+    String modelComment = "model which tests update";
+    Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
+
+    Map<String, String> versionUris = ImmutableMap.of("n1", "u1");
+    String[] versionAliases = {"alias1", "alias2"};
+    String versionComment = "version which tests update";
+
+    NameIdentifier schemaIdent = NameIdentifier.of(metalake, catalog, 
schemaName);
+    schemaOperationDispatcher.createSchema(schemaIdent, schemaComment, props);
+
+    NameIdentifier modelIdent =
+        NameIdentifierUtil.ofModel(metalake, catalog, schemaName, modelName);
+    modelOperationDispatcher.registerModel(modelIdent, modelComment, props);
+
+    modelOperationDispatcher.linkModelVersion(
+        modelIdent, versionUris, versionAliases, versionComment, props);
+
+    String newUriName = "n2";
+    String newUri = "u2";
+    Map<String, String> newVersionUris = ImmutableMap.of("n1", "u1", "n2", 
"u2");
+    ModelVersionChange change = ModelVersionChange.addUri(newUriName, newUri);
+    ModelVersion modelVersion = 
modelOperationDispatcher.getModelVersion(modelIdent, 0);
+    ModelVersion alteredModelVersion =
+        modelOperationDispatcher.alterModelVersion(modelIdent, 0, change);
+
+    Assertions.assertEquals(newVersionUris, alteredModelVersion.uris());
+    Assertions.assertEquals(modelVersion.version(), 
alteredModelVersion.version());
+    Assertions.assertEquals(modelVersion.aliases(), 
alteredModelVersion.aliases());
+    Assertions.assertEquals(modelVersion.comment(), 
alteredModelVersion.comment());
+    Assertions.assertEquals(modelVersion.properties(), 
alteredModelVersion.properties());
+  }
+
+  @Test
+  void testAddModelVersionUriByAlias() {
+    String schemaName = randomSchemaName();
+    String schemaComment = "schema which tests update";
+
+    String modelName = randomModelName();
+    String modelComment = "model which tests update";
+    Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
+
+    Map<String, String> versionUris = ImmutableMap.of("n1", "u1");
+    String[] versionAliases = {"alias1", "alias2"};
+    String versionComment = "version which tests update";
+
+    NameIdentifier schemaIdent = NameIdentifier.of(metalake, catalog, 
schemaName);
+    schemaOperationDispatcher.createSchema(schemaIdent, schemaComment, props);
+
+    NameIdentifier modelIdent =
+        NameIdentifierUtil.ofModel(metalake, catalog, schemaName, modelName);
+    modelOperationDispatcher.registerModel(modelIdent, modelComment, props);
+
+    modelOperationDispatcher.linkModelVersion(
+        modelIdent, versionUris, versionAliases, versionComment, props);
+
+    String newUriName = "n2";
+    String newUri = "u2";
+    Map<String, String> newVersionUris = ImmutableMap.of("n1", "u1", "n2", 
"u2");
+    ModelVersionChange change = ModelVersionChange.addUri(newUriName, newUri);
+    ModelVersion modelVersion =
+        modelOperationDispatcher.getModelVersion(modelIdent, 
versionAliases[0]);
+    ModelVersion alteredModelVersion =
+        modelOperationDispatcher.alterModelVersion(modelIdent, 
versionAliases[0], change);
+
+    Assertions.assertEquals(newVersionUris, alteredModelVersion.uris());
+    Assertions.assertEquals(modelVersion.version(), 
alteredModelVersion.version());
+    Assertions.assertEquals(modelVersion.aliases(), 
alteredModelVersion.aliases());
+    Assertions.assertEquals(modelVersion.comment(), 
alteredModelVersion.comment());
+    Assertions.assertEquals(modelVersion.properties(), 
alteredModelVersion.properties());
+  }
+
+  @Test
+  void testRemoveModelVersionUri() {
+    String schemaName = randomSchemaName();
+    String schemaComment = "schema which tests update";
+
+    String modelName = randomModelName();
+    String modelComment = "model which tests update";
+    Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
+
+    Map<String, String> versionUris = ImmutableMap.of("n1", "u1", "n2", "u2");
+    String[] versionAliases = {"alias1", "alias2"};
+    String versionComment = "version which tests update";
+
+    NameIdentifier schemaIdent = NameIdentifier.of(metalake, catalog, 
schemaName);
+    schemaOperationDispatcher.createSchema(schemaIdent, schemaComment, props);
+
+    NameIdentifier modelIdent =
+        NameIdentifierUtil.ofModel(metalake, catalog, schemaName, modelName);
+    modelOperationDispatcher.registerModel(modelIdent, modelComment, props);
+
+    modelOperationDispatcher.linkModelVersion(
+        modelIdent, versionUris, versionAliases, versionComment, props);
+
+    Map<String, String> newVersionUris = ImmutableMap.of("n2", "u2");
+    ModelVersionChange change = ModelVersionChange.removeUri("n1");
+    ModelVersion modelVersion = 
modelOperationDispatcher.getModelVersion(modelIdent, 0);
+    ModelVersion alteredModelVersion =
+        modelOperationDispatcher.alterModelVersion(modelIdent, 0, change);
+
+    Assertions.assertEquals(newVersionUris, alteredModelVersion.uris());
+    Assertions.assertEquals(modelVersion.version(), 
alteredModelVersion.version());
+    Assertions.assertEquals(modelVersion.aliases(), 
alteredModelVersion.aliases());
+    Assertions.assertEquals(modelVersion.comment(), 
alteredModelVersion.comment());
+    Assertions.assertEquals(modelVersion.properties(), 
alteredModelVersion.properties());
+  }
+
+  @Test
+  void testRemoveModelVersionUriByAlias() {
+    String schemaName = randomSchemaName();
+    String schemaComment = "schema which tests update";
+
+    String modelName = randomModelName();
+    String modelComment = "model which tests update";
+    Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
+
+    Map<String, String> versionUris = ImmutableMap.of("n1", "u1", "n2", "u2");
     String[] versionAliases = {"alias1", "alias2"};
     String versionComment = "version which tests update";
 
@@ -826,13 +977,14 @@ public class TestModelOperationDispatcher extends 
TestOperationDispatcher {
     modelOperationDispatcher.linkModelVersion(
         modelIdent, versionUris, versionAliases, versionComment, props);
 
-    ModelVersionChange change = ModelVersionChange.updateUri(newUri);
+    Map<String, String> newVersionUris = ImmutableMap.of("n2", "u2");
+    ModelVersionChange change = ModelVersionChange.removeUri("n1");
     ModelVersion modelVersion =
         modelOperationDispatcher.getModelVersion(modelIdent, 
versionAliases[0]);
     ModelVersion alteredModelVersion =
         modelOperationDispatcher.alterModelVersion(modelIdent, 
versionAliases[0], change);
 
-    Assertions.assertEquals(newUri, alteredModelVersion.uri());
+    Assertions.assertEquals(newVersionUris, alteredModelVersion.uris());
     Assertions.assertEquals(modelVersion.version(), 
alteredModelVersion.version());
     Assertions.assertEquals(modelVersion.aliases(), 
alteredModelVersion.aliases());
     Assertions.assertEquals(modelVersion.comment(), 
alteredModelVersion.comment());
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 61cdef1f1d..10b6bc123c 100644
--- 
a/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
+++ 
b/core/src/test/java/org/apache/gravitino/connector/TestCatalogOperations.java
@@ -1133,7 +1133,7 @@ public class TestCatalogOperations
     String newComment = testModelVersion.comment();
     int newVersion = testModelVersion.version();
     String[] newAliases = testModelVersion.aliases();
-    String newUri = testModelVersion.uri();
+    Map<String, String> newUris = Maps.newHashMap(testModelVersion.uris());
 
     for (ModelVersionChange change : changes) {
       if (change instanceof ModelVersionChange.UpdateComment) {
@@ -1162,20 +1162,32 @@ public class TestCatalogOperations
 
       } else if (change instanceof ModelVersionChange.UpdateUri) {
         ModelVersionChange.UpdateUri updateUriChange = 
(ModelVersionChange.UpdateUri) change;
-        newUri = updateUriChange.newUri();
+        newUris.replace(updateUriChange.uriName(), updateUriChange.newUri());
+
+      } else if (change instanceof ModelVersionChange.AddUri) {
+        ModelVersionChange.AddUri addUriChange = (ModelVersionChange.AddUri) 
change;
+        newUris.putIfAbsent(addUriChange.uriName(), addUriChange.uri());
+
+      } else if (change instanceof ModelVersionChange.RemoveUri) {
+        ModelVersionChange.RemoveUri removeUriChange = 
(ModelVersionChange.RemoveUri) change;
+        newUris.remove(removeUriChange.uriName());
 
       } else {
         throw new IllegalArgumentException("Unsupported model version change: 
" + change);
       }
     }
 
+    if (newUris.isEmpty()) {
+      throw new IllegalArgumentException("Model version URI cannot be empty");
+    }
+
     TestModelVersion updatedModelVersion =
         TestModelVersion.builder()
             .withVersion(newVersion)
             .withComment(newComment)
             .withProperties(newProps)
             .withAuditInfo(updatedAuditInfo)
-            .withUris(ImmutableMap.of(ModelVersion.URI_NAME_UNKNOWN, newUri))
+            .withUris(newUris)
             .withAliases(newAliases)
             .build();
 
diff --git 
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelVersionMetaService.java
 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelVersionMetaService.java
index b0e2c1a866..3d59e8591a 100644
--- 
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelVersionMetaService.java
+++ 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelVersionMetaService.java
@@ -753,10 +753,9 @@ public class TestModelVersionMetaService extends 
TestJDBCBackend {
     Map<String, String> properties = ImmutableMap.of("k1", "v1", "k2", "v2");
     String modelName = randomModelName();
     String modelComment = "model1 comment";
-    String modelVersionUri = "S3://test/path/to/model/version";
+    Map<String, String> modelVersionUris = ImmutableMap.of("n1", "u1");
     List<String> modelVersionAliases = ImmutableList.of("alias1", "alias2");
     String modelVersionComment = "test comment";
-    String updatedUri = "S3://test/path/to/new/model/version";
     int version = 0;
 
     ModelEntity modelEntity =
@@ -773,17 +772,18 @@ public class TestModelVersionMetaService extends 
TestJDBCBackend {
         createModelVersionEntity(
             modelEntity.nameIdentifier(),
             version,
-            ImmutableMap.of(ModelVersion.URI_NAME_UNKNOWN, modelVersionUri),
+            modelVersionUris,
             modelVersionAliases,
             modelVersionComment,
             properties,
             auditInfo);
 
+    Map<String, String> newModelVersionUris = ImmutableMap.of("n1", "u1-1", 
"n2", "u2");
     ModelVersionEntity updatedModelVersionEntity =
         createModelVersionEntity(
             modelVersionEntity.modelIdentifier(),
             modelVersionEntity.version(),
-            ImmutableMap.of(ModelVersion.URI_NAME_UNKNOWN, updatedUri),
+            newModelVersionUris,
             modelVersionEntity.aliases(),
             modelVersionEntity.comment(),
             modelVersionEntity.properties(),

Reply via email to