This is an automated email from the ASF dual-hosted git repository.
yuqi4733 pushed a commit to branch branch-1.1
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.1 by this push:
new 7709d85883 [Cherry-pick to branch-1.1] [#9727] fix(model): fix
UnsupportedOperationException when updating aliases for model version created
without aliases (#10071) (#10095)
7709d85883 is described below
commit 7709d85883093ed25f057275e26f0bd7a30a1ca9
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Mar 2 12:46:10 2026 +0800
[Cherry-pick to branch-1.1] [#9727] fix(model): fix
UnsupportedOperationException when updating aliases for model version created
without aliases (#10071) (#10095)
**Cherry-pick Information:**
- Original commit: 8bd045d3a29a60451a92a7c129d5dd5456588d43
- Target branch: `branch-1.1`
- Status: ⚠️ **Has conflicts - manual resolution required**
Please review and resolve the conflicts before merging.
---------
Co-authored-by: Jerry Shao <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Qi Yu <[email protected]>
Co-authored-by: Jerry Shao <[email protected]>
---
.../integration/test/ModelCatalogOperationsIT.java | 42 ++++++++++++++++
.../tests/integration/test_model_catalog.py | 44 +++++++++++++++++
.../service/ModelVersionMetaService.java | 3 +-
.../storage/relational/utils/POConverters.java | 37 +++++++-------
.../service/TestModelVersionMetaService.java | 57 ++++++++++++++++++++++
.../storage/relational/utils/TestPOConverters.java | 48 ++++++++++++++++++
6 files changed, 211 insertions(+), 20 deletions(-)
diff --git
a/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/integration/test/ModelCatalogOperationsIT.java
b/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/integration/test/ModelCatalogOperationsIT.java
index f16cec8e9c..9a53e3cdc8 100644
---
a/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/integration/test/ModelCatalogOperationsIT.java
+++
b/catalogs/catalog-model/src/test/java/org/apache/gravtitino/catalog/model/integration/test/ModelCatalogOperationsIT.java
@@ -1072,6 +1072,48 @@ public class ModelCatalogOperationsIT extends BaseIT {
Assertions.assertEquals(modelVersion.properties(),
reloadedModelVersion.properties());
}
+ @Test
+ void testLinkAndUpdateModelVersionAliasesFromEmpty() {
+ // Regression test for https://github.com/apache/gravitino/issues/9727:
+ // updating aliases on a model version that was linked without any aliases
must not throw.
+ String modelName = RandomNameUtils.genRandomName("model1");
+ Map<String, String> properties = ImmutableMap.of("key1", "val1");
+ NameIdentifier modelIdent = NameIdentifier.of(schemaName, modelName);
+
+ gravitinoCatalog.asModelCatalog().registerModel(modelIdent, null, null);
+
+ // Link a model version with NO aliases
+ gravitinoCatalog
+ .asModelCatalog()
+ .linkModelVersion(modelIdent, "uri", new String[] {}, "comment",
properties);
+
+ ModelVersion modelVersion =
gravitinoCatalog.asModelCatalog().getModelVersion(modelIdent, 0);
+ Assertions.assertEquals(0, modelVersion.version());
+ Assertions.assertEquals(0, modelVersion.aliases().length);
+
+ // Now add aliases to the version that previously had none — must not throw
+ ModelVersionChange change =
+ ModelVersionChange.updateAliases(new String[] {"alias1", "alias2"},
new String[] {});
+ ModelVersion updatedModelVersion =
+ Assertions.assertDoesNotThrow(
+ () ->
gravitinoCatalog.asModelCatalog().alterModelVersion(modelIdent, 0, change));
+
+ String[] expectedAliases = {"alias1", "alias2"};
+ Assertions.assertEquals(0, updatedModelVersion.version());
+ Assertions.assertArrayEquals(expectedAliases,
updatedModelVersion.aliases());
+
+ // Reload and verify aliases are persisted
+ ModelVersion reloadedModelVersion =
+ gravitinoCatalog.asModelCatalog().getModelVersion(modelIdent, 0);
+ Assertions.assertArrayEquals(expectedAliases,
reloadedModelVersion.aliases());
+
+ // Verify lookups by alias now work
+ Assertions.assertDoesNotThrow(
+ () -> gravitinoCatalog.asModelCatalog().getModelVersion(modelIdent,
"alias1"));
+ Assertions.assertDoesNotThrow(
+ () -> gravitinoCatalog.asModelCatalog().getModelVersion(modelIdent,
"alias2"));
+ }
+
@Test
public void testGetModelVersionUri() {
// Test get model version without default uri name
diff --git a/clients/client-python/tests/integration/test_model_catalog.py
b/clients/client-python/tests/integration/test_model_catalog.py
index a73c6e7efc..488b2a0d3d 100644
--- a/clients/client-python/tests/integration/test_model_catalog.py
+++ b/clients/client-python/tests/integration/test_model_catalog.py
@@ -564,6 +564,50 @@ class TestModelCatalog(IntegrationTestEnv):
self.assertEqual("comment", updated_model_version.comment())
self.assertEqual({"k1": "v1", "k2": "v2"},
updated_model_version.properties())
+ def test_link_update_model_version_aliases_from_empty(self):
+ # Regression test for https://github.com/apache/gravitino/issues/9727:
+ # updating aliases on a model version that was linked without any
aliases must not throw.
+ model_name = f"model_it_model{str(randint(0, 1000))}"
+ model_ident = NameIdentifier.of(self._schema_name, model_name)
+ properties = {"k1": "v1"}
+ self._catalog.as_model_catalog().register_model(
+ model_ident, "comment", properties
+ )
+
+ # Link a model version with NO aliases
+ self._catalog.as_model_catalog().link_model_version(
+ model_ident,
+ uri="uri",
+ aliases=[],
+ comment="comment",
+ properties=properties,
+ )
+
+ original_model_version =
self._catalog.as_model_catalog().get_model_version(
+ model_ident, 0
+ )
+ self.assertEqual(0, original_model_version.version())
+ self.assertEqual([], original_model_version.aliases())
+
+ # Add aliases to the version that previously had none — must not raise
+ changes = [ModelVersionChange.update_aliases(["alias1", "alias2"], [])]
+ updated_model_version =
self._catalog.as_model_catalog().alter_model_version(
+ model_ident, 0, *changes
+ )
+
+ self.assertEqual(0, updated_model_version.version())
+ self.assertCountEqual(["alias1", "alias2"],
updated_model_version.aliases())
+
+ # Reload and verify aliases are persisted
+ reloaded =
self._catalog.as_model_catalog().get_model_version(model_ident, 0)
+ self.assertCountEqual(["alias1", "alias2"], reloaded.aliases())
+
+ # Verify lookup by alias works
+ by_alias = self._catalog.as_model_catalog().get_model_version_by_alias(
+ model_ident, "alias1"
+ )
+ self.assertEqual(0, by_alias.version())
+
def test_link_get_model_version(self):
model_name = "model_it_model" + str(randint(0, 1000))
model_ident = NameIdentifier.of(self._schema_name, model_name)
diff --git
a/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 82f92a7443..38dd40576a 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
@@ -339,7 +339,8 @@ public class ModelVersionMetaService {
boolean isAliasChanged =
isModelVersionAliasUpdated(oldModelVersionEntity,
newModelVersionEntity);
List<ModelVersionAliasRelPO> newAliasRelPOs =
- POConverters.updateModelVersionAliasRelPO(oldAliasRelPOs,
newModelVersionEntity);
+ POConverters.updateModelVersionAliasRelPO(
+ oldAliasRelPOs, newModelVersionEntity, modelEntity.id());
boolean isModelVersionUriUpdated =
isModelVersionUriUpdated(oldModelVersionEntity, newModelVersionEntity);
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 a94f9b3cba..95344a8f89 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
@@ -1708,23 +1708,32 @@ public class POConverters {
}
/**
- * Construct a new ModelVersionAliasRelPO object with the given alias.
+ * Construct a list of new {@link ModelVersionAliasRelPO} objects for the
updated model version,
+ * one entry per alias in the new model version.
*
- * @param oldModelVersionAliasRelPOs The old ModelVersionAliasRelPOs object
+ * @param oldModelVersionAliasRelPOs The old ModelVersionAliasRelPOs list
* @param newModelVersion The new {@link ModelVersionEntity} object
- * @return The new ModelVersionAliasRelPO object
+ * @param modelId The DB ID of the model entity
+ * @return A list of new {@link ModelVersionAliasRelPO} objects, one per
alias
*/
public static List<ModelVersionAliasRelPO> updateModelVersionAliasRelPO(
- List<ModelVersionAliasRelPO> oldModelVersionAliasRelPOs,
ModelVersionEntity newModelVersion) {
+ List<ModelVersionAliasRelPO> oldModelVersionAliasRelPOs,
+ ModelVersionEntity newModelVersion,
+ Long modelId) {
if (!oldModelVersionAliasRelPOs.isEmpty()) {
ModelVersionAliasRelPO oldModelVersionAliasRelPO =
oldModelVersionAliasRelPOs.get(0);
return newModelVersion.aliases().stream()
- .map(alias -> createAliasRelPO(oldModelVersionAliasRelPO, alias))
+ .map(
+ alias ->
+ createAliasRelPO(
+ oldModelVersionAliasRelPO.getModelId(),
+ oldModelVersionAliasRelPO.getModelVersion(),
+ alias))
.collect(Collectors.toList());
} else {
return newModelVersion.aliases().stream()
- .map(alias -> createAliasRelPO(newModelVersion, alias))
+ .map(alias -> createAliasRelPO(modelId, newModelVersion.version(),
alias))
.collect(Collectors.toList());
}
}
@@ -1775,21 +1784,11 @@ public class POConverters {
.collect(Collectors.toList());
}
- private static ModelVersionAliasRelPO createAliasRelPO(
- ModelVersionAliasRelPO oldModelVersionAliasRelPO, String alias) {
- return ModelVersionAliasRelPO.builder()
- .withModelVersion(oldModelVersionAliasRelPO.getModelVersion())
- .withModelVersionAlias(alias)
- .withModelId(oldModelVersionAliasRelPO.getModelId())
- .withDeletedAt(DEFAULT_DELETED_AT)
- .build();
- }
-
- private static ModelVersionAliasRelPO createAliasRelPO(ModelVersionEntity
entity, String alias) {
+ private static ModelVersionAliasRelPO createAliasRelPO(Long modelId, int
version, String alias) {
return ModelVersionAliasRelPO.builder()
- .withModelVersion(entity.version())
+ .withModelVersion(version)
.withModelVersionAlias(alias)
- .withModelId(entity.id())
+ .withModelId(modelId)
.withDeletedAt(DEFAULT_DELETED_AT)
.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 c3fe11c98c..2ab5b484b3 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
@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.sql.SQLException;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -913,6 +914,62 @@ public class TestModelVersionMetaService extends
TestJDBCBackend {
updatePropertiesUpdater));
}
+ @TestTemplate
+ void testUpdateModelVersionAliasesFromEmpty() throws IOException {
+ createParentEntities(METALAKE_NAME, CATALOG_NAME, SCHEMA_NAME, AUDIT_INFO);
+
+ Map<String, String> properties = ImmutableMap.of("k1", "v1");
+ String modelName = randomModelName();
+ List<String> updatedVersionAliases = ImmutableList.of("alias1", "alias2");
+
+ ModelEntity modelEntity =
+ createModelEntity(
+ RandomIdGenerator.INSTANCE.nextId(),
+ MODEL_NS,
+ modelName,
+ "model comment",
+ 0,
+ properties,
+ AUDIT_INFO);
+
+ // Create model version with NO aliases
+ ModelVersionEntity modelVersionEntity =
+ createModelVersionEntity(
+ modelEntity.nameIdentifier(),
+ 0,
+ ImmutableMap.of(ModelVersion.URI_NAME_UNKNOWN, "S3://test/path"),
+ Collections.emptyList(),
+ "version comment",
+ properties,
+ AUDIT_INFO);
+
+ ModelVersionEntity updatedModelVersionEntity =
+ createModelVersionEntity(
+ modelVersionEntity.modelIdentifier(),
+ modelVersionEntity.version(),
+ modelVersionEntity.uris(),
+ updatedVersionAliases,
+ modelVersionEntity.comment(),
+ modelVersionEntity.properties(),
+ modelVersionEntity.auditInfo());
+
+ Assertions.assertDoesNotThrow(
+ () -> ModelMetaService.getInstance().insertModel(modelEntity, false));
+ Assertions.assertDoesNotThrow(
+ () ->
ModelVersionMetaService.getInstance().insertModelVersion(modelVersionEntity));
+
+ // Updating aliases on a version that had no aliases must not throw
+ Function<ModelVersionEntity, ModelVersionEntity> updater = old ->
updatedModelVersionEntity;
+
+ ModelVersionEntity altered =
+ Assertions.assertDoesNotThrow(
+ () ->
+ ModelVersionMetaService.getInstance()
+ .updateModelVersion(modelVersionEntity.nameIdentifier(),
updater));
+
+ Assertions.assertEquals(updatedVersionAliases, altered.aliases());
+ }
+
private NameIdentifier getModelVersionIdent(NameIdentifier modelIdent, int
version) {
List<String> parts = Lists.newArrayList(modelIdent.namespace().levels());
parts.add(modelIdent.name());
diff --git
a/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
b/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
index 2582b459c6..87da71493f 100644
---
a/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
+++
b/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
@@ -1224,6 +1224,54 @@ public class TestPOConverters {
assertEquals(expectedModelVersionWithNull, convertedModelVersionWithNull);
}
+ @Test
+ public void testUpdateModelVersionAliasRelPO() {
+ NameIdentifier modelIdent = NameIdentifierUtil.ofModel("m", "c", "s",
"model1");
+ AuditInfo auditInfo =
+
AuditInfo.builder().withCreator("creator").withCreateTime(FIX_INSTANT).build();
+ List<String> newAliases = ImmutableList.of("alias3", "alias4");
+ Long modelId = 1L;
+
+ ModelVersionEntity newModelVersion =
+ ModelVersionEntity.builder()
+ .withModelIdentifier(modelIdent)
+ .withVersion(1)
+ .withAliases(newAliases)
+ .withComment("test")
+ .withProperties(null)
+ .withUris(ImmutableMap.of("uri", "hdfs://localhost/test"))
+ .withAuditInfo(auditInfo)
+ .build();
+
+ // Case 1: old alias list is non-empty — modelId/version copied from old PO
+ List<ModelVersionAliasRelPO> oldAliasPOs =
+ ImmutableList.of(
+ ModelVersionAliasRelPO.builder()
+ .withModelVersionAlias("alias1")
+ .withModelVersion(1)
+ .withModelId(modelId)
+ .withDeletedAt(0L)
+ .build());
+
+ List<ModelVersionAliasRelPO> result =
+ POConverters.updateModelVersionAliasRelPO(oldAliasPOs,
newModelVersion, modelId);
+ assertEquals(2, result.size());
+ assertEquals("alias3", result.get(0).getModelVersionAlias());
+ assertEquals("alias4", result.get(1).getModelVersionAlias());
+ assertEquals(1, result.get(0).getModelVersion());
+ assertEquals(modelId, result.get(0).getModelId());
+
+ // Case 2: old alias list is empty (model version created without aliases)
— should not throw
+ List<ModelVersionAliasRelPO> resultFromEmpty =
+ POConverters.updateModelVersionAliasRelPO(
+ Collections.emptyList(), newModelVersion, modelId);
+ assertEquals(2, resultFromEmpty.size());
+ assertEquals("alias3", resultFromEmpty.get(0).getModelVersionAlias());
+ assertEquals("alias4", resultFromEmpty.get(1).getModelVersionAlias());
+ assertEquals(1, resultFromEmpty.get(0).getModelVersion());
+ assertEquals(modelId, resultFromEmpty.get(0).getModelId());
+ }
+
@Test
public void testStatisticPO() throws JsonProcessingException {
List<StatisticEntity> statisticEntities = Lists.newArrayList();