This is an automated email from the ASF dual-hosted git repository.
jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.3 by this push:
new eb41cb38c6 [Cherry-pick to branch-1.3] [#12201] refactor(core): inject
table hook dependencies (#12202) (#12204)
eb41cb38c6 is described below
commit eb41cb38c6e109f7beb57c1c3702dd3433730acf
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Jul 27 14:16:22 2026 +0800
[Cherry-pick to branch-1.3] [#12201] refactor(core): inject table hook
dependencies (#12202) (#12204)
**Cherry-pick Information:**
- Original commit: 074a288d85806748c4e30fb40d2e0356da49de12
- Target branch: `branch-1.3`
- Status: ✅ Clean cherry-pick (no conflicts)
Co-authored-by: Nevin Zheng <[email protected]>
---
.../java/org/apache/gravitino/GravitinoEnv.java | 3 +-
.../apache/gravitino/hook/TableHookDispatcher.java | 25 +-
.../authorization/TestAuthorizationUtils.java | 80 ++++
.../gravitino/hook/TestTableHookDispatcher.java | 424 +++++++--------------
4 files changed, 240 insertions(+), 292 deletions(-)
diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index 11c1291129..4f42989045 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -711,7 +711,8 @@ public class GravitinoEnv {
new TableNormalizeDispatcher(internalTableOperationDispatcher,
catalogManager);
TableEventDispatcher tableEventDispatcher =
new TableEventDispatcher(eventBus, tableNormalizeDispatcher);
- this.tableDispatcher = new TableHookDispatcher(tableEventDispatcher);
+ this.tableDispatcher =
+ new TableHookDispatcher(tableEventDispatcher, this::ownerDispatcher,
catalogManager);
// TODO: We can install hooks when we need, we only supports ownership
post hook,
// partition doesn't have ownership, so we don't need it now.
diff --git
a/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java
b/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java
index e898a549cb..bb923afdf2 100644
--- a/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java
+++ b/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java
@@ -20,14 +20,15 @@ package org.apache.gravitino.hook;
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
import org.apache.gravitino.Entity;
-import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.authorization.AuthorizationUtils;
import org.apache.gravitino.authorization.Owner;
import org.apache.gravitino.authorization.OwnerDispatcher;
import org.apache.gravitino.catalog.CapabilityHelpers;
+import org.apache.gravitino.catalog.CatalogManager;
import org.apache.gravitino.catalog.TableDispatcher;
import org.apache.gravitino.connector.capability.Capability;
import org.apache.gravitino.exceptions.NoSuchSchemaException;
@@ -50,9 +51,24 @@ import org.apache.gravitino.utils.PrincipalUtils;
*/
public class TableHookDispatcher implements TableDispatcher {
private final TableDispatcher dispatcher;
+ private final Supplier<OwnerDispatcher> ownerDispatcher;
+ private final CatalogManager catalogManager;
- public TableHookDispatcher(TableDispatcher dispatcher) {
+ /**
+ * Creates a table hook dispatcher.
+ *
+ * @param dispatcher the underlying table dispatcher
+ * @param ownerDispatcher supplies the owner dispatcher, or {@code null}
when authorization is
+ * disabled
+ * @param catalogManager the catalog manager used to apply catalog
capabilities
+ */
+ public TableHookDispatcher(
+ TableDispatcher dispatcher,
+ Supplier<OwnerDispatcher> ownerDispatcher,
+ CatalogManager catalogManager) {
this.dispatcher = dispatcher;
+ this.ownerDispatcher = ownerDispatcher;
+ this.catalogManager = catalogManager;
}
@Override
@@ -81,15 +97,14 @@ public class TableHookDispatcher implements TableDispatcher
{
ident, columns, comment, properties, partitions, distribution,
sortOrders, indexes);
// Set the creator as the owner of the table.
- OwnerDispatcher ownerManager =
GravitinoEnv.getInstance().ownerDispatcher();
+ OwnerDispatcher ownerManager = ownerDispatcher.get();
if (ownerManager != null) {
// The inner NormalizeDispatcher case-folds the table name (and its
schema namespace)
// based on catalog capabilities, so the entity is stored under the
normalized identifier.
// Apply the same normalization here so the owner is attached to the
same identifier the
// manager sees.
NameIdentifier normalizedIdent =
- CapabilityHelpers.applyCapabilities(
- ident, Capability.Scope.TABLE,
GravitinoEnv.getInstance().catalogManager());
+ CapabilityHelpers.applyCapabilities(ident, Capability.Scope.TABLE,
catalogManager);
ownerManager.setOwner(
normalizedIdent.namespace().level(0),
NameIdentifierUtil.toMetadataObject(normalizedIdent,
Entity.EntityType.TABLE),
diff --git
a/core/src/test/java/org/apache/gravitino/authorization/TestAuthorizationUtils.java
b/core/src/test/java/org/apache/gravitino/authorization/TestAuthorizationUtils.java
index c0ae444eb0..e922449ce6 100644
---
a/core/src/test/java/org/apache/gravitino/authorization/TestAuthorizationUtils.java
+++
b/core/src/test/java/org/apache/gravitino/authorization/TestAuthorizationUtils.java
@@ -27,6 +27,7 @@ import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Entity;
import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.Schema;
@@ -35,6 +36,7 @@ import org.apache.gravitino.catalog.CatalogManager;
import org.apache.gravitino.catalog.SchemaDispatcher;
import org.apache.gravitino.catalog.TableDispatcher;
import org.apache.gravitino.connector.BaseCatalog;
+import org.apache.gravitino.connector.authorization.AuthorizationPlugin;
import org.apache.gravitino.exceptions.IllegalNameIdentifierException;
import org.apache.gravitino.exceptions.IllegalNamespaceException;
import org.apache.gravitino.meta.AuditInfo;
@@ -42,6 +44,7 @@ import org.apache.gravitino.meta.RoleEntity;
import org.apache.gravitino.rel.Table;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
@@ -321,4 +324,81 @@ class TestAuthorizationUtils {
.handleEntityNameIdMappingChange("metalake", ident,
Entity.EntityType.TABLE);
}
}
+
+ @Test
+ void
testRenameTablePrivilegesNotifiesAuthorizationPluginWithExpectedChange() {
+ NameIdentifier ident = NameIdentifier.of("metalake", "catalog", "schema",
"table");
+ NameIdentifier catalogIdent = NameIdentifier.of("metalake", "catalog");
+ List<String> locations = Lists.newArrayList("/warehouse/schema/table");
+
+ AccessControlDispatcher accessControlDispatcher =
Mockito.mock(AccessControlDispatcher.class);
+ CatalogManager catalogManager = Mockito.mock(CatalogManager.class);
+ BaseCatalog<?> baseCatalog = Mockito.mock(BaseCatalog.class);
+ AuthorizationPlugin authorizationPlugin =
Mockito.mock(AuthorizationPlugin.class);
+
Mockito.when(catalogManager.loadCatalog(catalogIdent)).thenReturn(baseCatalog);
+
Mockito.when(baseCatalog.getAuthorizationPlugin()).thenReturn(authorizationPlugin);
+
+ GravitinoEnv envMock = Mockito.mock(GravitinoEnv.class);
+
Mockito.when(envMock.accessControlDispatcher()).thenReturn(accessControlDispatcher);
+ Mockito.when(envMock.catalogManager()).thenReturn(catalogManager);
+
+ try (MockedStatic<GravitinoEnv> envStatic =
Mockito.mockStatic(GravitinoEnv.class)) {
+ envStatic.when(GravitinoEnv::getInstance).thenReturn(envMock);
+
+ AuthorizationUtils.authorizationPluginRenamePrivileges(
+ ident, Entity.EntityType.TABLE, "renamed_table", locations);
+ }
+
+ ArgumentCaptor<MetadataObjectChange[]> changesCaptor =
+ ArgumentCaptor.forClass(MetadataObjectChange[].class);
+
Mockito.verify(authorizationPlugin).onMetadataUpdated(changesCaptor.capture());
+ Assertions.assertEquals(1, changesCaptor.getValue().length);
+
+ MetadataObjectChange.RenameMetadataObject renameChange =
+ Assertions.assertInstanceOf(
+ MetadataObjectChange.RenameMetadataObject.class,
changesCaptor.getValue()[0]);
+ Assertions.assertEquals(MetadataObject.Type.TABLE,
renameChange.metadataObject().type());
+ Assertions.assertEquals("catalog.schema.table",
renameChange.metadataObject().fullName());
+ Assertions.assertEquals(MetadataObject.Type.TABLE,
renameChange.newMetadataObject().type());
+ Assertions.assertEquals(
+ "catalog.schema.renamed_table",
renameChange.newMetadataObject().fullName());
+ Assertions.assertEquals(locations, renameChange.locations());
+ }
+
+ @Test
+ void
testRemoveTablePrivilegesNotifiesAuthorizationPluginWithExpectedChange() {
+ NameIdentifier ident = NameIdentifier.of("metalake", "catalog", "schema",
"table");
+ NameIdentifier catalogIdent = NameIdentifier.of("metalake", "catalog");
+ List<String> locations = Lists.newArrayList("/warehouse/schema/table");
+
+ AccessControlDispatcher accessControlDispatcher =
Mockito.mock(AccessControlDispatcher.class);
+ CatalogManager catalogManager = Mockito.mock(CatalogManager.class);
+ BaseCatalog<?> baseCatalog = Mockito.mock(BaseCatalog.class);
+ AuthorizationPlugin authorizationPlugin =
Mockito.mock(AuthorizationPlugin.class);
+
Mockito.when(catalogManager.loadCatalog(catalogIdent)).thenReturn(baseCatalog);
+
Mockito.when(baseCatalog.getAuthorizationPlugin()).thenReturn(authorizationPlugin);
+
+ GravitinoEnv envMock = Mockito.mock(GravitinoEnv.class);
+
Mockito.when(envMock.accessControlDispatcher()).thenReturn(accessControlDispatcher);
+ Mockito.when(envMock.catalogManager()).thenReturn(catalogManager);
+
+ try (MockedStatic<GravitinoEnv> envStatic =
Mockito.mockStatic(GravitinoEnv.class)) {
+ envStatic.when(GravitinoEnv::getInstance).thenReturn(envMock);
+
+ AuthorizationUtils.authorizationPluginRemovePrivileges(
+ ident, Entity.EntityType.TABLE, locations);
+ }
+
+ ArgumentCaptor<MetadataObjectChange[]> changesCaptor =
+ ArgumentCaptor.forClass(MetadataObjectChange[].class);
+
Mockito.verify(authorizationPlugin).onMetadataUpdated(changesCaptor.capture());
+ Assertions.assertEquals(1, changesCaptor.getValue().length);
+
+ MetadataObjectChange.RemoveMetadataObject removeChange =
+ Assertions.assertInstanceOf(
+ MetadataObjectChange.RemoveMetadataObject.class,
changesCaptor.getValue()[0]);
+ Assertions.assertEquals(MetadataObject.Type.TABLE,
removeChange.metadataObject().type());
+ Assertions.assertEquals("catalog.schema.table",
removeChange.metadataObject().fullName());
+ Assertions.assertEquals(locations, removeChange.getLocations());
+ }
}
diff --git
a/core/src/test/java/org/apache/gravitino/hook/TestTableHookDispatcher.java
b/core/src/test/java/org/apache/gravitino/hook/TestTableHookDispatcher.java
index 45f2cc4ca4..bf9431a504 100644
--- a/core/src/test/java/org/apache/gravitino/hook/TestTableHookDispatcher.java
+++ b/core/src/test/java/org/apache/gravitino/hook/TestTableHookDispatcher.java
@@ -18,334 +18,186 @@
*/
package org.apache.gravitino.hook;
-import static org.apache.gravitino.Configs.CATALOG_CACHE_EVICTION_INTERVAL_MS;
-import static org.apache.gravitino.Configs.DEFAULT_ENTITY_RELATIONAL_STORE;
-import static
org.apache.gravitino.Configs.ENTITY_CHANGE_LOG_CLEANUP_INTERVAL_SECS;
-import static
org.apache.gravitino.Configs.ENTITY_CHANGE_LOG_POLL_INTERVAL_SECS;
-import static org.apache.gravitino.Configs.ENTITY_CHANGE_LOG_RETENTION_SECS;
-import static
org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER;
-import static
org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_MAX_CONNECTIONS;
-import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_URL;
-import static
org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_WAIT_MILLISECONDS;
-import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_STORE;
-import static org.apache.gravitino.Configs.ENTITY_STORE;
-import static org.apache.gravitino.Configs.RELATIONAL_ENTITY_STORE;
-import static org.apache.gravitino.Configs.SERVICE_ADMINS;
-import static org.apache.gravitino.Configs.STORE_DELETE_AFTER_TIME;
-import static org.apache.gravitino.Configs.STORE_TRANSACTION_MAX_SKEW_TIME;
-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.apache.gravitino.Configs.VERSION_RETENTION_COUNT;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import java.util.Map;
-import org.apache.commons.lang3.reflect.FieldUtils;
-import org.apache.gravitino.Config;
-import org.apache.gravitino.GravitinoEnv;
+import java.util.List;
+import org.apache.gravitino.Entity;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
-import org.apache.gravitino.Namespace;
-import org.apache.gravitino.TestColumn;
-import org.apache.gravitino.authorization.AccessControlManager;
+import org.apache.gravitino.authorization.AuthorizationUtils;
import org.apache.gravitino.authorization.Owner;
import org.apache.gravitino.authorization.OwnerDispatcher;
import org.apache.gravitino.catalog.CatalogManager;
import org.apache.gravitino.catalog.TableDispatcher;
-import org.apache.gravitino.catalog.TestOperationDispatcher;
-import org.apache.gravitino.catalog.TestTableOperationDispatcher;
-import org.apache.gravitino.connector.BaseCatalog;
-import org.apache.gravitino.connector.authorization.AuthorizationPlugin;
import org.apache.gravitino.connector.capability.Capability;
import org.apache.gravitino.connector.capability.CapabilityResult;
-import org.apache.gravitino.lock.LockManager;
import org.apache.gravitino.rel.Column;
import org.apache.gravitino.rel.Table;
import org.apache.gravitino.rel.TableChange;
-import org.apache.gravitino.rel.expressions.NamedReference;
-import org.apache.gravitino.rel.expressions.distributions.Distribution;
import org.apache.gravitino.rel.expressions.distributions.Distributions;
-import org.apache.gravitino.rel.expressions.distributions.Strategy;
-import org.apache.gravitino.rel.expressions.literals.Literals;
import org.apache.gravitino.rel.expressions.sorts.SortOrder;
-import org.apache.gravitino.rel.expressions.sorts.SortOrders;
import org.apache.gravitino.rel.expressions.transforms.Transform;
-import org.apache.gravitino.rel.expressions.transforms.Transforms;
import org.apache.gravitino.rel.indexes.Index;
-import org.apache.gravitino.rel.indexes.Indexes;
-import org.apache.gravitino.rel.partitions.Partitions;
-import org.apache.gravitino.rel.partitions.RangePartition;
-import org.apache.gravitino.rel.types.Types;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
+import org.mockito.MockedStatic;
import org.mockito.Mockito;
-public class TestTableHookDispatcher extends TestOperationDispatcher {
+public class TestTableHookDispatcher {
- private static TableHookDispatcher tableHookDispatcher;
- private static SchemaHookDispatcher schemaHookDispatcher;
- private static AccessControlManager accessControlManager =
- Mockito.mock(AccessControlManager.class);
- private static AuthorizationPlugin authorizationPlugin;
-
- @BeforeAll
- public static void initialize() throws Exception {
- TestTableOperationDispatcher.initialize();
-
- tableHookDispatcher =
- new
TableHookDispatcher(TestTableOperationDispatcher.getTableOperationDispatcher());
- schemaHookDispatcher =
- new
SchemaHookDispatcher(TestTableOperationDispatcher.getSchemaOperationDispatcher());
-
- FieldUtils.writeField(
- GravitinoEnv.getInstance(), "accessControlDispatcher",
accessControlManager, true);
- catalogManager = Mockito.mock(CatalogManager.class);
- FieldUtils.writeField(GravitinoEnv.getInstance(), "catalogManager",
catalogManager, true);
- BaseCatalog catalog = Mockito.mock(BaseCatalog.class);
- Mockito.when(catalog.capability()).thenReturn(Capability.DEFAULT);
- CatalogManager.CatalogWrapper catalogWrapper =
- Mockito.mock(CatalogManager.CatalogWrapper.class);
- Mockito.when(catalogWrapper.catalog()).thenReturn(catalog);
- Mockito.when(catalogWrapper.capabilities()).thenReturn(Capability.DEFAULT);
-
- Mockito.when(catalogManager.loadCatalog(any())).thenReturn(catalog);
-
Mockito.when(catalogManager.loadCatalogAndWrap(any())).thenReturn(catalogWrapper);
- authorizationPlugin = Mockito.mock(AuthorizationPlugin.class);
-
Mockito.when(catalog.getAuthorizationPlugin()).thenReturn(authorizationPlugin);
- }
+ private static final String METALAKE = "metalake";
+ private static final String CATALOG = "catalog";
@Test
public void testDropAuthorizationPrivilege() {
- Namespace tableNs = Namespace.of(metalake, catalog, "schema1123");
- Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
- schemaHookDispatcher.createSchema(NameIdentifier.of(tableNs.levels()),
"comment", props);
-
- NameIdentifier tableIdent = NameIdentifier.of(tableNs, "tableNAME");
- Column[] columns =
- new Column[] {
- TestColumn.builder()
- .withName("colNAME1")
- .withPosition(0)
- .withType(Types.StringType.get())
- .build(),
- TestColumn.builder()
- .withName("colNAME2")
- .withPosition(1)
- .withType(Types.StringType.get())
- .build()
- };
- RangePartition assignedPartition =
- Partitions.range(
- "partition_V1",
- Literals.stringLiteral("value1"),
- Literals.stringLiteral("value2"),
- null);
- Transform[] transforms =
- new Transform[] {
- Transforms.range(
- new String[] {columns[0].name()}, new RangePartition[]
{assignedPartition})
- };
- Distribution distribution =
- Distributions.fields(Strategy.HASH, 5, new String[]
{columns[0].name()});
- SortOrder[] sortOrders =
- new SortOrder[]
{SortOrders.ascending(NamedReference.field(columns[0].name()))};
- Index[] indexes = new Index[] {Indexes.primary("index1", new String[][]
{{columns[0].name()}})};
- tableHookDispatcher.createTable(
- tableIdent, columns, "comment", props, transforms, distribution,
sortOrders, indexes);
-
- withMockedAuthorizationUtils(
- () -> {
- tableHookDispatcher.dropTable(tableIdent);
- Config config = Mockito.mock(Config.class);
- Mockito.when(config.get(SERVICE_ADMINS))
- .thenReturn(Lists.newArrayList("admin1", "admin2"));
-
Mockito.when(config.get(ENTITY_STORE)).thenReturn(RELATIONAL_ENTITY_STORE);
- Mockito.when(config.get(ENTITY_RELATIONAL_STORE))
- .thenReturn(DEFAULT_ENTITY_RELATIONAL_STORE);
- Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_URL))
- .thenReturn(
-
String.format("jdbc:h2:file:%s;DB_CLOSE_DELAY=-1;MODE=MYSQL", "/tmp/testdb"));
- Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER))
- .thenReturn("org.h2.Driver");
-
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_MAX_CONNECTIONS)).thenReturn(100);
-
Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_WAIT_MILLISECONDS))
- .thenReturn(1000L);
-
Mockito.when(config.get(STORE_TRANSACTION_MAX_SKEW_TIME)).thenReturn(1000L);
- Mockito.when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60
* 1000L);
-
Mockito.when(config.get(ENTITY_CHANGE_LOG_POLL_INTERVAL_SECS)).thenReturn(3L);
-
Mockito.when(config.get(ENTITY_CHANGE_LOG_RETENTION_SECS)).thenReturn(24 * 60 *
60L);
-
Mockito.when(config.get(ENTITY_CHANGE_LOG_CLEANUP_INTERVAL_SECS)).thenReturn(60
* 60L);
- Mockito.when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L);
-
Mockito.when(config.get(CATALOG_CACHE_EVICTION_INTERVAL_MS)).thenReturn(1000L);
-
Mockito.doReturn(100000L).when(config).get(TREE_LOCK_MAX_NODE_IN_MEMORY);
-
Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY);
- Mockito.doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL);
- try {
- FieldUtils.writeField(
- GravitinoEnv.getInstance(), "lockManager", new
LockManager(config), true);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- schemaHookDispatcher.dropSchema(NameIdentifier.of(tableNs.levels()),
true);
- });
+ TableDispatcher dispatcher = Mockito.mock(TableDispatcher.class);
+ TableHookDispatcher hook =
+ new TableHookDispatcher(dispatcher, () -> null,
Mockito.mock(CatalogManager.class));
+ NameIdentifier ident = NameIdentifier.of(METALAKE, CATALOG, "schema",
"table");
+ List<String> locations = ImmutableList.of("/test");
+ Mockito.when(dispatcher.dropTable(ident)).thenReturn(true);
+
+ try (MockedStatic<AuthorizationUtils> authorizationUtils =
+ Mockito.mockStatic(AuthorizationUtils.class)) {
+ authorizationUtils
+ .when(() -> AuthorizationUtils.getMetadataObjectLocation(ident,
Entity.EntityType.TABLE))
+ .thenReturn(locations);
+
+ assertTrue(hook.dropTable(ident));
+
+ authorizationUtils.verify(
+ () ->
+ AuthorizationUtils.authorizationPluginRemovePrivileges(
+ ident, Entity.EntityType.TABLE, locations));
+ }
}
@Test
public void testCreateTableSetsOwnerWithNormalizedIdentifier() throws
Exception {
- // Self-contained: use a fresh hook with a directly-mocked TableDispatcher
and a case-
- // insensitive catalog so we can verify the helper passes a normalized
ident to setOwner.
- CatalogManager savedCatalogManager =
GravitinoEnv.getInstance().catalogManager();
- OwnerDispatcher savedOwnerDispatcher =
GravitinoEnv.getInstance().ownerDispatcher();
-
- CatalogManager mockCatalogManager = Mockito.mock(CatalogManager.class);
- CatalogManager.CatalogWrapper mockWrapper =
Mockito.mock(CatalogManager.CatalogWrapper.class);
- Mockito.when(mockWrapper.capabilities()).thenReturn(new
CaseInsensitiveCapability());
-
Mockito.when(mockCatalogManager.loadCatalogAndWrap(any())).thenReturn(mockWrapper);
-
- OwnerDispatcher mockOwnerDispatcher = Mockito.mock(OwnerDispatcher.class);
- TableDispatcher mockTableDispatcher = Mockito.mock(TableDispatcher.class);
- Mockito.when(
- mockTableDispatcher.createTable(any(), any(), any(), any(), any(),
any(), any(), any()))
- .thenReturn(Mockito.mock(Table.class));
-
- FieldUtils.writeField(GravitinoEnv.getInstance(), "catalogManager",
mockCatalogManager, true);
- FieldUtils.writeField(GravitinoEnv.getInstance(), "ownerDispatcher",
mockOwnerDispatcher, true);
-
- try {
- TableHookDispatcher localHook = new
TableHookDispatcher(mockTableDispatcher);
- NameIdentifier ident = NameIdentifier.of(metalake, catalog,
"SCHEMA_NORM", "MY_TABLE");
- localHook.createTable(
- ident,
- new Column[0],
- "comment",
- ImmutableMap.of(),
- new Transform[0],
- Distributions.NONE,
- new SortOrder[0],
- new Index[0]);
-
- ArgumentCaptor<MetadataObject> captor =
ArgumentCaptor.forClass(MetadataObject.class);
- Mockito.verify(mockOwnerDispatcher)
- .setOwner(eq(metalake), captor.capture(), any(),
eq(Owner.Type.USER));
- Assertions.assertEquals(
- "my_table",
- captor.getValue().name(),
- "Table name passed to setOwner must be lowercased by
Capability.Scope.TABLE normalization");
- Assertions.assertEquals(
- catalog + ".schema_norm",
- captor.getValue().parent(),
- "Table parent (catalog.schema) must have its schema component
lowercased by"
- + " Capability.Scope.TABLE namespace normalization");
- } finally {
- FieldUtils.writeField(
- GravitinoEnv.getInstance(), "catalogManager", savedCatalogManager,
true);
- FieldUtils.writeField(
- GravitinoEnv.getInstance(), "ownerDispatcher", savedOwnerDispatcher,
true);
- }
+ CatalogManager catalogManager = Mockito.mock(CatalogManager.class);
+ CatalogManager.CatalogWrapper wrapper =
Mockito.mock(CatalogManager.CatalogWrapper.class);
+ Mockito.when(wrapper.capabilities()).thenReturn(new
CaseInsensitiveCapability());
+ Mockito.when(catalogManager.loadCatalogAndWrap(any())).thenReturn(wrapper);
+
+ OwnerDispatcher ownerDispatcher = Mockito.mock(OwnerDispatcher.class);
+ TableDispatcher dispatcher = Mockito.mock(TableDispatcher.class);
+ Table createdTable = Mockito.mock(Table.class);
+ Mockito.when(dispatcher.createTable(any(), any(), any(), any(), any(),
any(), any(), any()))
+ .thenReturn(createdTable);
+ TableHookDispatcher hook =
+ new TableHookDispatcher(dispatcher, () -> ownerDispatcher,
catalogManager);
+ NameIdentifier ident = NameIdentifier.of(METALAKE, CATALOG, "SCHEMA_NORM",
"MY_TABLE");
+
+ assertSame(
+ createdTable,
+ hook.createTable(
+ ident,
+ new Column[0],
+ "comment",
+ ImmutableMap.of(),
+ new Transform[0],
+ Distributions.NONE,
+ new SortOrder[0],
+ new Index[0]));
+
+ ArgumentCaptor<MetadataObject> captor =
ArgumentCaptor.forClass(MetadataObject.class);
+ Mockito.verify(ownerDispatcher)
+ .setOwner(eq(METALAKE), captor.capture(), any(), eq(Owner.Type.USER));
+ assertEquals("my_table", captor.getValue().name());
+ assertEquals(CATALOG + ".schema_norm", captor.getValue().parent());
}
@Test
- public void testCreateTableThrowsWhenSetOwnerFails() throws
IllegalAccessException {
- // Save the original ownerDispatcher so we can restore it in the finally
block instead of
- // wiping it to null and leaking that into other tests in the suite.
- OwnerDispatcher savedOwnerDispatcher =
GravitinoEnv.getInstance().ownerDispatcher();
-
- // Create the schema first with the existing (non-throwing)
ownerDispatcher, then swap to the
- // throwing mock only for the table create we actually want to exercise.
Otherwise the throwing
- // mock would fire during schema creation and we would never reach the
table call.
- Namespace tableNs = Namespace.of(metalake, catalog, "schema_owner_fail");
- Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
- schemaHookDispatcher.createSchema(NameIdentifier.of(tableNs.levels()),
"comment", props);
+ public void testCreateTableSkipsOwnerWhenAuthorizationDisabled() {
+ CatalogManager catalogManager = Mockito.mock(CatalogManager.class);
+ TableDispatcher dispatcher = Mockito.mock(TableDispatcher.class);
+ Table createdTable = Mockito.mock(Table.class);
+ Mockito.when(dispatcher.createTable(any(), any(), any(), any(), any(),
any(), any(), any()))
+ .thenReturn(createdTable);
+ TableHookDispatcher hook = new TableHookDispatcher(dispatcher, () -> null,
catalogManager);
+
+ assertSame(
+ createdTable,
+ hook.createTable(
+ NameIdentifier.of(METALAKE, CATALOG, "schema", "table"),
+ new Column[0],
+ "comment",
+ ImmutableMap.of(),
+ new Transform[0],
+ Distributions.NONE,
+ new SortOrder[0],
+ new Index[0]));
+
+ Mockito.verifyNoInteractions(catalogManager);
+ }
- OwnerDispatcher mockOwnerDispatcher = Mockito.mock(OwnerDispatcher.class);
+ @Test
+ public void testCreateTableThrowsWhenSetOwnerFails() throws Exception {
+ OwnerDispatcher ownerDispatcher = Mockito.mock(OwnerDispatcher.class);
Mockito.doThrow(new RuntimeException("Set owner failed"))
- .when(mockOwnerDispatcher)
+ .when(ownerDispatcher)
.setOwner(any(), any(), any(), any());
- FieldUtils.writeField(GravitinoEnv.getInstance(), "ownerDispatcher",
mockOwnerDispatcher, true);
-
- try {
- NameIdentifier tableIdent = NameIdentifier.of(tableNs,
"table_owner_fail");
- Column[] columns =
- new Column[] {
- TestColumn.builder()
- .withName("col1")
- .withPosition(0)
- .withType(Types.StringType.get())
- .build()
- };
- RuntimeException thrown =
- Assertions.assertThrows(
- RuntimeException.class,
- () ->
- tableHookDispatcher.createTable(
- tableIdent,
- columns,
- "comment",
- props,
- new Transform[0],
- Distributions.NONE,
- new SortOrder[0],
- new Index[0]));
- Assertions.assertEquals("Set owner failed", thrown.getMessage());
- } finally {
- FieldUtils.writeField(
- GravitinoEnv.getInstance(), "ownerDispatcher", savedOwnerDispatcher,
true);
- }
+ TableDispatcher dispatcher = Mockito.mock(TableDispatcher.class);
+ Mockito.when(dispatcher.createTable(any(), any(), any(), any(), any(),
any(), any(), any()))
+ .thenReturn(Mockito.mock(Table.class));
+ CatalogManager catalogManager = Mockito.mock(CatalogManager.class);
+ CatalogManager.CatalogWrapper wrapper =
Mockito.mock(CatalogManager.CatalogWrapper.class);
+ Mockito.when(wrapper.capabilities()).thenReturn(Capability.DEFAULT);
+ Mockito.when(catalogManager.loadCatalogAndWrap(any())).thenReturn(wrapper);
+ TableHookDispatcher hook =
+ new TableHookDispatcher(dispatcher, () -> ownerDispatcher,
catalogManager);
+
+ RuntimeException thrown =
+ assertThrows(
+ RuntimeException.class,
+ () ->
+ hook.createTable(
+ NameIdentifier.of(METALAKE, CATALOG, "schema", "table"),
+ new Column[0],
+ "comment",
+ ImmutableMap.of(),
+ new Transform[0],
+ Distributions.NONE,
+ new SortOrder[0],
+ new Index[0]));
+
+ assertEquals("Set owner failed", thrown.getMessage());
}
@Test
public void testRenameAuthorizationPrivilege() {
- Namespace tableNs = Namespace.of(metalake, catalog, "schema1124");
- Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
- schemaHookDispatcher.createSchema(NameIdentifier.of(tableNs.levels()),
"comment", props);
-
- NameIdentifier tableIdent = NameIdentifier.of(tableNs, "tableNAME");
- Column[] columns =
- new Column[] {
- TestColumn.builder()
- .withName("colNAME1")
- .withPosition(0)
- .withType(Types.StringType.get())
- .build(),
- TestColumn.builder()
- .withName("colNAME2")
- .withPosition(1)
- .withType(Types.StringType.get())
- .build()
- };
- RangePartition assignedPartition =
- Partitions.range(
- "partition_V1",
- Literals.stringLiteral("value1"),
- Literals.stringLiteral("value2"),
- null);
- Transform[] transforms =
- new Transform[] {
- Transforms.range(
- new String[] {columns[0].name()}, new RangePartition[]
{assignedPartition})
- };
- Distribution distribution =
- Distributions.fields(Strategy.HASH, 5, new String[]
{columns[0].name()});
- SortOrder[] sortOrders =
- new SortOrder[]
{SortOrders.ascending(NamedReference.field(columns[0].name()))};
- Index[] indexes = new Index[] {Indexes.primary("index1", new String[][]
{{columns[0].name()}})};
- tableHookDispatcher.createTable(
- tableIdent, columns, "comment", props, transforms, distribution,
sortOrders, indexes);
-
- Mockito.reset(authorizationPlugin);
- TableChange setChange = TableChange.setProperty("k1", "v1");
- tableHookDispatcher.alterTable(tableIdent, setChange);
- Mockito.verify(authorizationPlugin,
Mockito.never()).onMetadataUpdated(any());
-
- Mockito.reset(authorizationPlugin);
+ TableDispatcher dispatcher = Mockito.mock(TableDispatcher.class);
+ TableHookDispatcher hook =
+ new TableHookDispatcher(dispatcher, () -> null,
Mockito.mock(CatalogManager.class));
+ NameIdentifier ident = NameIdentifier.of(METALAKE, CATALOG, "schema",
"table");
+ Table alteredTable = Mockito.mock(Table.class);
+ TableChange setChange = TableChange.setProperty("key", "value");
TableChange renameChange = TableChange.rename("newName");
- tableHookDispatcher.alterTable(tableIdent, renameChange);
- Mockito.verify(authorizationPlugin).onMetadataUpdated(any());
+ List<String> locations = ImmutableList.of("/test");
+ Mockito.when(dispatcher.alterTable(ident,
setChange)).thenReturn(alteredTable);
+ Mockito.when(dispatcher.alterTable(ident,
renameChange)).thenReturn(alteredTable);
+
+ try (MockedStatic<AuthorizationUtils> authorizationUtils =
+ Mockito.mockStatic(AuthorizationUtils.class)) {
+ assertSame(alteredTable, hook.alterTable(ident, setChange));
+ authorizationUtils.verifyNoInteractions();
+
+ authorizationUtils
+ .when(() -> AuthorizationUtils.getMetadataObjectLocation(ident,
Entity.EntityType.TABLE))
+ .thenReturn(locations);
+ assertSame(alteredTable, hook.alterTable(ident, renameChange));
+ authorizationUtils.verify(
+ () ->
+ AuthorizationUtils.authorizationPluginRenamePrivileges(
+ ident, Entity.EntityType.TABLE, "newName", locations));
+ }
}
private static class CaseInsensitiveCapability implements Capability {