This is an automated email from the ASF dual-hosted git repository.
jerryshao 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 d9005e9897 [#12564] feat(core): Add Semantic Model management
framework (#12565)
d9005e9897 is described below
commit d9005e9897adfd5b4db99b8afcb8ccaaabca9d5f
Author: mchades <[email protected]>
AuthorDate: Wed Aug 26 17:09:50 2026 +0800
[#12564] feat(core): Add Semantic Model management framework (#12565)
### What changes were proposed in this pull request?
Add the core management framework for first-class Semantic Models:
- Add the Semantic Model dispatcher contract and normalization layer.
- Add the operation dispatcher with relational catalog checks, parent
schema validation, and tree locking.
- Wire the dispatcher chain into `GravitinoEnv`.
- Add `Capability.Scope.SEMANTIC_MODEL` and Semantic Model name
normalization.
- Add the managed-operation boundary, with persistence methods
intentionally left unsupported until the entity storage implementation
is added.
- Add focused tests for capability handling, normalization, dispatching,
and unsupported managed operations.
### Why are the changes needed?
This framework establishes where Semantic Model lifecycle operations,
normalization, locking, and persistence coordination belong in Gravitino
core, while keeping entity persistence and business validation in
independently reviewable follow-up changes.
Fix: #12564
### Does this PR introduce _any_ user-facing change?
Yes. `Capability.Scope` gains the `SEMANTIC_MODEL` value. No Semantic
Model CRUD operation becomes functional in this PR.
### How was this patch tested?
- `git diff HEAD^ HEAD --check`
- `./gradlew spotlessApply`
- `./gradlew :core:check -PskipITs`
---
.../java/org/apache/gravitino/GravitinoEnv.java | 24 +++
.../gravitino/catalog/CapabilityHelpers.java | 30 ++--
.../catalog/ManagedSemanticModelOperations.java | 103 ++++++++++++
.../gravitino/catalog/SemanticModelDispatcher.java | 24 +++
.../catalog/SemanticModelNormalizeDispatcher.java | 130 +++++++++++++++
.../catalog/SemanticModelOperationDispatcher.java | 135 +++++++++++++++
.../gravitino/connector/capability/Capability.java | 5 +-
.../gravitino/catalog/TestCapabilityHelpers.java | 34 ++++
.../TestManagedSemanticModelOperations.java | 56 +++++++
.../TestSemanticModelNormalizeDispatcher.java | 142 ++++++++++++++++
.../TestSemanticModelOperationDispatcher.java | 184 +++++++++++++++++++++
.../gravitino/hook/TestModelHookDispatcher.java | 11 +-
12 files changed, 856 insertions(+), 22 deletions(-)
diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index 597fa3d5a8..551283c3d1 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -47,6 +47,9 @@ import
org.apache.gravitino.catalog.PartitionOperationDispatcher;
import org.apache.gravitino.catalog.SchemaDispatcher;
import org.apache.gravitino.catalog.SchemaNormalizeDispatcher;
import org.apache.gravitino.catalog.SchemaOperationDispatcher;
+import org.apache.gravitino.catalog.SemanticModelDispatcher;
+import org.apache.gravitino.catalog.SemanticModelNormalizeDispatcher;
+import org.apache.gravitino.catalog.SemanticModelOperationDispatcher;
import org.apache.gravitino.catalog.TableDispatcher;
import org.apache.gravitino.catalog.TableNormalizeDispatcher;
import org.apache.gravitino.catalog.TableOperationDispatcher;
@@ -152,6 +155,8 @@ public class GravitinoEnv {
private FunctionDispatcher functionDispatcher;
+ private SemanticModelDispatcher semanticModelDispatcher;
+
private ViewDispatcher viewDispatcher;
private ViewDispatcher internalViewDispatcher;
@@ -341,6 +346,15 @@ public class GravitinoEnv {
return functionDispatcher;
}
+ /**
+ * Get the Semantic Model dispatcher associated with the Gravitino
environment.
+ *
+ * @return The Semantic Model dispatcher.
+ */
+ public SemanticModelDispatcher semanticModelDispatcher() {
+ return semanticModelDispatcher;
+ }
+
/**
* Get the ViewDispatcher associated with the Gravitino environment.
*
@@ -873,6 +887,16 @@ public class GravitinoEnv {
new ViewEventDispatcher(eventBus, viewNormalizeDispatcher);
this.viewDispatcher = viewEventDispatcher;
+ // Semantic Model operation chain: SemanticModelNormalizeDispatcher ->
+ // SemanticModelOperationDispatcher -> ManagedSemanticModelOperations.
+ // TODO(#12595): Add Semantic Model event dispatching before server
integration.
+ // TODO(#12594): Add Semantic Model ownership and privilege hooks.
+ SemanticModelOperationDispatcher semanticModelOperationDispatcher =
+ new SemanticModelOperationDispatcher(
+ catalogManager, schemaOperationDispatcher, entityStore,
idGenerator, secretManager);
+ this.semanticModelDispatcher =
+ new SemanticModelNormalizeDispatcher(semanticModelOperationDispatcher,
catalogManager);
+
this.statisticDispatcher =
new StatisticEventDispatcher(
eventBus, new StatisticManager(entityStore, idGenerator, config));
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
b/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
index eae018c2c0..39ad527199 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
@@ -152,12 +152,7 @@ public class CapabilityHelpers {
Namespace namespace, Capability.Scope identScope, Capability
capabilities) {
String metalake = namespace.level(0);
String catalog = namespace.level(1);
- if (identScope == Capability.Scope.TABLE
- || identScope == Capability.Scope.VIEW
- || identScope == Capability.Scope.FILESET
- || identScope == Capability.Scope.TOPIC
- || identScope == Capability.Scope.MODEL
- || identScope == Capability.Scope.FUNCTION) {
+ if (hasSchemaParent(identScope)) {
String schema = namespace.level(namespace.length() - 1);
schema = applyCaseSensitiveOnName(Capability.Scope.SCHEMA, schema,
capabilities);
return Namespace.of(metalake, catalog, schema);
@@ -223,11 +218,7 @@ public class CapabilityHelpers {
Namespace namespace, Capability.Scope identScope, Capability
capabilities) {
String metalake = namespace.level(0);
String catalog = namespace.level(1);
- if (identScope == Capability.Scope.TABLE
- || identScope == Capability.Scope.VIEW
- || identScope == Capability.Scope.FILESET
- || identScope == Capability.Scope.TOPIC
- || identScope == Capability.Scope.FUNCTION) {
+ if (hasSchemaParent(identScope)) {
String schema = namespace.level(namespace.length() - 1);
schema = applyCapabilitiesOnName(Capability.Scope.SCHEMA, schema,
capabilities);
return Namespace.of(metalake, catalog, schema);
@@ -235,6 +226,21 @@ public class CapabilityHelpers {
return namespace;
}
+ private static boolean hasSchemaParent(Capability.Scope resourceScope) {
+ switch (resourceScope) {
+ case TABLE:
+ case VIEW:
+ case FILESET:
+ case TOPIC:
+ case MODEL:
+ case FUNCTION:
+ case SEMANTIC_MODEL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
private static Index applyCapabilities(Index index, Capability capabilities)
{
return Indexes.of(
index.type(),
@@ -499,7 +505,7 @@ public class CapabilityHelpers {
column.defaultValue());
}
- private static String applyCapabilitiesOnName(
+ static String applyCapabilitiesOnName(
Capability.Scope scope, String name, Capability capabilities) {
applyNameSpecification(scope, name, capabilities);
return applyCaseSensitiveOnName(scope, name, capabilities);
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/ManagedSemanticModelOperations.java
b/core/src/main/java/org/apache/gravitino/catalog/ManagedSemanticModelOperations.java
new file mode 100644
index 0000000000..de6b1f819d
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/catalog/ManagedSemanticModelOperations.java
@@ -0,0 +1,103 @@
+/*
+ * 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.catalog;
+
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.exceptions.IllegalSemanticModelException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchSemanticModelException;
+import org.apache.gravitino.exceptions.SemanticModelAlreadyExistsException;
+import org.apache.gravitino.semantic.SemanticModel;
+import org.apache.gravitino.semantic.SemanticModelCatalog;
+import org.apache.gravitino.semantic.SemanticModelChange;
+import org.apache.gravitino.semantic.SemanticModelDefinition;
+import org.apache.gravitino.storage.IdGenerator;
+
+/**
+ * Provides storage-level Semantic Model operations backed by Gravitino's
{@link EntityStore}.
+ *
+ * <p>The framework establishes the managed-operation boundary. Semantic Model
entity persistence
+ * and lifecycle implementations will be added in a follow-up change.
+ */
+public class ManagedSemanticModelOperations implements SemanticModelCatalog {
+
+ @SuppressWarnings("UnusedVariable")
+ private final EntityStore store;
+
+ @SuppressWarnings("UnusedVariable")
+ private final IdGenerator idGenerator;
+
+ /**
+ * Creates managed Semantic Model operations.
+ *
+ * @param store The EntityStore used for persistence.
+ * @param idGenerator The stable entity ID generator.
+ */
+ public ManagedSemanticModelOperations(EntityStore store, IdGenerator
idGenerator) {
+ this.store = store;
+ this.idGenerator = idGenerator;
+ }
+
+ @Override
+ public NameIdentifier[] listSemanticModels(Namespace namespace) throws
NoSuchSchemaException {
+ // TODO: Implement when SemanticModelEntity is available.
+ throw new UnsupportedOperationException(
+ "listSemanticModels: SemanticModelEntity is not yet implemented");
+ }
+
+ @Override
+ public SemanticModel loadSemanticModel(NameIdentifier ident) throws
NoSuchSemanticModelException {
+ // TODO: Implement when SemanticModelEntity is available.
+ throw new UnsupportedOperationException(
+ "loadSemanticModel: SemanticModelEntity is not yet implemented");
+ }
+
+ @Override
+ public SemanticModel createSemanticModel(
+ NameIdentifier ident,
+ @Nullable String comment,
+ SemanticModelDefinition definition,
+ Map<String, String> properties)
+ throws NoSuchSchemaException, SemanticModelAlreadyExistsException,
+ IllegalSemanticModelException {
+ // TODO: Implement when SemanticModelEntity is available.
+ throw new UnsupportedOperationException(
+ "createSemanticModel: SemanticModelEntity is not yet implemented");
+ }
+
+ @Override
+ public SemanticModel alterSemanticModel(NameIdentifier ident,
SemanticModelChange... changes)
+ throws NoSuchSemanticModelException, SemanticModelAlreadyExistsException,
+ IllegalSemanticModelException {
+ // TODO: Implement when SemanticModelEntity is available.
+ throw new UnsupportedOperationException(
+ "alterSemanticModel: SemanticModelEntity is not yet implemented");
+ }
+
+ @Override
+ public boolean dropSemanticModel(NameIdentifier ident) {
+ // TODO: Implement when SemanticModelEntity is available.
+ throw new UnsupportedOperationException(
+ "dropSemanticModel: SemanticModelEntity is not yet implemented");
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/SemanticModelDispatcher.java
b/core/src/main/java/org/apache/gravitino/catalog/SemanticModelDispatcher.java
new file mode 100644
index 0000000000..2b35df18f2
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/catalog/SemanticModelDispatcher.java
@@ -0,0 +1,24 @@
+/*
+ * 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.catalog;
+
+import org.apache.gravitino.semantic.SemanticModelCatalog;
+
+/** A dispatcher specialization for schema-scoped Semantic Model operations. */
+public interface SemanticModelDispatcher extends SemanticModelCatalog {}
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/SemanticModelNormalizeDispatcher.java
b/core/src/main/java/org/apache/gravitino/catalog/SemanticModelNormalizeDispatcher.java
new file mode 100644
index 0000000000..ac6411db27
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/catalog/SemanticModelNormalizeDispatcher.java
@@ -0,0 +1,130 @@
+/*
+ * 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.catalog;
+
+import static org.apache.gravitino.catalog.CapabilityHelpers.applyCapabilities;
+import static
org.apache.gravitino.catalog.CapabilityHelpers.applyCapabilitiesOnName;
+import static
org.apache.gravitino.catalog.CapabilityHelpers.applyCaseSensitive;
+import static org.apache.gravitino.catalog.CapabilityHelpers.getCapability;
+
+import com.google.common.base.Preconditions;
+import java.util.Arrays;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.exceptions.IllegalSemanticModelException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchSemanticModelException;
+import org.apache.gravitino.exceptions.SemanticModelAlreadyExistsException;
+import org.apache.gravitino.semantic.SemanticModel;
+import org.apache.gravitino.semantic.SemanticModelChange;
+import org.apache.gravitino.semantic.SemanticModelDefinition;
+
+/** Normalizes Semantic Model parents while retaining Gravitino-owned model
naming rules. */
+public class SemanticModelNormalizeDispatcher implements
SemanticModelDispatcher {
+
+ private final SemanticModelDispatcher dispatcher;
+ private final CatalogManager catalogManager;
+
+ /**
+ * Creates a Semantic Model normalization dispatcher.
+ *
+ * @param dispatcher The underlying dispatcher.
+ * @param catalogManager The catalog manager used to load parent naming
capabilities.
+ */
+ public SemanticModelNormalizeDispatcher(
+ SemanticModelDispatcher dispatcher, CatalogManager catalogManager) {
+ this.dispatcher = dispatcher;
+ this.catalogManager = catalogManager;
+ }
+
+ @Override
+ public NameIdentifier[] listSemanticModels(Namespace namespace) throws
NoSuchSchemaException {
+ return dispatcher.listSemanticModels(normalizeParentForLookup(namespace));
+ }
+
+ @Override
+ public SemanticModel loadSemanticModel(NameIdentifier ident) throws
NoSuchSemanticModelException {
+ return dispatcher.loadSemanticModel(normalizeForLookup(ident));
+ }
+
+ @Override
+ public boolean semanticModelExists(NameIdentifier ident) {
+ return dispatcher.semanticModelExists(normalizeForLookup(ident));
+ }
+
+ @Override
+ public SemanticModel createSemanticModel(
+ NameIdentifier ident,
+ @Nullable String comment,
+ SemanticModelDefinition definition,
+ Map<String, String> properties)
+ throws NoSuchSchemaException, SemanticModelAlreadyExistsException,
+ IllegalSemanticModelException {
+ return dispatcher.createSemanticModel(
+ normalizeForCreate(ident), comment, definition, properties);
+ }
+
+ @Override
+ public SemanticModel alterSemanticModel(NameIdentifier ident,
SemanticModelChange... changes)
+ throws NoSuchSemanticModelException, SemanticModelAlreadyExistsException,
+ IllegalSemanticModelException {
+ Preconditions.checkArgument(
+ changes != null && changes.length > 0, "At least one change is
required");
+ SemanticModelChange[] normalizedChanges =
+
Arrays.stream(changes).map(this::normalizeChange).toArray(SemanticModelChange[]::new);
+ return dispatcher.alterSemanticModel(normalizeForLookup(ident),
normalizedChanges);
+ }
+
+ @Override
+ public boolean dropSemanticModel(NameIdentifier ident) {
+ return dispatcher.dropSemanticModel(normalizeForLookup(ident));
+ }
+
+ private Namespace normalizeParentForLookup(Namespace namespace) {
+ Capability capability =
getCapability(NameIdentifier.of(namespace.levels()), catalogManager);
+ return applyCaseSensitive(namespace, Capability.Scope.SEMANTIC_MODEL,
capability);
+ }
+
+ private NameIdentifier normalizeForLookup(NameIdentifier ident) {
+ return NameIdentifier.of(normalizeParentForLookup(ident.namespace()),
ident.name());
+ }
+
+ private NameIdentifier normalizeForCreate(NameIdentifier ident) {
+ Capability capability = getCapability(ident, catalogManager);
+ Namespace namespace =
+ applyCapabilities(ident.namespace(), Capability.Scope.SEMANTIC_MODEL,
capability);
+ return NameIdentifier.of(namespace,
normalizeSemanticModelName(ident.name()));
+ }
+
+ private SemanticModelChange normalizeChange(SemanticModelChange change) {
+ if (change instanceof SemanticModelChange.RenameSemanticModel) {
+ return SemanticModelChange.rename(
+ normalizeSemanticModelName(
+ ((SemanticModelChange.RenameSemanticModel)
change).getNewName()));
+ }
+ return change;
+ }
+
+ private String normalizeSemanticModelName(String name) {
+ return applyCapabilitiesOnName(Capability.Scope.SEMANTIC_MODEL, name,
Capability.DEFAULT);
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/SemanticModelOperationDispatcher.java
b/core/src/main/java/org/apache/gravitino/catalog/SemanticModelOperationDispatcher.java
new file mode 100644
index 0000000000..8388310f2d
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/catalog/SemanticModelOperationDispatcher.java
@@ -0,0 +1,135 @@
+/*
+ * 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.catalog;
+
+import com.google.common.base.Preconditions;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.exceptions.IllegalSemanticModelException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchSemanticModelException;
+import org.apache.gravitino.exceptions.SemanticModelAlreadyExistsException;
+import org.apache.gravitino.secret.SecretManager;
+import org.apache.gravitino.semantic.SemanticModel;
+import org.apache.gravitino.semantic.SemanticModelChange;
+import org.apache.gravitino.semantic.SemanticModelDefinition;
+import org.apache.gravitino.storage.IdGenerator;
+
+/** Dispatches always-managed Semantic Model operations to Gravitino's
EntityStore. */
+public class SemanticModelOperationDispatcher extends OperationDispatcher
+ implements SemanticModelDispatcher {
+
+ private final CatalogManager catalogManager;
+ private final SchemaDispatcher schemaDispatcher;
+ private final ManagedSemanticModelOperations managedOperations;
+
+ /**
+ * Creates a Semantic Model operation dispatcher.
+ *
+ * @param catalogManager The catalog manager.
+ * @param schemaDispatcher The schema operation dispatcher used for parent
validation.
+ * @param store The EntityStore used for Semantic Model persistence.
+ * @param idGenerator The stable entity ID generator.
+ * @param secretManager The secret manager required by the operation
dispatcher base class.
+ */
+ public SemanticModelOperationDispatcher(
+ CatalogManager catalogManager,
+ SchemaDispatcher schemaDispatcher,
+ EntityStore store,
+ IdGenerator idGenerator,
+ SecretManager secretManager) {
+ super(catalogManager, store, idGenerator, secretManager);
+ this.catalogManager = catalogManager;
+ this.schemaDispatcher = schemaDispatcher;
+ this.managedOperations = new ManagedSemanticModelOperations(store,
idGenerator);
+ }
+
+ @Override
+ public NameIdentifier[] listSemanticModels(Namespace namespace) throws
NoSuchSchemaException {
+ checkRelationalCatalog(namespace);
+ NameIdentifier schemaIdent = NameIdentifier.of(namespace.levels());
+ schemaDispatcher.loadSchema(schemaIdent);
+ return managedOperations.listSemanticModels(namespace);
+ }
+
+ @Override
+ public SemanticModel loadSemanticModel(NameIdentifier ident) throws
NoSuchSemanticModelException {
+ checkRelationalCatalog(ident.namespace());
+ if (!schemaDispatcher.schemaExists(schemaIdentifier(ident))) {
+ throw new NoSuchSemanticModelException("Semantic Model %s does not
exist", ident);
+ }
+ return managedOperations.loadSemanticModel(ident);
+ }
+
+ @Override
+ public SemanticModel createSemanticModel(
+ NameIdentifier ident,
+ @Nullable String comment,
+ SemanticModelDefinition definition,
+ Map<String, String> properties)
+ throws NoSuchSchemaException, SemanticModelAlreadyExistsException,
+ IllegalSemanticModelException {
+ Preconditions.checkArgument(definition != null, "Definition must not be
null");
+ Preconditions.checkArgument(properties != null, "Properties must not be
null");
+ checkRelationalCatalog(ident.namespace());
+ NameIdentifier schemaIdent = schemaIdentifier(ident);
+ schemaDispatcher.loadSchema(schemaIdent);
+ return managedOperations.createSemanticModel(ident, comment, definition,
properties);
+ }
+
+ @Override
+ public SemanticModel alterSemanticModel(NameIdentifier ident,
SemanticModelChange... changes)
+ throws NoSuchSemanticModelException, SemanticModelAlreadyExistsException,
+ IllegalSemanticModelException {
+ checkRelationalCatalog(ident.namespace());
+ NameIdentifier schemaIdent = schemaIdentifier(ident);
+ if (!schemaDispatcher.schemaExists(schemaIdent)) {
+ throw new NoSuchSemanticModelException("Semantic Model %s does not
exist", ident);
+ }
+ return managedOperations.alterSemanticModel(ident, changes);
+ }
+
+ @Override
+ public boolean dropSemanticModel(NameIdentifier ident) {
+ checkRelationalCatalog(ident.namespace());
+ if (!schemaDispatcher.schemaExists(schemaIdentifier(ident))) {
+ return false;
+ }
+ return managedOperations.dropSemanticModel(ident);
+ }
+
+ private void checkRelationalCatalog(Namespace namespace) {
+ NameIdentifier catalogIdent = NameIdentifier.of(namespace.level(0),
namespace.level(1));
+ Catalog catalog = catalogManager.loadCatalog(catalogIdent);
+ if (catalog.type() != Catalog.Type.RELATIONAL) {
+ throw new UnsupportedOperationException(
+ String.format(
+ "Catalog %s has type %s and does not support Semantic Model
operations",
+ catalogIdent, catalog.type()));
+ }
+ }
+
+ private static NameIdentifier schemaIdentifier(NameIdentifier ident) {
+ return NameIdentifier.of(ident.namespace().levels());
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/connector/capability/Capability.java
b/core/src/main/java/org/apache/gravitino/connector/capability/Capability.java
index 66a6e11d61..239cf6a8ce 100644
---
a/core/src/main/java/org/apache/gravitino/connector/capability/Capability.java
+++
b/core/src/main/java/org/apache/gravitino/connector/capability/Capability.java
@@ -43,7 +43,8 @@ public interface Capability {
TOPIC,
PARTITION,
MODEL,
- FUNCTION
+ FUNCTION,
+ SEMANTIC_MODEL
}
/**
@@ -179,7 +180,7 @@ public interface Capability {
@Override
public CapabilityResult managedStorage(Scope scope) {
- if (scope == Scope.FUNCTION) {
+ if (scope == Scope.FUNCTION || scope == Scope.SEMANTIC_MODEL) {
return CapabilityResult.SUPPORTED;
}
diff --git
a/core/src/test/java/org/apache/gravitino/catalog/TestCapabilityHelpers.java
b/core/src/test/java/org/apache/gravitino/catalog/TestCapabilityHelpers.java
index 529ee4992c..f576db0099 100644
--- a/core/src/test/java/org/apache/gravitino/catalog/TestCapabilityHelpers.java
+++ b/core/src/test/java/org/apache/gravitino/catalog/TestCapabilityHelpers.java
@@ -142,4 +142,38 @@ public class TestCapabilityHelpers {
Assertions.assertEquals("My Table", result.name());
}
+
+ @Test
+ void testSemanticModelUsesSchemaNamespaceCapabilities() {
+ Namespace namespace = Namespace.of("metalake", "catalog", "mixedSchema");
+
+ Assertions.assertEquals(
+ Namespace.of("metalake", "catalog", "MIXEDSCHEMA"),
+ CapabilityHelpers.applyCapabilities(
+ namespace, Capability.Scope.SEMANTIC_MODEL, UPPERCASE_CAPABILITY));
+ Assertions.assertEquals(
+ Namespace.of("metalake", "catalog", "MIXEDSCHEMA"),
+ CapabilityHelpers.applyCaseSensitive(
+ namespace, Capability.Scope.SEMANTIC_MODEL, UPPERCASE_CAPABILITY));
+ }
+
+ @Test
+ void testModelUsesSchemaNamespaceCapabilities() {
+ Namespace namespace = Namespace.of("metalake", "catalog", "mixedSchema");
+
+ Assertions.assertEquals(
+ Namespace.of("metalake", "catalog", "MIXEDSCHEMA"),
+ CapabilityHelpers.applyCapabilities(
+ namespace, Capability.Scope.MODEL, UPPERCASE_CAPABILITY));
+ Assertions.assertEquals(
+ Namespace.of("metalake", "catalog", "MIXEDSCHEMA"),
+ CapabilityHelpers.applyCaseSensitive(
+ namespace, Capability.Scope.MODEL, UPPERCASE_CAPABILITY));
+ }
+
+ @Test
+ void testSemanticModelUsesManagedStorageByDefault() {
+ Assertions.assertTrue(
+
Capability.DEFAULT.managedStorage(Capability.Scope.SEMANTIC_MODEL).supported());
+ }
}
diff --git
a/core/src/test/java/org/apache/gravitino/catalog/TestManagedSemanticModelOperations.java
b/core/src/test/java/org/apache/gravitino/catalog/TestManagedSemanticModelOperations.java
new file mode 100644
index 0000000000..2375316ce8
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/catalog/TestManagedSemanticModelOperations.java
@@ -0,0 +1,56 @@
+/*
+ * 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.catalog;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+
+import java.util.Map;
+import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.semantic.SemanticModelChange;
+import org.apache.gravitino.semantic.SemanticModelDefinition;
+import org.apache.gravitino.storage.IdGenerator;
+import org.junit.jupiter.api.Test;
+
+public class TestManagedSemanticModelOperations {
+
+ private static final Namespace NAMESPACE = Namespace.of("metalake",
"catalog", "schema");
+ private static final NameIdentifier IDENT = NameIdentifier.of(NAMESPACE,
"model");
+
+ private final ManagedSemanticModelOperations operations =
+ new ManagedSemanticModelOperations(mock(EntityStore.class),
mock(IdGenerator.class));
+
+ @Test
+ public void testOperationsRemainUnsupportedUntilEntityPersistenceIsAdded() {
+ SemanticModelDefinition definition = mock(SemanticModelDefinition.class);
+
+ assertThrows(
+ UnsupportedOperationException.class, () ->
operations.listSemanticModels(NAMESPACE));
+ assertThrows(UnsupportedOperationException.class, () ->
operations.loadSemanticModel(IDENT));
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> operations.createSemanticModel(IDENT, null, definition,
Map.of()));
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> operations.alterSemanticModel(IDENT,
SemanticModelChange.updateComment("updated")));
+ assertThrows(UnsupportedOperationException.class, () ->
operations.dropSemanticModel(IDENT));
+ }
+}
diff --git
a/core/src/test/java/org/apache/gravitino/catalog/TestSemanticModelNormalizeDispatcher.java
b/core/src/test/java/org/apache/gravitino/catalog/TestSemanticModelNormalizeDispatcher.java
new file mode 100644
index 0000000000..4f5401f29a
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/catalog/TestSemanticModelNormalizeDispatcher.java
@@ -0,0 +1,142 @@
+/*
+ * 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.catalog;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Locale;
+import java.util.Map;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.connector.capability.CapabilityResult;
+import org.apache.gravitino.semantic.Dataset;
+import org.apache.gravitino.semantic.SemanticModel;
+import org.apache.gravitino.semantic.SemanticModelChange;
+import org.apache.gravitino.semantic.SemanticModelDefinition;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TestSemanticModelNormalizeDispatcher {
+
+ private static final Namespace INPUT_NAMESPACE =
+ Namespace.of("metalake", "catalog", "MixedSchema");
+ private static final Namespace NORMALIZED_NAMESPACE =
+ Namespace.of("metalake", "catalog", "MIXEDSCHEMA");
+ private static final NameIdentifier INPUT_IDENT =
+ NameIdentifier.of(INPUT_NAMESPACE, "SalesModel");
+ private static final NameIdentifier NORMALIZED_IDENT =
+ NameIdentifier.of(NORMALIZED_NAMESPACE, "SalesModel");
+
+ private SemanticModelDispatcher delegate;
+ private SemanticModelNormalizeDispatcher dispatcher;
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ delegate = mock(SemanticModelDispatcher.class);
+ CatalogManager catalogManager = mock(CatalogManager.class);
+ CatalogManager.CatalogWrapper wrapper =
mock(CatalogManager.CatalogWrapper.class);
+ when(wrapper.capabilities()).thenReturn(new ParentNormalizingCapability());
+ when(catalogManager.loadCatalogAndWrap(NameIdentifier.of("metalake",
"catalog")))
+ .thenReturn(wrapper);
+ dispatcher = new SemanticModelNormalizeDispatcher(delegate,
catalogManager);
+ }
+
+ @Test
+ public void testParentUsesCatalogCapabilityButModelUsesStableRules() {
+ SemanticModelDefinition definition = definition();
+ SemanticModel model = mock(SemanticModel.class);
+ when(delegate.createSemanticModel(NORMALIZED_IDENT, "comment", definition,
Map.of()))
+ .thenReturn(model);
+ when(delegate.loadSemanticModel(NORMALIZED_IDENT)).thenReturn(model);
+ when(delegate.semanticModelExists(NORMALIZED_IDENT)).thenReturn(true);
+ when(delegate.listSemanticModels(NORMALIZED_NAMESPACE))
+ .thenReturn(new NameIdentifier[] {NORMALIZED_IDENT});
+ when(delegate.dropSemanticModel(NORMALIZED_IDENT)).thenReturn(true);
+
+ assertEquals(
+ model, dispatcher.createSemanticModel(INPUT_IDENT, "comment",
definition, Map.of()));
+ assertEquals(model, dispatcher.loadSemanticModel(INPUT_IDENT));
+ assertTrue(dispatcher.semanticModelExists(INPUT_IDENT));
+ assertEquals(NORMALIZED_IDENT,
dispatcher.listSemanticModels(INPUT_NAMESPACE)[0]);
+ assertTrue(dispatcher.dropSemanticModel(INPUT_IDENT));
+
+ verify(delegate).createSemanticModel(NORMALIZED_IDENT, "comment",
definition, Map.of());
+ verify(delegate).loadSemanticModel(NORMALIZED_IDENT);
+ verify(delegate).semanticModelExists(NORMALIZED_IDENT);
+ verify(delegate).listSemanticModels(NORMALIZED_NAMESPACE);
+ verify(delegate).dropSemanticModel(NORMALIZED_IDENT);
+ }
+
+ @Test
+ public void testRenameUsesStableSemanticModelRules() {
+ SemanticModel model = mock(SemanticModel.class);
+ SemanticModelChange rename = SemanticModelChange.rename("RevenueModel");
+ when(delegate.alterSemanticModel(NORMALIZED_IDENT,
rename)).thenReturn(model);
+
+ assertEquals(model, dispatcher.alterSemanticModel(INPUT_IDENT, rename));
+ verify(delegate).alterSemanticModel(NORMALIZED_IDENT, rename);
+
+ NameIdentifier invalidIdent = NameIdentifier.of(INPUT_NAMESPACE, "invalid
model");
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> dispatcher.createSemanticModel(invalidIdent, null, definition(),
Map.of()));
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ dispatcher.alterSemanticModel(
+ INPUT_IDENT, SemanticModelChange.rename("invalid model")));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> dispatcher.alterSemanticModel(INPUT_IDENT, new
SemanticModelChange[0]));
+ }
+
+ private static SemanticModelDefinition definition() {
+ Dataset dataset =
+ Dataset.builder()
+ .withName("orders")
+ .withSource(NameIdentifier.of("sales", "mart", "orders"))
+ .build();
+ return SemanticModelDefinition.builder().withDatasets(new Dataset[]
{dataset}).build();
+ }
+
+ private static final class ParentNormalizingCapability implements Capability
{
+
+ @Override
+ public CapabilityResult caseSensitiveOnName(Scope scope) {
+ return CapabilityResult.unsupported("Normalize names for this test");
+ }
+
+ @Override
+ public String normalizeName(Scope scope, String name) {
+ if (scope == Scope.SCHEMA) {
+ return name.toUpperCase(Locale.ROOT);
+ }
+ if (scope == Scope.SEMANTIC_MODEL) {
+ return name.toLowerCase(Locale.ROOT);
+ }
+ return name;
+ }
+ }
+}
diff --git
a/core/src/test/java/org/apache/gravitino/catalog/TestSemanticModelOperationDispatcher.java
b/core/src/test/java/org/apache/gravitino/catalog/TestSemanticModelOperationDispatcher.java
new file mode 100644
index 0000000000..fc7d0c7ac7
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/catalog/TestSemanticModelOperationDispatcher.java
@@ -0,0 +1,184 @@
+/*
+ * 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.catalog;
+
+import static org.apache.gravitino.Configs.TREE_LOCK_CLEAN_INTERVAL;
+import static org.apache.gravitino.Configs.TREE_LOCK_MAX_NODE_IN_MEMORY;
+import static org.apache.gravitino.Configs.TREE_LOCK_MIN_NODE_IN_MEMORY;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.Schema;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchSemanticModelException;
+import org.apache.gravitino.lock.LockManager;
+import org.apache.gravitino.secret.SecretManager;
+import org.apache.gravitino.semantic.Dataset;
+import org.apache.gravitino.semantic.SemanticModelChange;
+import org.apache.gravitino.semantic.SemanticModelDefinition;
+import org.apache.gravitino.storage.IdGenerator;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TestSemanticModelOperationDispatcher {
+
+ private static final String METALAKE = "metalake";
+ private static final NameIdentifier CATALOG_IDENT =
NameIdentifier.of(METALAKE, "catalog");
+ private static final Namespace NAMESPACE = Namespace.of(METALAKE, "catalog",
"schema");
+ private static final NameIdentifier SCHEMA_IDENT =
NameIdentifier.of(NAMESPACE.levels());
+ private static final NameIdentifier MODEL_IDENT =
NameIdentifier.of(NAMESPACE, "sales_model");
+
+ private CatalogManager catalogManager;
+ private SchemaDispatcher schemaDispatcher;
+ private SemanticModelOperationDispatcher dispatcher;
+
+ @BeforeAll
+ public static void initializeLockManager() throws IllegalAccessException {
+ Config config = mock(Config.class);
+ doReturn(100000L).when(config).get(TREE_LOCK_MAX_NODE_IN_MEMORY);
+ doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY);
+ doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL);
+ FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", new
LockManager(config), true);
+ }
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ catalogManager = mock(CatalogManager.class);
+ schemaDispatcher = mock(SchemaDispatcher.class);
+
+ Catalog catalog = mock(Catalog.class);
+ when(catalog.type()).thenReturn(Catalog.Type.RELATIONAL);
+ when(catalogManager.loadCatalog(CATALOG_IDENT)).thenReturn(catalog);
+
when(schemaDispatcher.loadSchema(SCHEMA_IDENT)).thenReturn(mock(Schema.class));
+ when(schemaDispatcher.schemaExists(SCHEMA_IDENT)).thenReturn(true);
+
+ dispatcher =
+ new SemanticModelOperationDispatcher(
+ catalogManager,
+ schemaDispatcher,
+ mock(EntityStore.class),
+ mock(IdGenerator.class),
+ mock(SecretManager.class));
+ }
+
+ @Test
+ public void testFrameworkValidatesParentAndDelegatesToManagedOperations() {
+ UnsupportedOperationException listFailure =
+ assertThrows(
+ UnsupportedOperationException.class, () ->
dispatcher.listSemanticModels(NAMESPACE));
+ assertTrue(listFailure.getMessage().startsWith("listSemanticModels:"));
+
+ UnsupportedOperationException createFailure =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> dispatcher.createSemanticModel(MODEL_IDENT, null,
definition(), Map.of()));
+ assertTrue(createFailure.getMessage().startsWith("createSemanticModel:"));
+
+ UnsupportedOperationException loadFailure =
+ assertThrows(
+ UnsupportedOperationException.class, () ->
dispatcher.loadSemanticModel(MODEL_IDENT));
+ assertTrue(loadFailure.getMessage().startsWith("loadSemanticModel:"));
+
+ UnsupportedOperationException alterFailure =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () ->
+ dispatcher.alterSemanticModel(
+ MODEL_IDENT,
SemanticModelChange.updateComment("updated")));
+ assertTrue(alterFailure.getMessage().startsWith("alterSemanticModel:"));
+
+ UnsupportedOperationException dropFailure =
+ assertThrows(
+ UnsupportedOperationException.class, () ->
dispatcher.dropSemanticModel(MODEL_IDENT));
+ assertTrue(dropFailure.getMessage().startsWith("dropSemanticModel:"));
+
+ verify(schemaDispatcher, times(2)).loadSchema(SCHEMA_IDENT);
+ verify(schemaDispatcher, times(3)).schemaExists(SCHEMA_IDENT);
+ }
+
+ @Test
+ public void testMissingSchemaPreservesTypedResults() {
+ when(schemaDispatcher.loadSchema(SCHEMA_IDENT))
+ .thenThrow(new NoSuchSchemaException("Schema does not exist"));
+ when(schemaDispatcher.schemaExists(SCHEMA_IDENT)).thenReturn(false);
+
+ assertThrows(NoSuchSchemaException.class, () ->
dispatcher.listSemanticModels(NAMESPACE));
+ assertThrows(
+ NoSuchSchemaException.class,
+ () -> dispatcher.createSemanticModel(MODEL_IDENT, null, definition(),
Map.of()));
+ assertThrows(
+ NoSuchSemanticModelException.class, () ->
dispatcher.loadSemanticModel(MODEL_IDENT));
+ assertThrows(
+ NoSuchSemanticModelException.class,
+ () ->
+ dispatcher.alterSemanticModel(
+ MODEL_IDENT, SemanticModelChange.updateComment("updated")));
+ assertFalse(dispatcher.dropSemanticModel(MODEL_IDENT));
+ }
+
+ @Test
+ public void testNonRelationalCatalogIsRejectedBeforeSchemaLookup() {
+ Catalog catalog = mock(Catalog.class);
+ when(catalog.type()).thenReturn(Catalog.Type.FILESET);
+ when(catalogManager.loadCatalog(CATALOG_IDENT)).thenReturn(catalog);
+
+ UnsupportedOperationException failure =
+ assertThrows(
+ UnsupportedOperationException.class, () ->
dispatcher.listSemanticModels(NAMESPACE));
+
+ assertTrue(failure.getMessage().contains("does not support Semantic Model
operations"));
+ verify(schemaDispatcher, never()).loadSchema(SCHEMA_IDENT);
+ }
+
+ @Test
+ public void testInvalidInputsAreRejectedBeforeCatalogLookup() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> dispatcher.createSemanticModel(MODEL_IDENT, null, null,
Map.of()));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> dispatcher.createSemanticModel(MODEL_IDENT, null, definition(),
null));
+ verify(catalogManager, never()).loadCatalog(CATALOG_IDENT);
+ }
+
+ private static SemanticModelDefinition definition() {
+ Dataset dataset =
+ Dataset.builder()
+ .withName("orders")
+ .withSource(NameIdentifier.of("sales", "mart", "orders"))
+ .build();
+ return SemanticModelDefinition.builder().withDatasets(new Dataset[]
{dataset}).build();
+ }
+}
diff --git
a/core/src/test/java/org/apache/gravitino/hook/TestModelHookDispatcher.java
b/core/src/test/java/org/apache/gravitino/hook/TestModelHookDispatcher.java
index c6777b5891..624d1558e1 100644
--- a/core/src/test/java/org/apache/gravitino/hook/TestModelHookDispatcher.java
+++ b/core/src/test/java/org/apache/gravitino/hook/TestModelHookDispatcher.java
@@ -120,16 +120,11 @@ public class TestModelHookDispatcher {
"my_model",
captor.getValue().name(),
"Model name passed to setOwner must be lowercased by
Capability.Scope.MODEL normalization");
- // MODEL scope is intentionally excluded from
CapabilityHelpers.applyCapabilities(Namespace,
- // Scope, Capability), so the schema component in the namespace is NOT
lowercased -- the
- // captured parent reflects exactly what ModelNormalizeDispatcher would
also pass to the
- // manager. This assertion locks that behavior in.
Assertions.assertEquals(
- "test_catalog.TEST_SCHEMA",
+ "test_catalog.test_schema",
captor.getValue().parent(),
- "Model parent must keep its schema component as-is:
Capability.Scope.MODEL is excluded"
- + " from namespace normalization in CapabilityHelpers; if this
changes, ownership"
- + " attachment will diverge from what ModelNormalizeDispatcher
passes to the manager");
+ "Model parent passed to setOwner must be lowercased by
Capability.Scope.SCHEMA"
+ + " normalization");
}
@Test