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

yuqi4733 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 ece06fada [#5950] feat(catalog-model): Add integration tests for model 
API (#6051)
ece06fada is described below

commit ece06fada7613dc115e6a8154e48da1f56df878c
Author: Jerry Shao <[email protected]>
AuthorDate: Thu Jan 2 16:49:17 2025 +0800

    [#5950] feat(catalog-model): Add integration tests for model API (#6051)
    
    ### What changes were proposed in this pull request?
    
    This PR adds the integration tests for model API to make sure it works
    as expected.
    
    ### Why are the changes needed?
    
    To have an end to end test.
    
    Fix: #5950
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Add ITs.
    
    ---------
    
    Co-authored-by: Qi Yu <[email protected]>
---
 catalogs/catalog-model/build.gradle.kts            |  14 +-
 .../integration/test/ModelCatalogOperationsIT.java | 364 +++++++++++++++++++
 .../src/test/resources/log4j2.properties           |  73 ++++
 .../gravitino/client/generic_model_catalog.py      |   2 +-
 .../gravitino/client/gravitino_metalake.py         |   4 +-
 .../tests/integration/integration_test_env.py      |  10 +-
 .../tests/integration/test_model_catalog.py        | 403 +++++++++++++++++++++
 .../ModelVersionAliasSQLProviderFactory.java       |   5 +-
 .../base/ModelVersionAliasRelBaseSQLProvider.java  |   9 +-
 .../h2/ModelVersionAliasRelH2SQLProvider.java      |  40 ++
 .../ModelVersionAliasRelPostgreSQLProvider.java    |   2 +-
 .../ModelVersionMetaPostgreSQLProvider.java        |   2 +-
 12 files changed, 909 insertions(+), 19 deletions(-)

diff --git a/catalogs/catalog-model/build.gradle.kts 
b/catalogs/catalog-model/build.gradle.kts
index 95af305fc..5c1255322 100644
--- a/catalogs/catalog-model/build.gradle.kts
+++ b/catalogs/catalog-model/build.gradle.kts
@@ -29,17 +29,15 @@ dependencies {
     exclude(group = "*")
   }
 
-  implementation(project(":core")) {
+  implementation(project(":catalogs:catalog-common")) {
     exclude(group = "*")
   }
   implementation(project(":common")) {
     exclude(group = "*")
   }
-
-  implementation(project(":catalogs:catalog-common")) {
+  implementation(project(":core")) {
     exclude(group = "*")
   }
-
   implementation(libs.guava)
   implementation(libs.slf4j.api)
 
@@ -47,14 +45,17 @@ dependencies {
   testImplementation(project(":integration-test-common", "testArtifacts"))
   testImplementation(project(":server"))
   testImplementation(project(":server-common"))
-
   testImplementation(libs.bundles.log4j)
   testImplementation(libs.commons.io)
   testImplementation(libs.commons.lang3)
   testImplementation(libs.mockito.core)
   testImplementation(libs.mockito.inline)
+  testImplementation(libs.mysql.driver)
   testImplementation(libs.junit.jupiter.api)
   testImplementation(libs.junit.jupiter.params)
+  testImplementation(libs.postgresql.driver)
+  testImplementation(libs.testcontainers)
+  testImplementation(libs.testcontainers.mysql)
 
   testRuntimeOnly(libs.junit.jupiter.engine)
 }
@@ -68,8 +69,9 @@ tasks {
   val copyCatalogLibs by registering(Copy::class) {
     dependsOn("jar", "runtimeJars")
     from("build/libs") {
-      exclude("slf4j-*.jar")
       exclude("guava-*.jar")
+      exclude("log4j-*.jar")
+      exclude("slf4j-*.jar")
     }
     into("$rootDir/distribution/package/catalogs/model/libs")
   }
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
new file mode 100644
index 000000000..6e7adac55
--- /dev/null
+++ 
b/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/integration/test/ModelCatalogOperationsIT.java
@@ -0,0 +1,364 @@
+/*
+ * 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.gravtitino.catalog.model.integration.test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.Schema;
+import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.exceptions.ModelAlreadyExistsException;
+import 
org.apache.gravitino.exceptions.ModelVersionAliasesAlreadyExistException;
+import org.apache.gravitino.exceptions.NoSuchModelException;
+import org.apache.gravitino.exceptions.NoSuchModelVersionException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.integration.test.util.BaseIT;
+import org.apache.gravitino.model.Model;
+import org.apache.gravitino.model.ModelVersion;
+import org.apache.gravitino.utils.RandomNameUtils;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class ModelCatalogOperationsIT extends BaseIT {
+
+  private final String metalakeName = 
RandomNameUtils.genRandomName("model_it_metalake");
+  private final String catalogName = 
RandomNameUtils.genRandomName("model_it_catalog");
+  private final String schemaName = 
RandomNameUtils.genRandomName("model_it_schema");
+
+  private GravitinoMetalake gravitinoMetalake;
+  private Catalog gravitinoCatalog;
+
+  @BeforeAll
+  public void setUp() {
+    createMetalake();
+    createCatalog();
+  }
+
+  @AfterAll
+  public void tearDown() {
+    gravitinoMetalake.dropCatalog(catalogName, true);
+    client.dropMetalake(metalakeName, true);
+  }
+
+  @BeforeEach
+  public void beforeEach() {
+    createSchema();
+  }
+
+  @AfterEach
+  public void afterEach() {
+    dropSchema();
+  }
+
+  @Test
+  public void testRegisterAndGetModel() {
+    String modelName = RandomNameUtils.genRandomName("model1");
+    NameIdentifier modelIdent = NameIdentifier.of(schemaName, modelName);
+    String comment = "comment";
+    Map<String, String> properties = ImmutableMap.of("key1", "val1", "key2", 
"val2");
+
+    Model model = gravitinoCatalog.asModelCatalog().registerModel(modelIdent, 
comment, properties);
+    Assertions.assertEquals(modelName, model.name());
+    Assertions.assertEquals(comment, model.comment());
+    Assertions.assertEquals(properties, model.properties());
+
+    Model loadModel = gravitinoCatalog.asModelCatalog().getModel(modelIdent);
+    Assertions.assertEquals(modelName, loadModel.name());
+    Assertions.assertEquals(comment, loadModel.comment());
+    Assertions.assertEquals(properties, loadModel.properties());
+
+    
Assertions.assertTrue(gravitinoCatalog.asModelCatalog().modelExists(modelIdent));
+
+    // Test register existing model
+    Assertions.assertThrows(
+        ModelAlreadyExistsException.class,
+        () -> gravitinoCatalog.asModelCatalog().registerModel(modelIdent, 
comment, properties));
+
+    // Test register model in a non-existent schema
+    NameIdentifier nonExistentSchemaIdent = 
NameIdentifier.of("non_existent_schema", modelName);
+    Assertions.assertThrows(
+        NoSuchSchemaException.class,
+        () ->
+            gravitinoCatalog
+                .asModelCatalog()
+                .registerModel(nonExistentSchemaIdent, comment, properties));
+
+    // Test get non-existent model
+    NameIdentifier nonExistentModelIdent = NameIdentifier.of(schemaName, 
"non_existent_model");
+    Assertions.assertThrows(
+        NoSuchModelException.class,
+        () -> 
gravitinoCatalog.asModelCatalog().getModel(nonExistentModelIdent));
+
+    // Test get model from non-existent schema
+    NameIdentifier nonExistentModelIdent2 = 
NameIdentifier.of("non_existent_schema", modelName);
+    Assertions.assertThrows(
+        NoSuchModelException.class,
+        () -> 
gravitinoCatalog.asModelCatalog().getModel(nonExistentModelIdent2));
+  }
+
+  @Test
+  public void testRegisterAndListModels() {
+    String modelName1 = RandomNameUtils.genRandomName("model1");
+    String modelName2 = RandomNameUtils.genRandomName("model2");
+    NameIdentifier modelIdent1 = NameIdentifier.of(schemaName, modelName1);
+    NameIdentifier modelIdent2 = NameIdentifier.of(schemaName, modelName2);
+
+    gravitinoCatalog.asModelCatalog().registerModel(modelIdent1, null, null);
+    gravitinoCatalog.asModelCatalog().registerModel(modelIdent2, null, null);
+
+    NameIdentifier[] models =
+        gravitinoCatalog.asModelCatalog().listModels(Namespace.of(schemaName));
+    Set<NameIdentifier> resultSet = Sets.newHashSet(models);
+
+    Assertions.assertEquals(2, resultSet.size());
+    Assertions.assertTrue(resultSet.contains(modelIdent1));
+    Assertions.assertTrue(resultSet.contains(modelIdent2));
+
+    // Test delete and list models
+    
Assertions.assertTrue(gravitinoCatalog.asModelCatalog().deleteModel(modelIdent1));
+    NameIdentifier[] modelsAfterDelete =
+        gravitinoCatalog.asModelCatalog().listModels(Namespace.of(schemaName));
+
+    Assertions.assertEquals(1, modelsAfterDelete.length);
+    Assertions.assertEquals(modelIdent2, modelsAfterDelete[0]);
+
+    
Assertions.assertTrue(gravitinoCatalog.asModelCatalog().deleteModel(modelIdent2));
+    NameIdentifier[] modelsAfterDeleteAll =
+        gravitinoCatalog.asModelCatalog().listModels(Namespace.of(schemaName));
+
+    Assertions.assertEquals(0, modelsAfterDeleteAll.length);
+
+    // Test list models from non-existent schema
+    Assertions.assertThrows(
+        NoSuchSchemaException.class,
+        () -> 
gravitinoCatalog.asModelCatalog().listModels(Namespace.of("non_existent_schema")));
+  }
+
+  @Test
+  public void testRegisterAndDeleteModel() {
+    String modelName = RandomNameUtils.genRandomName("model1");
+    NameIdentifier modelIdent = NameIdentifier.of(schemaName, modelName);
+    gravitinoCatalog.asModelCatalog().registerModel(modelIdent, null, null);
+
+    
Assertions.assertTrue(gravitinoCatalog.asModelCatalog().deleteModel(modelIdent));
+    
Assertions.assertFalse(gravitinoCatalog.asModelCatalog().modelExists(modelIdent));
+    
Assertions.assertFalse(gravitinoCatalog.asModelCatalog().deleteModel(modelIdent));
+
+    // Test delete non-existent model
+    NameIdentifier nonExistentModelIdent = NameIdentifier.of(schemaName, 
"non_existent_model");
+    
Assertions.assertFalse(gravitinoCatalog.asModelCatalog().deleteModel(nonExistentModelIdent));
+
+    // Test delete model from non-existent schema
+    NameIdentifier nonExistentSchemaIdent = 
NameIdentifier.of("non_existent_schema", modelName);
+    
Assertions.assertFalse(gravitinoCatalog.asModelCatalog().deleteModel(nonExistentSchemaIdent));
+  }
+
+  @Test
+  public void testLinkAndGerModelVersion() {
+    String modelName = RandomNameUtils.genRandomName("model1");
+    Map<String, String> properties = ImmutableMap.of("key1", "val1", "key2", 
"val2");
+    NameIdentifier modelIdent = NameIdentifier.of(schemaName, modelName);
+    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());
+    Assertions.assertTrue(
+        gravitinoCatalog.asModelCatalog().modelVersionExists(modelIdent, 
"alias1"));
+
+    ModelVersion modelVersion1 = 
gravitinoCatalog.asModelCatalog().getModelVersion(modelIdent, 0);
+
+    Assertions.assertEquals(0, modelVersion1.version());
+    Assertions.assertEquals("uri", modelVersion1.uri());
+    Assertions.assertArrayEquals(new String[] {"alias1"}, 
modelVersion1.aliases());
+    
Assertions.assertTrue(gravitinoCatalog.asModelCatalog().modelVersionExists(modelIdent,
 0));
+
+    // Test link a version to a non-existent model
+    NameIdentifier nonExistentModelIdent = NameIdentifier.of(schemaName, 
"non_existent_model");
+    Assertions.assertThrows(
+        NoSuchModelException.class,
+        () ->
+            gravitinoCatalog
+                .asModelCatalog()
+                .linkModelVersion(
+                    nonExistentModelIdent, "uri", new String[] {"alias1"}, 
"comment", properties));
+
+    // Test link a version using existing alias
+    Assertions.assertThrows(
+        ModelVersionAliasesAlreadyExistException.class,
+        () ->
+            gravitinoCatalog
+                .asModelCatalog()
+                .linkModelVersion(
+                    modelIdent, "uri", new String[] {"alias1"}, "comment", 
properties));
+
+    // Test get non-existent model version
+    Assertions.assertThrows(
+        NoSuchModelVersionException.class,
+        () -> gravitinoCatalog.asModelCatalog().getModelVersion(modelIdent, 
"non_existent_alias"));
+    Assertions.assertFalse(
+        gravitinoCatalog.asModelCatalog().modelVersionExists(modelIdent, 
"non_existent_alias"));
+
+    Assertions.assertThrows(
+        NoSuchModelVersionException.class,
+        () -> gravitinoCatalog.asModelCatalog().getModelVersion(modelIdent, 
1));
+    
Assertions.assertFalse(gravitinoCatalog.asModelCatalog().modelVersionExists(modelIdent,
 1));
+  }
+
+  @Test
+  public void testLinkAndDeleteModelVersions() {
+    String modelName = RandomNameUtils.genRandomName("model1");
+    NameIdentifier modelIdent = NameIdentifier.of(schemaName, modelName);
+    gravitinoCatalog.asModelCatalog().registerModel(modelIdent, null, null);
+
+    gravitinoCatalog
+        .asModelCatalog()
+        .linkModelVersion(modelIdent, "uri1", new String[] {"alias1"}, 
"comment1", null);
+    gravitinoCatalog
+        .asModelCatalog()
+        .linkModelVersion(modelIdent, "uri2", new String[] {"alias2"}, 
"comment2", null);
+
+    Assertions.assertTrue(
+        gravitinoCatalog.asModelCatalog().deleteModelVersion(modelIdent, 
"alias1"));
+    Assertions.assertFalse(
+        gravitinoCatalog.asModelCatalog().modelVersionExists(modelIdent, 
"alias1"));
+    
Assertions.assertFalse(gravitinoCatalog.asModelCatalog().deleteModelVersion(modelIdent,
 0));
+
+    
Assertions.assertTrue(gravitinoCatalog.asModelCatalog().deleteModelVersion(modelIdent,
 1));
+    Assertions.assertFalse(
+        gravitinoCatalog.asModelCatalog().modelVersionExists(modelIdent, 
"alias2"));
+    
Assertions.assertFalse(gravitinoCatalog.asModelCatalog().deleteModelVersion(modelIdent,
 1));
+
+    // Test delete non-existent model version
+    Assertions.assertFalse(
+        gravitinoCatalog.asModelCatalog().deleteModelVersion(modelIdent, 
"non_existent_alias"));
+
+    // Test delete model version of non-existent model
+    NameIdentifier nonExistentModelIdent = NameIdentifier.of(schemaName, 
"non_existent_model");
+    Assertions.assertFalse(
+        
gravitinoCatalog.asModelCatalog().deleteModelVersion(nonExistentModelIdent, 
"alias1"));
+
+    // Test delete model version of non-existent schema
+    NameIdentifier nonExistentSchemaIdent = 
NameIdentifier.of("non_existent_schema", modelName);
+    Assertions.assertFalse(
+        
gravitinoCatalog.asModelCatalog().deleteModelVersion(nonExistentSchemaIdent, 
"alias1"));
+  }
+
+  @Test
+  public void testLinkAndListModelVersions() {
+    String modelName = RandomNameUtils.genRandomName("model1");
+    NameIdentifier modelIdent = NameIdentifier.of(schemaName, modelName);
+    gravitinoCatalog.asModelCatalog().registerModel(modelIdent, null, null);
+
+    gravitinoCatalog
+        .asModelCatalog()
+        .linkModelVersion(modelIdent, "uri1", new String[] {"alias1"}, 
"comment1", null);
+    gravitinoCatalog
+        .asModelCatalog()
+        .linkModelVersion(modelIdent, "uri2", new String[] {"alias2"}, 
"comment2", null);
+
+    int[] modelVersions = 
gravitinoCatalog.asModelCatalog().listModelVersions(modelIdent);
+    Set<Integer> resultSet = 
Arrays.stream(modelVersions).boxed().collect(Collectors.toSet());
+
+    Assertions.assertEquals(2, resultSet.size());
+    Assertions.assertTrue(resultSet.contains(0));
+    Assertions.assertTrue(resultSet.contains(1));
+
+    // Test list model versions of non-existent model
+    NameIdentifier nonExistentModelIdent = NameIdentifier.of(schemaName, 
"non_existent_model");
+    Assertions.assertThrows(
+        NoSuchModelException.class,
+        () -> 
gravitinoCatalog.asModelCatalog().listModelVersions(nonExistentModelIdent));
+
+    // Test list model versions of non-existent schema
+    NameIdentifier nonExistentSchemaIdent = 
NameIdentifier.of("non_existent_schema", modelName);
+    Assertions.assertThrows(
+        NoSuchModelException.class,
+        () -> 
gravitinoCatalog.asModelCatalog().listModelVersions(nonExistentSchemaIdent));
+
+    // Test delete and list model versions
+    
Assertions.assertTrue(gravitinoCatalog.asModelCatalog().deleteModelVersion(modelIdent,
 1));
+    int[] modelVersionsAfterDelete =
+        gravitinoCatalog.asModelCatalog().listModelVersions(modelIdent);
+
+    Assertions.assertEquals(1, modelVersionsAfterDelete.length);
+    Assertions.assertEquals(0, modelVersionsAfterDelete[0]);
+
+    
Assertions.assertTrue(gravitinoCatalog.asModelCatalog().deleteModelVersion(modelIdent,
 0));
+    int[] modelVersionsAfterDeleteAll =
+        gravitinoCatalog.asModelCatalog().listModelVersions(modelIdent);
+
+    Assertions.assertEquals(0, modelVersionsAfterDeleteAll.length);
+  }
+
+  private void createMetalake() {
+    GravitinoMetalake[] gravitinoMetalakes = client.listMetalakes();
+    Assertions.assertEquals(0, gravitinoMetalakes.length);
+
+    client.createMetalake(metalakeName, "comment", Collections.emptyMap());
+    GravitinoMetalake loadMetalake = client.loadMetalake(metalakeName);
+    Assertions.assertEquals(metalakeName, loadMetalake.name());
+
+    gravitinoMetalake = loadMetalake;
+  }
+
+  private void createCatalog() {
+    gravitinoMetalake.createCatalog(catalogName, Catalog.Type.MODEL, 
"comment", ImmutableMap.of());
+    gravitinoCatalog = gravitinoMetalake.loadCatalog(catalogName);
+  }
+
+  private void createSchema() {
+    Map<String, String> properties = Maps.newHashMap();
+    properties.put("key1", "val1");
+    properties.put("key2", "val2");
+    String comment = "comment";
+
+    gravitinoCatalog.asSchemas().createSchema(schemaName, comment, properties);
+    Schema loadSchema = gravitinoCatalog.asSchemas().loadSchema(schemaName);
+    Assertions.assertEquals(schemaName, loadSchema.name());
+    Assertions.assertEquals(comment, loadSchema.comment());
+    Assertions.assertEquals("val1", loadSchema.properties().get("key1"));
+    Assertions.assertEquals("val2", loadSchema.properties().get("key2"));
+  }
+
+  private void dropSchema() {
+    gravitinoCatalog.asSchemas().dropSchema(schemaName, true);
+  }
+}
diff --git a/catalogs/catalog-model/src/test/resources/log4j2.properties 
b/catalogs/catalog-model/src/test/resources/log4j2.properties
new file mode 100644
index 000000000..88da637c1
--- /dev/null
+++ b/catalogs/catalog-model/src/test/resources/log4j2.properties
@@ -0,0 +1,73 @@
+#
+# 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.
+#
+
+# Set to debug or trace if log4j initialization is failing
+status = info
+
+# Name of the configuration
+name = ConsoleLogConfig
+
+# Console appender configuration
+appender.console.type = Console
+appender.console.name = consoleLogger
+appender.console.layout.type = PatternLayout
+appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p [%t] %c{1}:%L - 
%m%n
+
+# Log files location
+property.logPath = 
${sys:gravitino.log.path:-build/catalog-model-integration-test.log}
+
+# File appender configuration
+appender.file.type = File
+appender.file.name = fileLogger
+appender.file.fileName = ${logPath}
+appender.file.layout.type = PatternLayout
+appender.file.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %c - %m%n
+
+# Root logger level
+rootLogger.level = info
+
+# Root logger referring to console and file appenders
+rootLogger.appenderRef.stdout.ref = consoleLogger
+rootLogger.appenderRef.file.ref = fileLogger
+
+# File appender configuration for testcontainers
+appender.testcontainersFile.type = File
+appender.testcontainersFile.name = testcontainersLogger
+appender.testcontainersFile.fileName = build/testcontainers.log
+appender.testcontainersFile.layout.type = PatternLayout
+appender.testcontainersFile.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] 
%-5p %c - %m%n
+
+# Logger for testcontainers
+logger.testcontainers.name = org.testcontainers
+logger.testcontainers.level = debug
+logger.testcontainers.additivity = false
+logger.testcontainers.appenderRef.file.ref = testcontainersLogger
+
+logger.tc.name = tc
+logger.tc.level = debug
+logger.tc.additivity = false
+logger.tc.appenderRef.file.ref = testcontainersLogger
+
+logger.docker.name = com.github.dockerjava
+logger.docker.level = warn
+logger.docker.additivity = false
+logger.docker.appenderRef.file.ref = testcontainersLogger
+
+logger.http.name = 
com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.wire
+logger.http.level = off
diff --git a/clients/client-python/gravitino/client/generic_model_catalog.py 
b/clients/client-python/gravitino/client/generic_model_catalog.py
index 6077b0b64..ca6b5cd31 100644
--- a/clients/client-python/gravitino/client/generic_model_catalog.py
+++ b/clients/client-python/gravitino/client/generic_model_catalog.py
@@ -199,7 +199,7 @@ class GenericModelCatalog(BaseSchemaCatalog):
 
         model_full_ident = self._model_full_identifier(model_ident)
         resp = self.rest_client.get(
-            self._format_model_version_request_path(model_full_ident),
+            
f"{self._format_model_version_request_path(model_full_ident)}/versions",
             error_handler=MODEL_ERROR_HANDLER,
         )
         model_version_list_resp = ModelVersionListResponse.from_json(
diff --git a/clients/client-python/gravitino/client/gravitino_metalake.py 
b/clients/client-python/gravitino/client/gravitino_metalake.py
index 0f72bfc07..be502aae7 100644
--- a/clients/client-python/gravitino/client/gravitino_metalake.py
+++ b/clients/client-python/gravitino/client/gravitino_metalake.py
@@ -128,7 +128,9 @@ class GravitinoMetalake(MetalakeDTO):
         Args:
             name: The name of the catalog.
             catalog_type: The type of the catalog.
-            provider: The provider of the catalog.
+            provider: The provider of the catalog. This parameter can be None 
if the catalog
+            provides a managed implementation. Currently, only the model 
catalog supports None
+            provider. For the details, please refer to the Catalog.Type.
             comment: The comment of the catalog.
             properties: The properties of the catalog.
 
diff --git a/clients/client-python/tests/integration/integration_test_env.py 
b/clients/client-python/tests/integration/integration_test_env.py
index 9344ff93a..308303e8a 100644
--- a/clients/client-python/tests/integration/integration_test_env.py
+++ b/clients/client-python/tests/integration/integration_test_env.py
@@ -67,7 +67,10 @@ class IntegrationTestEnv(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        if os.environ.get("START_EXTERNAL_GRAVITINO") is not None:
+        if (
+            os.environ.get("START_EXTERNAL_GRAVITINO") is not None
+            and os.environ.get("START_EXTERNAL_GRAVITINO").lower() == "true"
+        ):
             # Maybe Gravitino server already startup by Gradle test command or 
developer manual startup.
             if not check_gravitino_server_status():
                 logger.error("ERROR: Can't find online Gravitino server!")
@@ -112,7 +115,10 @@ class IntegrationTestEnv(unittest.TestCase):
 
     @classmethod
     def tearDownClass(cls):
-        if os.environ.get("START_EXTERNAL_GRAVITINO") is not None:
+        if (
+            os.environ.get("START_EXTERNAL_GRAVITINO") is not None
+            and os.environ.get("START_EXTERNAL_GRAVITINO").lower() == "true"
+        ):
             return
 
         logger.info("Stop integration test environment...")
diff --git a/clients/client-python/tests/integration/test_model_catalog.py 
b/clients/client-python/tests/integration/test_model_catalog.py
new file mode 100644
index 000000000..35ebfdc47
--- /dev/null
+++ b/clients/client-python/tests/integration/test_model_catalog.py
@@ -0,0 +1,403 @@
+# 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 random import randint
+
+from gravitino import GravitinoAdminClient, GravitinoClient, Catalog, 
NameIdentifier
+from gravitino.exceptions.base import (
+    ModelAlreadyExistsException,
+    NoSuchSchemaException,
+    NoSuchModelException,
+    ModelVersionAliasesAlreadyExistException,
+    NoSuchModelVersionException,
+)
+from gravitino.namespace import Namespace
+from tests.integration.integration_test_env import IntegrationTestEnv
+
+
+class TestModelCatalog(IntegrationTestEnv):
+
+    _metalake_name: str = "model_it_metalake" + str(randint(0, 1000))
+    _catalog_name: str = "model_it_catalog" + str(randint(0, 1000))
+    _schema_name: str = "model_it_schema" + str(randint(0, 1000))
+
+    _gravitino_admin_client: GravitinoAdminClient = None
+    _gravitino_client: GravitinoClient = None
+    _catalog: Catalog = None
+
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+
+        cls._gravitino_admin_client = 
GravitinoAdminClient(uri="http://localhost:8090";)
+        cls._gravitino_admin_client.create_metalake(
+            cls._metalake_name, comment="comment", properties={}
+        )
+
+        cls._gravitino_client = GravitinoClient(
+            uri="http://localhost:8090";, metalake_name=cls._metalake_name
+        )
+        cls._catalog = cls._gravitino_client.create_catalog(
+            name=cls._catalog_name,
+            catalog_type=Catalog.Type.MODEL,
+            provider=None,
+            comment="comment",
+            properties={},
+        )
+
+    @classmethod
+    def tearDownClass(cls):
+        cls._gravitino_client.drop_catalog(name=cls._catalog_name, force=True)
+        cls._gravitino_admin_client.drop_metalake(name=cls._metalake_name, 
force=True)
+
+        super().tearDownClass()
+
+    def setUp(self):
+        self._catalog.as_schemas().create_schema(self._schema_name, "comment", 
{})
+
+    def tearDown(self):
+        self._catalog.as_schemas().drop_schema(self._schema_name, True)
+
+    def test_register_get_model(self):
+        model_name = "model_it_model" + str(randint(0, 1000))
+        model_ident = NameIdentifier.of(self._schema_name, model_name)
+        comment = "comment"
+        properties = {"k1": "v1", "k2": "v2"}
+
+        model = self._catalog.as_model_catalog().register_model(
+            model_ident, comment, properties
+        )
+        self.assertEqual(model_name, model.name())
+        self.assertEqual(comment, model.comment())
+        self.assertEqual(0, model.latest_version())
+        self.assertEqual(properties, model.properties())
+
+        # Test register model without comment and properties
+        model = self._catalog.as_model_catalog().register_model(
+            NameIdentifier.of(
+                self._schema_name, model_name + "_no_comment_no_properties"
+            ),
+            comment=None,
+            properties=None,
+        )
+        self.assertEqual(model_name + "_no_comment_no_properties", 
model.name())
+        self.assertIsNone(model.comment())
+        self.assertEqual(0, model.latest_version())
+        self.assertEqual({}, model.properties())
+
+        ## Test register same name model again
+        with self.assertRaises(ModelAlreadyExistsException):
+            self._catalog.as_model_catalog().register_model(
+                model_ident, comment, properties
+            )
+
+        # Test register model in a non-existent schema
+        with self.assertRaises(NoSuchSchemaException):
+            self._catalog.as_model_catalog().register_model(
+                NameIdentifier.of("non_existent_schema", model_name),
+                comment,
+                properties,
+            )
+
+        # Test get model
+        model = self._catalog.as_model_catalog().get_model(model_ident)
+        self.assertEqual(model_name, model.name())
+        self.assertEqual(comment, model.comment())
+        self.assertEqual(0, model.latest_version())
+        self.assertEqual(properties, model.properties())
+
+        # Test get non-existent model
+        with self.assertRaises(NoSuchModelException):
+            self._catalog.as_model_catalog().get_model(
+                NameIdentifier.of(self._schema_name, "non_existent_model")
+            )
+
+        # Test get a model for non-existent schema
+        with self.assertRaises(NoSuchModelException):
+            self._catalog.as_model_catalog().get_model(
+                NameIdentifier.of("non_existent_schema", model_name)
+            )
+
+    def test_register_list_models(self):
+
+        model_name1 = "model_it_model1" + str(randint(0, 1000))
+        model_name2 = "model_it_model2" + str(randint(0, 1000))
+        model_ident1 = NameIdentifier.of(self._schema_name, model_name1)
+        model_ident2 = NameIdentifier.of(self._schema_name, model_name2)
+        comment = "comment"
+        properties = {"k1": "v1", "k2": "v2"}
+
+        self._catalog.as_model_catalog().register_model(
+            model_ident1, comment, properties
+        )
+        self._catalog.as_model_catalog().register_model(
+            model_ident2, comment, properties
+        )
+
+        models = self._catalog.as_model_catalog().list_models(
+            Namespace.of(self._schema_name)
+        )
+        self.assertEqual(2, len(models))
+        self.assertTrue(model_ident1 in models)
+        self.assertTrue(model_ident2 in models)
+
+        # Test delete and list models
+        
self.assertTrue(self._catalog.as_model_catalog().delete_model(model_ident1))
+        models = self._catalog.as_model_catalog().list_models(
+            Namespace.of(self._schema_name)
+        )
+        self.assertEqual(1, len(models))
+        self.assertTrue(model_ident2 in models)
+
+        
self.assertTrue(self._catalog.as_model_catalog().delete_model(model_ident2))
+        models = self._catalog.as_model_catalog().list_models(
+            Namespace.of(self._schema_name)
+        )
+        self.assertEqual(0, len(models))
+
+        # Test list models for non-existent schema
+        with self.assertRaises(NoSuchSchemaException):
+            self._catalog.as_model_catalog().list_models(
+                Namespace.of("non_existent_schema")
+            )
+
+    def test_register_delete_model(self):
+        model_name = "model_it_model" + str(randint(0, 1000))
+        model_ident = NameIdentifier.of(self._schema_name, model_name)
+        comment = "comment"
+        properties = {"k1": "v1", "k2": "v2"}
+
+        self._catalog.as_model_catalog().register_model(
+            model_ident, comment, properties
+        )
+        
self.assertTrue(self._catalog.as_model_catalog().delete_model(model_ident))
+        # delete again will return False
+        
self.assertFalse(self._catalog.as_model_catalog().delete_model(model_ident))
+
+        # Test delete model in non-existent schema
+        self.assertFalse(
+            self._catalog.as_model_catalog().delete_model(
+                NameIdentifier.of("non_existent_schema", model_name)
+            )
+        )
+
+        # Test delete non-existent model
+        self.assertFalse(
+            self._catalog.as_model_catalog().delete_model(
+                NameIdentifier.of(self._schema_name, "non_existent_model")
+            )
+        )
+
+    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)
+        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"},
+        )
+
+        # Test link model version to a non-existent model
+        with self.assertRaises(NoSuchModelException):
+            self._catalog.as_model_catalog().link_model_version(
+                NameIdentifier.of(self._schema_name, "non_existent_model"),
+                uri="uri",
+                aliases=["alias1", "alias2"],
+                comment="comment",
+                properties={"k1": "v1", "k2": "v2"},
+            )
+
+        # Test link model version with existing aliases
+        with self.assertRaises(ModelVersionAliasesAlreadyExistException):
+            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())
+
+        # Test get model version from non-existent model
+        with self.assertRaises(NoSuchModelVersionException):
+            self._catalog.as_model_catalog().get_model_version(
+                NameIdentifier.of(self._schema_name, "non_existent_model"), 0
+            )
+
+        with self.assertRaises(NoSuchModelVersionException):
+            self._catalog.as_model_catalog().get_model_version_by_alias(
+                NameIdentifier.of(self._schema_name, "non_existent_model"), 
"alias1"
+            )
+
+        # Test get non-existent model version
+        with self.assertRaises(NoSuchModelVersionException):
+            self._catalog.as_model_catalog().get_model_version(model_ident, 1)
+
+        with self.assertRaises(NoSuchModelVersionException):
+            self._catalog.as_model_catalog().get_model_version_by_alias(
+                model_ident, "non_existent_alias"
+            )
+
+        # Test link model version with None aliases, comment and properties
+        self._catalog.as_model_catalog().link_model_version(
+            model_ident, uri="uri", aliases=None, comment=None, properties=None
+        )
+        model_version = self._catalog.as_model_catalog().get_model_version(
+            model_ident, 1
+        )
+        self.assertEqual(1, model_version.version())
+        self.assertEqual("uri", model_version.uri())
+        self.assertEqual([], model_version.aliases())
+        self.assertIsNone(model_version.comment())
+        self.assertEqual({}, model_version.properties())
+
+    def test_link_list_model_versions(self):
+        model_name = "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 versions
+        self._catalog.as_model_catalog().link_model_version(
+            model_ident,
+            uri="uri1",
+            aliases=["alias1", "alias2"],
+            comment="comment",
+            properties={"k1": "v1", "k2": "v2"},
+        )
+
+        self._catalog.as_model_catalog().link_model_version(
+            model_ident,
+            uri="uri2",
+            aliases=["alias3", "alias4"],
+            comment="comment",
+            properties={"k1": "v1", "k2": "v2"},
+        )
+
+        model_versions = self._catalog.as_model_catalog().list_model_versions(
+            model_ident
+        )
+        self.assertEqual(2, len(model_versions))
+        self.assertTrue(0 in model_versions)
+        self.assertTrue(1 in model_versions)
+
+        # Test delete model version
+        self.assertTrue(
+            self._catalog.as_model_catalog().delete_model_version(model_ident, 
0)
+        )
+        model_versions = self._catalog.as_model_catalog().list_model_versions(
+            model_ident
+        )
+        self.assertEqual(1, len(model_versions))
+        self.assertTrue(1 in model_versions)
+
+        self.assertTrue(
+            self._catalog.as_model_catalog().delete_model_version(model_ident, 
1)
+        )
+        model_versions = self._catalog.as_model_catalog().list_model_versions(
+            model_ident
+        )
+        self.assertEqual(0, len(model_versions))
+
+        # Test list model versions for non-existent model
+        with self.assertRaises(NoSuchModelException):
+            self._catalog.as_model_catalog().list_model_versions(
+                NameIdentifier.of(self._schema_name, "non_existent_model")
+            )
+
+    def test_link_delete_model_version(self):
+        model_name = "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", {})
+
+        self._catalog.as_model_catalog().link_model_version(
+            model_ident,
+            uri="uri",
+            aliases=["alias1"],
+            comment="comment",
+            properties={"k1": "v1", "k2": "v2"},
+        )
+
+        self.assertTrue(
+            self._catalog.as_model_catalog().delete_model_version(model_ident, 
0)
+        )
+        self.assertFalse(
+            self._catalog.as_model_catalog().delete_model_version(model_ident, 
0)
+        )
+        self.assertFalse(
+            self._catalog.as_model_catalog().delete_model_version_by_alias(
+                model_ident, "alias1"
+            )
+        )
+
+        self._catalog.as_model_catalog().link_model_version(
+            model_ident,
+            uri="uri",
+            aliases=["alias2"],
+            comment="comment",
+            properties={"k1": "v1", "k2": "v2"},
+        )
+
+        self.assertTrue(
+            self._catalog.as_model_catalog().delete_model_version_by_alias(
+                model_ident, "alias2"
+            )
+        )
+        self.assertFalse(
+            self._catalog.as_model_catalog().delete_model_version_by_alias(
+                model_ident, "alias2"
+            )
+        )
+        self.assertFalse(
+            self._catalog.as_model_catalog().delete_model_version(model_ident, 
1)
+        )
+
+        # Test delete model version for non-existent model
+        self.assertFalse(
+            self._catalog.as_model_catalog().delete_model_version(
+                NameIdentifier.of(self._schema_name, "non_existent_model"), 0
+            )
+        )
+
+        self.assertFalse(
+            self._catalog.as_model_catalog().delete_model_version_by_alias(
+                NameIdentifier.of(self._schema_name, "non_existent_model"), 
"alias1"
+            )
+        )
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionAliasSQLProviderFactory.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionAliasSQLProviderFactory.java
index 726e3d0e2..c83e9deaa 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionAliasSQLProviderFactory.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/ModelVersionAliasSQLProviderFactory.java
@@ -23,6 +23,7 @@ import java.util.List;
 import java.util.Map;
 import org.apache.gravitino.storage.relational.JDBCBackend.JDBCBackendType;
 import 
org.apache.gravitino.storage.relational.mapper.provider.base.ModelVersionAliasRelBaseSQLProvider;
+import 
org.apache.gravitino.storage.relational.mapper.provider.h2.ModelVersionAliasRelH2SQLProvider;
 import 
org.apache.gravitino.storage.relational.mapper.provider.postgresql.ModelVersionAliasRelPostgreSQLProvider;
 import org.apache.gravitino.storage.relational.po.ModelVersionAliasRelPO;
 import org.apache.gravitino.storage.relational.session.SqlSessionFactoryHelper;
@@ -32,13 +33,11 @@ public class ModelVersionAliasSQLProviderFactory {
 
   static class ModelVersionAliasRelMySQLProvider extends 
ModelVersionAliasRelBaseSQLProvider {}
 
-  static class ModelVersionAliasRelH2Provider extends 
ModelVersionAliasRelBaseSQLProvider {}
-
   private static final Map<JDBCBackendType, 
ModelVersionAliasRelBaseSQLProvider>
       MODEL_VERSION_META_SQL_PROVIDER_MAP =
           ImmutableMap.of(
               JDBCBackendType.MYSQL, new ModelVersionAliasRelMySQLProvider(),
-              JDBCBackendType.H2, new ModelVersionAliasRelH2Provider(),
+              JDBCBackendType.H2, new ModelVersionAliasRelH2SQLProvider(),
               JDBCBackendType.POSTGRESQL, new 
ModelVersionAliasRelPostgreSQLProvider());
 
   public static ModelVersionAliasRelBaseSQLProvider getProvider() {
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelVersionAliasRelBaseSQLProvider.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelVersionAliasRelBaseSQLProvider.java
index 5354b888f..abaaa5a8a 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelVersionAliasRelBaseSQLProvider.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelVersionAliasRelBaseSQLProvider.java
@@ -100,13 +100,14 @@ public class ModelVersionAliasRelBaseSQLProvider {
       @Param("modelId") Long modelId, @Param("alias") String alias) {
     return "UPDATE "
         + ModelVersionAliasRelMapper.TABLE_NAME
-        + " SET deleted_at = (UNIX_TIMESTAMP() * 1000.0)"
-        + " + EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP(3)) / 1000"
-        + " WHERE model_id = #{modelId} AND model_version = ("
+        + " mvar JOIN ("
         + " SELECT model_version FROM "
         + ModelVersionAliasRelMapper.TABLE_NAME
         + " WHERE model_id = #{modelId} AND model_version_alias = #{alias} AND 
deleted_at = 0)"
-        + " AND deleted_at = 0";
+        + " subquery ON mvar.model_version = subquery.model_version"
+        + " SET mvar.deleted_at = (UNIX_TIMESTAMP() * 1000.0)"
+        + " + EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP(3)) / 1000"
+        + " WHERE mvar.model_id = #{modelId} AND mvar.deleted_at = 0";
   }
 
   public String softDeleteModelVersionAliasRelsBySchemaId(@Param("schemaId") 
Long schemaId) {
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/ModelVersionAliasRelH2SQLProvider.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/ModelVersionAliasRelH2SQLProvider.java
new file mode 100644
index 000000000..a9ddc01c1
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/ModelVersionAliasRelH2SQLProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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.storage.relational.mapper.provider.h2;
+
+import 
org.apache.gravitino.storage.relational.mapper.ModelVersionAliasRelMapper;
+import 
org.apache.gravitino.storage.relational.mapper.provider.base.ModelVersionAliasRelBaseSQLProvider;
+import org.apache.ibatis.annotations.Param;
+
+public class ModelVersionAliasRelH2SQLProvider extends 
ModelVersionAliasRelBaseSQLProvider {
+
+  @Override
+  public String softDeleteModelVersionAliasRelsByModelIdAndAlias(
+      @Param("modelId") Long modelId, @Param("alias") String alias) {
+    return "UPDATE "
+        + ModelVersionAliasRelMapper.TABLE_NAME
+        + " SET deleted_at = (UNIX_TIMESTAMP() * 1000.0)"
+        + " + EXTRACT(MICROSECOND FROM CURRENT_TIMESTAMP(3)) / 1000"
+        + " WHERE model_id = #{modelId} AND model_version = ("
+        + " SELECT model_version FROM "
+        + ModelVersionAliasRelMapper.TABLE_NAME
+        + " WHERE model_id = #{modelId} AND model_version_alias = #{alias} AND 
deleted_at = 0)"
+        + " AND deleted_at = 0";
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelVersionAliasRelPostgreSQLProvider.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelVersionAliasRelPostgreSQLProvider.java
index a37f05312..da23bdca2 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelVersionAliasRelPostgreSQLProvider.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelVersionAliasRelPostgreSQLProvider.java
@@ -46,7 +46,7 @@ public class ModelVersionAliasRelPostgreSQLProvider extends 
ModelVersionAliasRel
         + ModelVersionAliasRelMapper.TABLE_NAME
         + " SET deleted_at = floor(extract(epoch from((current_timestamp -"
         + " timestamp '1970-01-01 00:00:00')*1000)))"
-        + " WHERE model_id = #{modelId} AND model_version = #{version} AND 
deleted_at = 0";
+        + " WHERE model_id = #{modelId} AND model_version = #{modelVersion} 
AND deleted_at = 0";
   }
 
   @Override
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelVersionMetaPostgreSQLProvider.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelVersionMetaPostgreSQLProvider.java
index 09be14319..4183a5361 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelVersionMetaPostgreSQLProvider.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelVersionMetaPostgreSQLProvider.java
@@ -47,7 +47,7 @@ public class ModelVersionMetaPostgreSQLProvider extends 
ModelVersionMetaBaseSQLP
         + ModelVersionMetaMapper.TABLE_NAME
         + " SET deleted_at = floor(extract(epoch from((current_timestamp -"
         + " timestamp '1970-01-01 00:00:00')*1000)))"
-        + " WHERE model_id = #{modelId} AND version = #{version} AND 
deleted_at = 0";
+        + " WHERE model_id = #{modelId} AND version = #{modelVersion} AND 
deleted_at = 0";
   }
 
   @Override

Reply via email to