This is an automated email from the ASF dual-hosted git repository.
mchades 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 7f59be2615 [Cherry-pick to branch-1.3] [#12501][#12500] feat(core):
Support FUNCTION and VIEW metadata object policies (#12503) (#12904)
7f59be2615 is described below
commit 7f59be26157fda4020ab846cc074ce904c36c14f
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Sep 4 17:02:31 2026 +0800
[Cherry-pick to branch-1.3] [#12501][#12500] feat(core): Support FUNCTION
and VIEW metadata object policies (#12503) (#12904)
**Cherry-pick Information:**
- Original commit: 3afc9f16832029c7ad95f394250bd56b77d42220
- Target branch: `branch-1.3`
- Status: ✅ Conflicts resolved manually
**Resolution:**
- Kept the existing `branch-1.3` deletion timestamp implementation and
added only the mapper imports required by the VIEW/FUNCTION cleanup SQL.
- Preserved the original FUNCTION/VIEW policy association, direct
cleanup, and catalog/schema cascade cleanup without introducing the
main-only `DatabaseTimeSQL` dependency.
**Validation:**
- `./gradlew :core:spotlessApply :clients:client-java:spotlessApply
:docs:spotlessApply --no-daemon`
- `./gradlew :core:test --tests
org.apache.gravitino.policy.TestPolicyManager -PskipITs
-PskipDockerTests=true --no-daemon`
- `./gradlew :docs:build --no-daemon`
- `./gradlew :clients:client-java:test --tests
org.apache.gravitino.client.integration.test.PolicyIT
-PskipDockerTests=false -PtestMode=embedded --no-daemon` (14 tests, 0
failures, 0 errors)
- Conflict-marker scan and `git diff --check`
---------
Co-authored-by: Arvin <[email protected]>
Co-authored-by: mchades <[email protected]>
Co-authored-by: Jerry Shao <[email protected]>
---
.../client/integration/test/PolicyIT.java | 742 ++++++++++++++++++++-
.../org/apache/gravitino/policy/PolicyManager.java | 4 +-
.../PolicyMetadataObjectRelBaseSQLProvider.java | 22 +
.../PolicyMetadataObjectRelPostgreSQLProvider.java | 23 +
.../relational/service/FunctionMetaService.java | 6 +
.../relational/service/ViewMetaService.java | 6 +
.../apache/gravitino/policy/TestPolicyManager.java | 79 +++
docs/manage-policies-in-gravitino.md | 2 +-
docs/open-api/policies.yaml | 6 +-
docs/policies.md | 10 +-
10 files changed, 877 insertions(+), 23 deletions(-)
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/PolicyIT.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/PolicyIT.java
index d20e38fe8f..403bd05c2a 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/PolicyIT.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/PolicyIT.java
@@ -24,17 +24,33 @@ import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Schema;
+import org.apache.gravitino.client.ErrorHandlers;
import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.client.HTTPClient;
+import org.apache.gravitino.client.RESTClient;
+import org.apache.gravitino.dto.requests.PoliciesAssociateRequest;
+import org.apache.gravitino.dto.responses.NameListResponse;
+import org.apache.gravitino.dto.responses.PolicyListResponse;
+import org.apache.gravitino.dto.responses.PolicyResponse;
import org.apache.gravitino.dto.tag.MetadataObjectDTO;
import org.apache.gravitino.exceptions.NoSuchPolicyException;
import org.apache.gravitino.exceptions.PolicyAlreadyAssociatedException;
import org.apache.gravitino.exceptions.PolicyAlreadyExistsException;
+import org.apache.gravitino.function.Function;
+import org.apache.gravitino.function.FunctionDefinition;
+import org.apache.gravitino.function.FunctionDefinitions;
+import org.apache.gravitino.function.FunctionImpl;
+import org.apache.gravitino.function.FunctionImpls;
+import org.apache.gravitino.function.FunctionParam;
+import org.apache.gravitino.function.FunctionParams;
+import org.apache.gravitino.function.FunctionType;
import org.apache.gravitino.integration.test.container.ContainerSuite;
import org.apache.gravitino.integration.test.container.HiveContainer;
import org.apache.gravitino.integration.test.util.BaseIT;
@@ -44,9 +60,14 @@ import org.apache.gravitino.policy.Policy;
import org.apache.gravitino.policy.PolicyChange;
import org.apache.gravitino.policy.PolicyContent;
import org.apache.gravitino.policy.PolicyContents;
+import org.apache.gravitino.policy.SupportsPolicies;
import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Dialects;
+import org.apache.gravitino.rel.SQLRepresentation;
import org.apache.gravitino.rel.Table;
+import org.apache.gravitino.rel.View;
import org.apache.gravitino.rel.types.Types;
+import org.apache.gravitino.rest.RESTUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
@@ -65,6 +86,11 @@ public class PolicyIT extends BaseIT {
private static Catalog relationalCatalog;
private static Schema schema;
private static Table table;
+ private static View view;
+ private static Function function;
+ private static SupportsPolicies viewPolicyOperations;
+ private static SupportsPolicies functionPolicyOperations;
+ private static RESTClient policyRestClient;
private static Catalog modelCatalog;
private static Schema modelSchema;
@@ -73,6 +99,8 @@ public class PolicyIT extends BaseIT {
@BeforeAll
public void setUp() {
containerSuite.startHiveContainer();
+ policyRestClient =
+
closer.register(HTTPClient.builder(Collections.emptyMap()).uri(serverUri).build());
String hmsUri =
String.format(
"thrift://%s:%d",
@@ -116,6 +144,61 @@ public class PolicyIT extends BaseIT {
"comment",
Collections.emptyMap());
+ // Create view
+ String viewName = GravitinoITUtils.genRandomName("policy_it_view");
+ Assertions.assertFalse(
+
relationalCatalog.asViewCatalog().viewExists(NameIdentifier.of(schemaName,
viewName)));
+ view =
+ relationalCatalog
+ .asViewCatalog()
+ .createView(
+ NameIdentifier.of(schemaName, viewName),
+ "comment",
+ new Column[] {
+ Column.of("col1", Types.IntegerType.get()),
+ Column.of("col2", Types.StringType.get())
+ },
+ new SQLRepresentation[] {
+ SQLRepresentation.builder()
+ .withDialect(Dialects.HIVE)
+ .withSql("SELECT col1, col2 FROM " + table.name())
+ .build()
+ },
+ null,
+ null,
+ Collections.emptyMap());
+ viewPolicyOperations =
+ policyOperations(
+ relationalCatalog, schema, view.name(), MetadataObject.Type.VIEW,
policyRestClient);
+
+ // Create function
+ String functionName = GravitinoITUtils.genRandomName("policy_it_function");
+ Assertions.assertFalse(
+ relationalCatalog
+ .asFunctionCatalog()
+ .functionExists(NameIdentifier.of(schemaName, functionName)));
+ FunctionParam param = FunctionParams.of("x", Types.IntegerType.get());
+ FunctionImpl impl = FunctionImpls.ofSql(FunctionImpl.RuntimeType.SPARK,
"SELECT x + 1");
+ FunctionDefinition definition =
+ FunctionDefinitions.of(
+ new FunctionParam[] {param}, Types.IntegerType.get(), new
FunctionImpl[] {impl});
+ function =
+ relationalCatalog
+ .asFunctionCatalog()
+ .registerFunction(
+ NameIdentifier.of(schemaName, functionName),
+ "comment",
+ FunctionType.SCALAR,
+ true,
+ new FunctionDefinition[] {definition});
+ functionPolicyOperations =
+ policyOperations(
+ relationalCatalog,
+ schema,
+ function.name(),
+ MetadataObject.Type.FUNCTION,
+ policyRestClient);
+
// Create model catalog
String modelCatalogName =
GravitinoITUtils.genRandomName("policy_it_model_catalog");
Assertions.assertFalse(metalake.catalogExists(modelCatalogName));
@@ -142,6 +225,10 @@ public class PolicyIT extends BaseIT {
@AfterAll
public void tearDown() {
+ relationalCatalog
+ .asFunctionCatalog()
+ .dropFunction(NameIdentifier.of(schema.name(), function.name()));
+
relationalCatalog.asViewCatalog().dropView(NameIdentifier.of(schema.name(),
view.name()));
relationalCatalog.asTableCatalog().dropTable(NameIdentifier.of(schema.name(),
table.name()));
relationalCatalog.asSchemas().dropSchema(schema.name(), true);
metalake.dropCatalog(relationalCatalog.name(), true);
@@ -166,6 +253,12 @@ public class PolicyIT extends BaseIT {
@AfterEach
public void cleanUp() {
+ String[] functionPolicies = functionPolicyOperations.listPolicies();
+ functionPolicyOperations.associatePolicies(null, functionPolicies);
+
+ String[] viewPolicies = viewPolicyOperations.listPolicies();
+ viewPolicyOperations.associatePolicies(null, viewPolicies);
+
String[] tablePolicies = table.supportsPolicies().listPolicies();
table.supportsPolicies().associatePolicies(null, tablePolicies);
@@ -395,9 +488,13 @@ public class PolicyIT extends BaseIT {
@Test
public void testAssociatePoliciesToCatalog() {
Policy policy1 =
-
createCustomPolicy(GravitinoITUtils.genRandomName("policy_it_catalog_policy1"));
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_catalog_policy1"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
Policy policy2 =
-
createCustomPolicy(GravitinoITUtils.genRandomName("policy_it_catalog_policy2"));
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_catalog_policy2"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
// Test associate policies to catalog
String[] policies =
@@ -494,8 +591,14 @@ public class PolicyIT extends BaseIT {
@Test
public void testAssociatePoliciesToSchema() {
- Policy policy1 =
createCustomPolicy(GravitinoITUtils.genRandomName("policy_it_schema_policy1"));
- Policy policy2 =
createCustomPolicy(GravitinoITUtils.genRandomName("policy_it_schema_policy2"));
+ Policy policy1 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_schema_policy1"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
+ Policy policy2 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_schema_policy2"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
// Associate policies to catalog
relationalCatalog.supportsPolicies().associatePolicies(new String[]
{policy1.name()}, null);
@@ -655,9 +758,18 @@ public class PolicyIT extends BaseIT {
@Test
public void testAssociateAndDeletePolicies() {
- Policy policy1 =
createCustomPolicy(GravitinoITUtils.genRandomName("policy_it_policy1"));
- Policy policy2 =
createCustomPolicy(GravitinoITUtils.genRandomName("policy_it_policy2"));
- Policy policy3 =
createCustomPolicy(GravitinoITUtils.genRandomName("policy_it_policy3"));
+ Policy policy1 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_policy1"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
+ Policy policy2 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_policy2"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
+ Policy policy3 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_policy3"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
String[] associatedPolicies =
relationalCatalog
@@ -689,9 +801,18 @@ public class PolicyIT extends BaseIT {
@Test
public void testAssociatePoliciesToModel() {
- Policy policy1 =
createCustomPolicy(GravitinoITUtils.genRandomName("policy_it_model_policy1"));
- Policy policy2 =
createCustomPolicy(GravitinoITUtils.genRandomName("policy_it_model_policy2"));
- Policy policy3 =
createCustomPolicy(GravitinoITUtils.genRandomName("policy_it_model_policy3"));
+ Policy policy1 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_model_policy1"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
+ Policy policy2 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_model_policy2"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
+ Policy policy3 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_model_policy3"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
// Associate policies to catalog
modelCatalog.supportsPolicies().associatePolicies(new String[]
{policy1.name()}, null);
@@ -759,15 +880,606 @@ public class PolicyIT extends BaseIT {
MetadataObject.Type.MODEL,
policy3.associatedObjects().objects()[0].type());
}
- private Policy createCustomPolicy(String name) {
+ @Test
+ public void testAssociatePoliciesToView() {
+ Policy policy1 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_view_policy1"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
+ Policy policy2 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_view_policy2"),
+ ImmutableSet.of(MetadataObject.Type.SCHEMA));
+ Policy policy3 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_view_policy3"),
+ ImmutableSet.of(MetadataObject.Type.VIEW));
+
+ // Associate policies to catalog
+ relationalCatalog.supportsPolicies().associatePolicies(new String[]
{policy1.name()}, null);
+
+ // Associate policies to schema
+ schema.supportsPolicies().associatePolicies(new String[] {policy2.name()},
null);
+
+ // Test associate policies to view
+ String[] policies = viewPolicyOperations.associatePolicies(new String[]
{policy3.name()}, null);
+
+ Assertions.assertEquals(1, policies.length);
+ Assertions.assertEquals(policy3.name(), policies[0]);
+
+ // Test list associated policies for view
+ String[] policies1 = viewPolicyOperations.listPolicies();
+ Assertions.assertEquals(3, policies1.length);
+ Set<String> policyNames = Sets.newHashSet(policies1);
+ Assertions.assertTrue(policyNames.contains(policy1.name()));
+ Assertions.assertTrue(policyNames.contains(policy2.name()));
+ Assertions.assertTrue(policyNames.contains(policy3.name()));
+
+ // Test list associated policies with details for view
+ Policy[] policies2 = viewPolicyOperations.listPolicyInfos();
+ Assertions.assertEquals(3, policies2.length);
+
+ Set<String> nonInheritedPolicies =
+ Arrays.stream(policies2)
+ .filter(policy -> !policy.inherited().get())
+ .map(Policy::name)
+ .collect(Collectors.toSet());
+ Set<String> inheritedPolicies =
+ Arrays.stream(policies2)
+ .filter(policy -> policy.inherited().get())
+ .map(Policy::name)
+ .collect(Collectors.toSet());
+
+ Assertions.assertEquals(ImmutableSet.of(policy3.name()),
nonInheritedPolicies);
+ Assertions.assertEquals(ImmutableSet.of(policy1.name(), policy2.name()),
inheritedPolicies);
+
+ // Test get associated policy for view
+ Policy resultPolicy1 = viewPolicyOperations.getPolicy(policy1.name());
+ Assertions.assertEquals(policy1.name(), resultPolicy1.name());
+ Assertions.assertTrue(resultPolicy1.inherited().get());
+
+ Policy resultPolicy2 = viewPolicyOperations.getPolicy(policy2.name());
+ Assertions.assertEquals(policy2.name(), resultPolicy2.name());
+ Assertions.assertTrue(resultPolicy2.inherited().get());
+
+ Policy resultPolicy3 = viewPolicyOperations.getPolicy(policy3.name());
+ Assertions.assertEquals(policy3.name(), resultPolicy3.name());
+ Assertions.assertFalse(resultPolicy3.inherited().get());
+
+ // Test get objects associated with policy
+ Assertions.assertEquals(1, policy1.associatedObjects().count());
+ Assertions.assertEquals(
+ relationalCatalog.name(),
policy1.associatedObjects().objects()[0].name());
+ Assertions.assertEquals(
+ MetadataObject.Type.CATALOG,
policy1.associatedObjects().objects()[0].type());
+
+ Assertions.assertEquals(1, policy2.associatedObjects().count());
+ Assertions.assertEquals(schema.name(),
policy2.associatedObjects().objects()[0].name());
+ Assertions.assertEquals(
+ MetadataObject.Type.SCHEMA,
policy2.associatedObjects().objects()[0].type());
+
+ Assertions.assertEquals(1, policy3.associatedObjects().count());
+ Assertions.assertEquals(view.name(),
policy3.associatedObjects().objects()[0].name());
+ Assertions.assertEquals(
+ MetadataObject.Type.VIEW,
policy3.associatedObjects().objects()[0].type());
+
+ // Test disassociate policy from view
+ String[] policies3 =
+ viewPolicyOperations.associatePolicies(null, new String[]
{policy3.name()});
+ Assertions.assertEquals(0, policies3.length);
+ Assertions.assertEquals(
+ ImmutableSet.of(policy1.name(), policy2.name()),
+ ImmutableSet.copyOf(viewPolicyOperations.listPolicies()));
+ Assertions.assertEquals(0, policy3.associatedObjects().count());
+ }
+
+ @Test
+ public void testAssociatePoliciesToFunction() {
+ Policy policy1 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_func_policy1"),
+ ImmutableSet.of(MetadataObject.Type.CATALOG));
+ Policy policy2 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_func_policy2"),
+ ImmutableSet.of(MetadataObject.Type.SCHEMA));
+ Policy policy3 =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_func_policy3"),
+ ImmutableSet.of(MetadataObject.Type.FUNCTION));
+
+ // Associate policies to catalog
+ relationalCatalog.supportsPolicies().associatePolicies(new String[]
{policy1.name()}, null);
+
+ // Associate policies to schema
+ schema.supportsPolicies().associatePolicies(new String[] {policy2.name()},
null);
+
+ // Test associate policies to function
+ String[] policies =
+ functionPolicyOperations.associatePolicies(new String[]
{policy3.name()}, null);
+
+ Assertions.assertEquals(1, policies.length);
+ Assertions.assertEquals(policy3.name(), policies[0]);
+
+ // Test list associated policies for function
+ String[] policies1 = functionPolicyOperations.listPolicies();
+ Assertions.assertEquals(3, policies1.length);
+ Set<String> policyNames = Sets.newHashSet(policies1);
+ Assertions.assertTrue(policyNames.contains(policy1.name()));
+ Assertions.assertTrue(policyNames.contains(policy2.name()));
+ Assertions.assertTrue(policyNames.contains(policy3.name()));
+
+ // Test list associated policies with details for function
+ Policy[] policies2 = functionPolicyOperations.listPolicyInfos();
+ Assertions.assertEquals(3, policies2.length);
+
+ Set<String> nonInheritedPolicies =
+ Arrays.stream(policies2)
+ .filter(policy -> !policy.inherited().get())
+ .map(Policy::name)
+ .collect(Collectors.toSet());
+ Set<String> inheritedPolicies =
+ Arrays.stream(policies2)
+ .filter(policy -> policy.inherited().get())
+ .map(Policy::name)
+ .collect(Collectors.toSet());
+
+ Assertions.assertEquals(ImmutableSet.of(policy3.name()),
nonInheritedPolicies);
+ Assertions.assertEquals(ImmutableSet.of(policy1.name(), policy2.name()),
inheritedPolicies);
+
+ // Test get associated policy for function
+ Policy resultPolicy1 = functionPolicyOperations.getPolicy(policy1.name());
+ Assertions.assertEquals(policy1.name(), resultPolicy1.name());
+ Assertions.assertTrue(resultPolicy1.inherited().get());
+
+ Policy resultPolicy2 = functionPolicyOperations.getPolicy(policy2.name());
+ Assertions.assertEquals(policy2.name(), resultPolicy2.name());
+ Assertions.assertTrue(resultPolicy2.inherited().get());
+
+ Policy resultPolicy3 = functionPolicyOperations.getPolicy(policy3.name());
+ Assertions.assertEquals(policy3.name(), resultPolicy3.name());
+ Assertions.assertFalse(resultPolicy3.inherited().get());
+
+ // Test get objects associated with policy
+ Assertions.assertEquals(1, policy1.associatedObjects().count());
+ Assertions.assertEquals(
+ relationalCatalog.name(),
policy1.associatedObjects().objects()[0].name());
+ Assertions.assertEquals(
+ MetadataObject.Type.CATALOG,
policy1.associatedObjects().objects()[0].type());
+
+ Assertions.assertEquals(1, policy2.associatedObjects().count());
+ Assertions.assertEquals(schema.name(),
policy2.associatedObjects().objects()[0].name());
+ Assertions.assertEquals(
+ MetadataObject.Type.SCHEMA,
policy2.associatedObjects().objects()[0].type());
+
+ Assertions.assertEquals(1, policy3.associatedObjects().count());
+ Assertions.assertEquals(function.name(),
policy3.associatedObjects().objects()[0].name());
+ Assertions.assertEquals(
+ MetadataObject.Type.FUNCTION,
policy3.associatedObjects().objects()[0].type());
+
+ // Test disassociate policy from function
+ String[] policies3 =
+ functionPolicyOperations.associatePolicies(null, new String[]
{policy3.name()});
+ Assertions.assertEquals(0, policies3.length);
+ Assertions.assertEquals(
+ ImmutableSet.of(policy1.name(), policy2.name()),
+ ImmutableSet.copyOf(functionPolicyOperations.listPolicies()));
+ Assertions.assertEquals(0, policy3.associatedObjects().count());
+ }
+
+ @Test
+ public void testCascadeDeleteView() {
+ // Create a policy that supports VIEW
+ Policy policy =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_cascade_view"),
+ ImmutableSet.of(MetadataObject.Type.VIEW));
+
+ // Create a view in the existing schema
+ String viewName = GravitinoITUtils.genRandomName("policy_it_cascade_view");
+ NameIdentifier viewIdent = NameIdentifier.of(schema.name(), viewName);
+
Assertions.assertFalse(relationalCatalog.asViewCatalog().viewExists(viewIdent));
+ View cascadeView =
+ relationalCatalog
+ .asViewCatalog()
+ .createView(
+ viewIdent,
+ "comment",
+ new Column[] {
+ Column.of("col1", Types.IntegerType.get()),
+ Column.of("col2", Types.StringType.get())
+ },
+ new SQLRepresentation[] {
+ SQLRepresentation.builder()
+ .withDialect(Dialects.HIVE)
+ .withSql("SELECT col1, col2 FROM " + table.name())
+ .build()
+ },
+ null,
+ null,
+ Collections.emptyMap());
+
+ // Associate the policy with the view
+ SupportsPolicies cascadeViewPolicyOperations =
+ policyOperations(
+ relationalCatalog,
+ schema,
+ cascadeView.name(),
+ MetadataObject.Type.VIEW,
+ policyRestClient);
+ cascadeViewPolicyOperations.associatePolicies(new String[]
{policy.name()}, null);
+
+ // Verify the policy is associated with the view
+ Policy fetchedPolicy = metalake.getPolicy(policy.name());
+ Assertions.assertEquals(1, fetchedPolicy.associatedObjects().count());
+ Assertions.assertEquals(
+ cascadeView.name(),
fetchedPolicy.associatedObjects().objects()[0].name());
+ Assertions.assertEquals(
+ MetadataObject.Type.VIEW,
fetchedPolicy.associatedObjects().objects()[0].type());
+
+ // Delete the view — this should cascade-delete policy relations
+
Assertions.assertTrue(relationalCatalog.asViewCatalog().dropView(viewIdent));
+
+ // Verify the policy's associated objects are cleaned up
+ fetchedPolicy = metalake.getPolicy(policy.name());
+ Assertions.assertEquals(0, fetchedPolicy.associatedObjects().count());
+ }
+
+ @Test
+ public void testCascadeDeleteFunction() {
+ // Create a policy that supports FUNCTION
+ Policy policy =
+ createCustomPolicy(
+ GravitinoITUtils.genRandomName("policy_it_cascade_func"),
+ ImmutableSet.of(MetadataObject.Type.FUNCTION));
+
+ // Create a function in the existing schema
+ String funcName = GravitinoITUtils.genRandomName("policy_it_cascade_func");
+ NameIdentifier funcIdent = NameIdentifier.of(schema.name(), funcName);
+
Assertions.assertFalse(relationalCatalog.asFunctionCatalog().functionExists(funcIdent));
+ FunctionParam param = FunctionParams.of("x", Types.IntegerType.get());
+ FunctionImpl impl = FunctionImpls.ofSql(FunctionImpl.RuntimeType.SPARK,
"SELECT x + 1");
+ FunctionDefinition definition =
+ FunctionDefinitions.of(
+ new FunctionParam[] {param}, Types.IntegerType.get(), new
FunctionImpl[] {impl});
+ Function cascadeFunc =
+ relationalCatalog
+ .asFunctionCatalog()
+ .registerFunction(
+ funcIdent,
+ "comment",
+ FunctionType.SCALAR,
+ true,
+ new FunctionDefinition[] {definition});
+
+ // Associate the policy with the function
+ SupportsPolicies cascadeFunctionPolicyOperations =
+ policyOperations(
+ relationalCatalog,
+ schema,
+ cascadeFunc.name(),
+ MetadataObject.Type.FUNCTION,
+ policyRestClient);
+ cascadeFunctionPolicyOperations.associatePolicies(new String[]
{policy.name()}, null);
+
+ // Verify the policy is associated with the function
+ Policy fetchedPolicy = metalake.getPolicy(policy.name());
+ Assertions.assertEquals(1, fetchedPolicy.associatedObjects().count());
+ Assertions.assertEquals(
+ cascadeFunc.name(),
fetchedPolicy.associatedObjects().objects()[0].name());
+ Assertions.assertEquals(
+ MetadataObject.Type.FUNCTION,
fetchedPolicy.associatedObjects().objects()[0].type());
+
+ // Delete the function — this should cascade-delete policy relations
+
Assertions.assertTrue(relationalCatalog.asFunctionCatalog().dropFunction(funcIdent));
+
+ // Verify the policy's associated objects are cleaned up
+ fetchedPolicy = metalake.getPolicy(policy.name());
+ Assertions.assertEquals(0, fetchedPolicy.associatedObjects().count());
+ }
+
+ @Test
+ public void testCascadeDeleteSchemaWithViewAndFunction() {
+ // Create a policy that supports VIEW and FUNCTION
+ Policy policy =
+ metalake.createPolicy(
+ GravitinoITUtils.genRandomName("policy_it_cascade_schema"),
+ "custom",
+ "test comment",
+ true,
+ PolicyContents.custom(
+ ImmutableMap.of("rule1", "value1"),
+ ImmutableSet.of(MetadataObject.Type.VIEW,
MetadataObject.Type.FUNCTION),
+ null));
+
+ // Create a new schema
+ String cascadeSchemaName =
GravitinoITUtils.genRandomName("policy_it_cascade_schema");
+
Assertions.assertFalse(relationalCatalog.asSchemas().schemaExists(cascadeSchemaName));
+ Schema cascadeSchema =
+ relationalCatalog
+ .asSchemas()
+ .createSchema(cascadeSchemaName, "comment",
Collections.emptyMap());
+
+ // Create a view in the new schema
+ String cascadeViewName =
GravitinoITUtils.genRandomName("policy_it_cascade_schema_view");
+ NameIdentifier cascadeViewIdent = NameIdentifier.of(cascadeSchemaName,
cascadeViewName);
+ View cascadeView =
+ relationalCatalog
+ .asViewCatalog()
+ .createView(
+ cascadeViewIdent,
+ "comment",
+ new Column[] {
+ Column.of("col1", Types.IntegerType.get()),
+ Column.of("col2", Types.StringType.get())
+ },
+ new SQLRepresentation[] {
+ SQLRepresentation.builder()
+ .withDialect(Dialects.HIVE)
+ .withSql("SELECT col1, col2 FROM " + table.name())
+ .build()
+ },
+ null,
+ null,
+ Collections.emptyMap());
+
+ // Create a function in the new schema
+ String cascadeFuncName =
GravitinoITUtils.genRandomName("policy_it_cascade_schema_func");
+ NameIdentifier cascadeFuncIdent = NameIdentifier.of(cascadeSchemaName,
cascadeFuncName);
+ FunctionParam param = FunctionParams.of("x", Types.IntegerType.get());
+ FunctionImpl impl = FunctionImpls.ofSql(FunctionImpl.RuntimeType.SPARK,
"SELECT x + 1");
+ FunctionDefinition definition =
+ FunctionDefinitions.of(
+ new FunctionParam[] {param}, Types.IntegerType.get(), new
FunctionImpl[] {impl});
+ Function cascadeFunc =
+ relationalCatalog
+ .asFunctionCatalog()
+ .registerFunction(
+ cascadeFuncIdent,
+ "comment",
+ FunctionType.SCALAR,
+ true,
+ new FunctionDefinition[] {definition});
+
+ // Associate the policy with the view and function
+ SupportsPolicies cascadeViewPolicyOperations =
+ policyOperations(
+ relationalCatalog,
+ cascadeSchema,
+ cascadeView.name(),
+ MetadataObject.Type.VIEW,
+ policyRestClient);
+ SupportsPolicies cascadeFunctionPolicyOperations =
+ policyOperations(
+ relationalCatalog,
+ cascadeSchema,
+ cascadeFunc.name(),
+ MetadataObject.Type.FUNCTION,
+ policyRestClient);
+ cascadeViewPolicyOperations.associatePolicies(new String[]
{policy.name()}, null);
+ cascadeFunctionPolicyOperations.associatePolicies(new String[]
{policy.name()}, null);
+
+ // Verify the policy is associated with both view and function
+ Policy fetchedPolicy = metalake.getPolicy(policy.name());
+ Assertions.assertEquals(2, fetchedPolicy.associatedObjects().count());
+ Set<String> associatedNames =
+ Arrays.stream(fetchedPolicy.associatedObjects().objects())
+ .map(MetadataObject::name)
+ .collect(Collectors.toSet());
+ Assertions.assertTrue(associatedNames.contains(cascadeViewName));
+ Assertions.assertTrue(associatedNames.contains(cascadeFuncName));
+
+ // Drop the schema with cascade — this should cascade-delete policy
relations
+
Assertions.assertTrue(relationalCatalog.asSchemas().dropSchema(cascadeSchemaName,
true));
+
+ // Verify the policy's associated objects are cleaned up
+ fetchedPolicy = metalake.getPolicy(policy.name());
+ Assertions.assertEquals(0, fetchedPolicy.associatedObjects().count());
+ }
+
+ @Test
+ public void testCascadeDeleteCatalogWithViewAndFunction() {
+ // Create a policy that supports VIEW and FUNCTION
+ Policy policy =
+ metalake.createPolicy(
+ GravitinoITUtils.genRandomName("policy_it_cascade_catalog"),
+ "custom",
+ "test comment",
+ true,
+ PolicyContents.custom(
+ ImmutableMap.of("rule1", "value1"),
+ ImmutableSet.of(MetadataObject.Type.VIEW,
MetadataObject.Type.FUNCTION),
+ null));
+
+ // Create a new catalog for cascade test
+ String cascadeCatalogName =
GravitinoITUtils.genRandomName("policy_it_cascade_catalog");
+ Assertions.assertFalse(metalake.catalogExists(cascadeCatalogName));
+ String hmsUri =
+ String.format(
+ "thrift://%s:%d",
+ containerSuite.getHiveContainer().getContainerIpAddress(),
+ HiveContainer.HIVE_METASTORE_PORT);
+ Catalog cascadeCatalog =
+ metalake.createCatalog(
+ cascadeCatalogName,
+ Catalog.Type.RELATIONAL,
+ "hive",
+ "comment",
+ ImmutableMap.of("metastore.uris", hmsUri));
+
+ // Create a schema in the cascade catalog
+ String cascadeSchemaName =
GravitinoITUtils.genRandomName("policy_it_cascade_catalog_schema");
+
Assertions.assertFalse(cascadeCatalog.asSchemas().schemaExists(cascadeSchemaName));
+ Schema cascadeSchema =
+ cascadeCatalog
+ .asSchemas()
+ .createSchema(cascadeSchemaName, "comment",
Collections.emptyMap());
+
+ // Create a view in the cascade catalog
+ String cascadeViewName =
GravitinoITUtils.genRandomName("policy_it_cascade_catalog_view");
+ NameIdentifier cascadeViewIdent = NameIdentifier.of(cascadeSchemaName,
cascadeViewName);
+ View cascadeView =
+ cascadeCatalog
+ .asViewCatalog()
+ .createView(
+ cascadeViewIdent,
+ "comment",
+ new Column[] {
+ Column.of("col1", Types.IntegerType.get()),
+ Column.of("col2", Types.StringType.get())
+ },
+ new SQLRepresentation[] {
+
SQLRepresentation.builder().withDialect(Dialects.HIVE).withSql("SELECT
1").build()
+ },
+ null,
+ null,
+ Collections.emptyMap());
+
+ // Create a function in the cascade catalog
+ String cascadeFuncName =
GravitinoITUtils.genRandomName("policy_it_cascade_catalog_func");
+ NameIdentifier cascadeFuncIdent = NameIdentifier.of(cascadeSchemaName,
cascadeFuncName);
+ FunctionParam param = FunctionParams.of("x", Types.IntegerType.get());
+ FunctionImpl impl = FunctionImpls.ofSql(FunctionImpl.RuntimeType.SPARK,
"SELECT x + 1");
+ FunctionDefinition definition =
+ FunctionDefinitions.of(
+ new FunctionParam[] {param}, Types.IntegerType.get(), new
FunctionImpl[] {impl});
+ Function cascadeFunc =
+ cascadeCatalog
+ .asFunctionCatalog()
+ .registerFunction(
+ cascadeFuncIdent,
+ "comment",
+ FunctionType.SCALAR,
+ true,
+ new FunctionDefinition[] {definition});
+
+ // Associate the policy with the view and function
+ SupportsPolicies cascadeViewPolicyOperations =
+ policyOperations(
+ cascadeCatalog,
+ cascadeSchema,
+ cascadeView.name(),
+ MetadataObject.Type.VIEW,
+ policyRestClient);
+ SupportsPolicies cascadeFunctionPolicyOperations =
+ policyOperations(
+ cascadeCatalog,
+ cascadeSchema,
+ cascadeFunc.name(),
+ MetadataObject.Type.FUNCTION,
+ policyRestClient);
+ cascadeViewPolicyOperations.associatePolicies(new String[]
{policy.name()}, null);
+ cascadeFunctionPolicyOperations.associatePolicies(new String[]
{policy.name()}, null);
+
+ // Verify the policy is associated with both view and function
+ Policy fetchedPolicy = metalake.getPolicy(policy.name());
+ Assertions.assertEquals(2, fetchedPolicy.associatedObjects().count());
+ Set<String> associatedNames =
+ Arrays.stream(fetchedPolicy.associatedObjects().objects())
+ .map(MetadataObject::name)
+ .collect(Collectors.toSet());
+ Assertions.assertTrue(associatedNames.contains(cascadeViewName));
+ Assertions.assertTrue(associatedNames.contains(cascadeFuncName));
+
+ // Drop the catalog with cascade — this should cascade-delete policy
relations
+ Assertions.assertTrue(metalake.dropCatalog(cascadeCatalogName, true));
+
+ // Verify the policy's associated objects are cleaned up
+ fetchedPolicy = metalake.getPolicy(policy.name());
+ Assertions.assertEquals(0, fetchedPolicy.associatedObjects().count());
+ }
+
+ private Policy createCustomPolicy(String name, Set<MetadataObject.Type>
types) {
return metalake.createPolicy(
name,
"custom",
"test comment",
true,
- PolicyContents.custom(
- ImmutableMap.of("rule1", "value1"),
- ImmutableSet.of(MetadataObject.Type.CATALOG),
- null));
+ PolicyContents.custom(ImmutableMap.of("rule1", "value1"), types,
null));
+ }
+
+ private static SupportsPolicies policyOperations(
+ Catalog catalog,
+ Schema schema,
+ String objectName,
+ MetadataObject.Type objectType,
+ RESTClient restClient) {
+ MetadataObject metadataObject =
+ MetadataObjectDTO.builder()
+ .withParent(String.join(".", catalog.name(), schema.name()))
+ .withName(objectName)
+ .withType(objectType)
+ .build();
+ return new RestPolicyOperations(metalakeName, metadataObject, restClient);
+ }
+
+ private static final class RestPolicyOperations implements SupportsPolicies {
+ private final RESTClient restClient;
+ private final String requestPath;
+
+ private RestPolicyOperations(
+ String metalakeName, MetadataObject metadataObject, RESTClient
restClient) {
+ this.restClient = restClient;
+ this.requestPath =
+ String.format(
+ "api/metalakes/%s/objects/%s/%s/policies",
+ RESTUtils.encodeString(metalakeName),
+ metadataObject.type().name().toLowerCase(Locale.ROOT),
+ RESTUtils.encodeString(metadataObject.fullName()));
+ }
+
+ @Override
+ public String[] listPolicies() {
+ NameListResponse response =
+ restClient.get(
+ requestPath,
+ NameListResponse.class,
+ Collections.emptyMap(),
+ ErrorHandlers.policyErrorHandler());
+ response.validate();
+ return response.getNames();
+ }
+
+ @Override
+ public Policy[] listPolicyInfos() {
+ PolicyListResponse response =
+ restClient.get(
+ requestPath,
+ ImmutableMap.of("details", "true"),
+ PolicyListResponse.class,
+ Collections.emptyMap(),
+ ErrorHandlers.policyErrorHandler());
+ response.validate();
+ return response.getPolicies();
+ }
+
+ @Override
+ public Policy getPolicy(String name) throws NoSuchPolicyException {
+ PolicyResponse response =
+ restClient.get(
+ requestPath + "/" + RESTUtils.encodeString(name),
+ PolicyResponse.class,
+ Collections.emptyMap(),
+ ErrorHandlers.policyErrorHandler());
+ response.validate();
+ return response.getPolicy();
+ }
+
+ @Override
+ public String[] associatePolicies(String[] policiesToAdd, String[]
policiesToRemove) {
+ PoliciesAssociateRequest request =
+ new PoliciesAssociateRequest(policiesToAdd, policiesToRemove);
+ request.validate();
+
+ NameListResponse response =
+ restClient.post(
+ requestPath,
+ request,
+ NameListResponse.class,
+ Collections.emptyMap(),
+ ErrorHandlers.policyErrorHandler());
+ response.validate();
+ return response.getNames();
+ }
}
}
diff --git a/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
b/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
index 8f7811074c..22256fba5f 100644
--- a/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
+++ b/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
@@ -63,7 +63,9 @@ public class PolicyManager implements PolicyDispatcher {
MetadataObject.Type.TABLE,
MetadataObject.Type.FILESET,
MetadataObject.Type.TOPIC,
- MetadataObject.Type.MODEL);
+ MetadataObject.Type.MODEL,
+ MetadataObject.Type.VIEW,
+ MetadataObject.Type.FUNCTION);
private final IdGenerator idGenerator;
private final EntityStore entityStore;
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/PolicyMetadataObjectRelBaseSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/PolicyMetadataObjectRelBaseSQLProvider.java
index 7aa00abe44..3e4af8580b 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/PolicyMetadataObjectRelBaseSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/PolicyMetadataObjectRelBaseSQLProvider.java
@@ -22,6 +22,7 @@ import static
org.apache.gravitino.storage.relational.mapper.PolicyVersionMapper
import java.util.List;
import org.apache.gravitino.storage.relational.mapper.FilesetMetaMapper;
+import org.apache.gravitino.storage.relational.mapper.FunctionMetaMapper;
import org.apache.gravitino.storage.relational.mapper.MetalakeMetaMapper;
import org.apache.gravitino.storage.relational.mapper.ModelMetaMapper;
import org.apache.gravitino.storage.relational.mapper.PolicyMetaMapper;
@@ -29,6 +30,7 @@ import
org.apache.gravitino.storage.relational.mapper.PolicyMetadataObjectRelMap
import org.apache.gravitino.storage.relational.mapper.SchemaMetaMapper;
import org.apache.gravitino.storage.relational.mapper.TableMetaMapper;
import org.apache.gravitino.storage.relational.mapper.TopicMetaMapper;
+import org.apache.gravitino.storage.relational.mapper.ViewMetaMapper;
import org.apache.gravitino.storage.relational.po.PolicyMetadataObjectRelPO;
import org.apache.ibatis.annotations.Param;
@@ -191,6 +193,12 @@ public class PolicyMetadataObjectRelBaseSQLProvider {
+ " OR (metadata_object_type = 'MODEL' AND metadata_object_id IN
(SELECT model_id FROM "
+ ModelMetaMapper.TABLE_NAME
+ " WHERE catalog_id = #{catalogId}))"
+ + " OR (metadata_object_type = 'VIEW' AND metadata_object_id IN
(SELECT view_id FROM "
+ + ViewMetaMapper.TABLE_NAME
+ + " WHERE catalog_id = #{catalogId}))"
+ + " OR (metadata_object_type = 'FUNCTION' AND metadata_object_id IN
(SELECT function_id FROM "
+ + FunctionMetaMapper.TABLE_NAME
+ + " WHERE catalog_id = #{catalogId}))"
+ " )";
}
@@ -234,6 +242,20 @@ public class PolicyMetadataObjectRelBaseSQLProvider {
+ "#{schemaId}"
+ "</foreach>"
+ "))"
+ + " OR (metadata_object_type = 'VIEW' AND metadata_object_id IN
(SELECT view_id FROM "
+ + ViewMetaMapper.TABLE_NAME
+ + " WHERE schema_id IN "
+ + "<foreach collection='schemaIds' item='schemaId' open='(' close=')'
separator=','>"
+ + "#{schemaId}"
+ + "</foreach>"
+ + "))"
+ + " OR (metadata_object_type = 'FUNCTION' AND metadata_object_id IN
(SELECT function_id FROM "
+ + FunctionMetaMapper.TABLE_NAME
+ + " WHERE schema_id IN "
+ + "<foreach collection='schemaIds' item='schemaId' open='(' close=')'
separator=','>"
+ + "#{schemaId}"
+ + "</foreach>"
+ + "))"
+ " )"
+ "</script>";
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/PolicyMetadataObjectRelPostgreSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/PolicyMetadataObjectRelPostgreSQLProvider.java
index 085123ff60..e365f359da 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/PolicyMetadataObjectRelPostgreSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/PolicyMetadataObjectRelPostgreSQLProvider.java
@@ -23,6 +23,7 @@ import static
org.apache.gravitino.storage.relational.mapper.PolicyMetadataObjec
import java.util.List;
import org.apache.gravitino.storage.relational.mapper.CatalogMetaMapper;
import org.apache.gravitino.storage.relational.mapper.FilesetMetaMapper;
+import org.apache.gravitino.storage.relational.mapper.FunctionMetaMapper;
import org.apache.gravitino.storage.relational.mapper.MetalakeMetaMapper;
import org.apache.gravitino.storage.relational.mapper.ModelMetaMapper;
import org.apache.gravitino.storage.relational.mapper.PolicyMetaMapper;
@@ -30,6 +31,7 @@ import
org.apache.gravitino.storage.relational.mapper.SchemaMetaMapper;
import org.apache.gravitino.storage.relational.mapper.TableColumnMapper;
import org.apache.gravitino.storage.relational.mapper.TableMetaMapper;
import org.apache.gravitino.storage.relational.mapper.TopicMetaMapper;
+import org.apache.gravitino.storage.relational.mapper.ViewMetaMapper;
import
org.apache.gravitino.storage.relational.mapper.provider.base.PolicyMetadataObjectRelBaseSQLProvider;
import org.apache.ibatis.annotations.Param;
@@ -103,9 +105,16 @@ public class PolicyMetadataObjectRelPostgreSQLProvider
+ " LEFT JOIN "
+ ModelMetaMapper.TABLE_NAME
+ " mt ON pe_alias.metadata_object_id = mt.model_id AND
pe_alias.metadata_object_type = 'MODEL'"
+ + " LEFT JOIN "
+ + ViewMetaMapper.TABLE_NAME
+ + " vt ON pe_alias.metadata_object_id = vt.view_id AND
pe_alias.metadata_object_type = 'VIEW'"
+ + " LEFT JOIN "
+ + FunctionMetaMapper.TABLE_NAME
+ + " ft2 ON pe_alias.metadata_object_id = ft2.function_id AND
pe_alias.metadata_object_type = 'FUNCTION'"
+ " WHERE pe.id = pe_alias.id AND pe.deleted_at = 0 AND ("
+ " ct.catalog_id = #{catalogId} OR st.catalog_id = #{catalogId} OR
tt.catalog_id = #{catalogId}"
+ " OR tat.catalog_id = #{catalogId} OR ft.catalog_id = #{catalogId}
OR mt.catalog_id = #{catalogId}"
+ + " OR vt.catalog_id = #{catalogId} OR ft2.catalog_id = #{catalogId}"
+ " )";
}
@@ -135,6 +144,12 @@ public class PolicyMetadataObjectRelPostgreSQLProvider
+ " LEFT JOIN "
+ ModelMetaMapper.TABLE_NAME
+ " mt ON pe_alias.metadata_object_id = mt.model_id AND
pe_alias.metadata_object_type = 'MODEL'"
+ + " LEFT JOIN "
+ + ViewMetaMapper.TABLE_NAME
+ + " vt ON pe_alias.metadata_object_id = vt.view_id AND
pe_alias.metadata_object_type = 'VIEW'"
+ + " LEFT JOIN "
+ + FunctionMetaMapper.TABLE_NAME
+ + " ft2 ON pe_alias.metadata_object_id = ft2.function_id AND
pe_alias.metadata_object_type = 'FUNCTION'"
+ " WHERE pe.id = pe_alias.id AND pe.deleted_at = 0 AND ("
+ " st.schema_id IN "
+ "<foreach collection='schemaIds' item='schemaId' open='(' close=')'
separator=','>"
@@ -156,6 +171,14 @@ public class PolicyMetadataObjectRelPostgreSQLProvider
+ "<foreach collection='schemaIds' item='schemaId' open='(' close=')'
separator=','>"
+ "#{schemaId}"
+ "</foreach>"
+ + " OR vt.schema_id IN "
+ + "<foreach collection='schemaIds' item='schemaId' open='(' close=')'
separator=','>"
+ + "#{schemaId}"
+ + "</foreach>"
+ + " OR ft2.schema_id IN "
+ + "<foreach collection='schemaIds' item='schemaId' open='(' close=')'
separator=','>"
+ + "#{schemaId}"
+ + "</foreach>"
+ " )"
+ "</script>";
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/FunctionMetaService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/FunctionMetaService.java
index 2c582dc8c0..03e6b3b8d9 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/FunctionMetaService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/FunctionMetaService.java
@@ -44,6 +44,7 @@ import org.apache.gravitino.metrics.Monitored;
import org.apache.gravitino.storage.relational.mapper.FunctionMetaMapper;
import
org.apache.gravitino.storage.relational.mapper.FunctionVersionMetaMapper;
import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
+import
org.apache.gravitino.storage.relational.mapper.PolicyMetadataObjectRelMapper;
import org.apache.gravitino.storage.relational.mapper.SecurableObjectMapper;
import
org.apache.gravitino.storage.relational.mapper.TagMetadataObjectRelMapper;
import org.apache.gravitino.storage.relational.po.FunctionMaxVersionPO;
@@ -172,6 +173,11 @@ public class FunctionMetaService {
mapper ->
mapper.softDeleteTagMetadataObjectRelsByMetadataObject(
functionId, MetadataObject.Type.FUNCTION.name()));
+ SessionUtils.doWithoutCommit(
+ PolicyMetadataObjectRelMapper.class,
+ mapper ->
+ mapper.softDeletePolicyMetadataObjectRelsByMetadataObject(
+ functionId, MetadataObject.Type.FUNCTION.name()));
}
});
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/ViewMetaService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/ViewMetaService.java
index a305e05645..935c8e5667 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/ViewMetaService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/ViewMetaService.java
@@ -40,6 +40,7 @@ import org.apache.gravitino.meta.ViewEntity;
import org.apache.gravitino.metrics.Monitored;
import org.apache.gravitino.storage.relational.mapper.EntityChangeLogMapper;
import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
+import
org.apache.gravitino.storage.relational.mapper.PolicyMetadataObjectRelMapper;
import org.apache.gravitino.storage.relational.mapper.SecurableObjectMapper;
import
org.apache.gravitino.storage.relational.mapper.TagMetadataObjectRelMapper;
import org.apache.gravitino.storage.relational.mapper.ViewMetaMapper;
@@ -251,6 +252,11 @@ public class ViewMetaService {
mapper ->
mapper.softDeleteTagMetadataObjectRelsByMetadataObject(
viewId, MetadataObject.Type.VIEW.name()));
+ SessionUtils.doWithoutCommit(
+ PolicyMetadataObjectRelMapper.class,
+ mapper ->
+ mapper.softDeletePolicyMetadataObjectRelsByMetadataObject(
+ viewId, MetadataObject.Type.VIEW.name()));
}
},
() -> {
diff --git
a/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
b/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
index 5f69606220..ef25f5b960 100644
--- a/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
+++ b/core/src/test/java/org/apache/gravitino/policy/TestPolicyManager.java
@@ -63,23 +63,31 @@ import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.catalog.CatalogDispatcher;
+import org.apache.gravitino.catalog.FunctionDispatcher;
import org.apache.gravitino.catalog.SchemaDispatcher;
import org.apache.gravitino.catalog.TableDispatcher;
+import org.apache.gravitino.catalog.ViewDispatcher;
import org.apache.gravitino.exceptions.NoSuchMetalakeException;
import org.apache.gravitino.exceptions.NoSuchPolicyException;
import org.apache.gravitino.exceptions.NotFoundException;
import org.apache.gravitino.exceptions.PolicyAlreadyAssociatedException;
import org.apache.gravitino.exceptions.PolicyAlreadyExistsException;
+import org.apache.gravitino.function.FunctionDefinition;
+import org.apache.gravitino.function.FunctionType;
import org.apache.gravitino.lock.LockManager;
import org.apache.gravitino.meta.AuditInfo;
import org.apache.gravitino.meta.BaseMetalake;
import org.apache.gravitino.meta.CatalogEntity;
import org.apache.gravitino.meta.ColumnEntity;
+import org.apache.gravitino.meta.FunctionEntity;
import org.apache.gravitino.meta.PolicyEntity;
import org.apache.gravitino.meta.SchemaEntity;
import org.apache.gravitino.meta.SchemaVersion;
import org.apache.gravitino.meta.TableEntity;
+import org.apache.gravitino.meta.ViewEntity;
import org.apache.gravitino.metalake.MetalakeDispatcher;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
import org.apache.gravitino.rel.types.Types;
import org.apache.gravitino.storage.IdGenerator;
import org.apache.gravitino.storage.RandomIdGenerator;
@@ -97,10 +105,14 @@ public class TestPolicyManager {
private static final String SCHEMA = "schema_for_policy_test";
private static final String TABLE = "table_for_policy_test";
private static final String COLUMN = "column_for_policy_test";
+ private static final String VIEW = "view_for_policy_test";
+ private static final String FUNCTION = "function_for_policy_test";
private static final MetalakeDispatcher metalakeDispatcher =
mock(MetalakeDispatcher.class);
private static final CatalogDispatcher catalogDispatcher =
mock(CatalogDispatcher.class);
private static final SchemaDispatcher schemaDispatcher =
mock(SchemaDispatcher.class);
private static final TableDispatcher tableDispatcher =
mock(TableDispatcher.class);
+ private static final ViewDispatcher viewDispatcher =
mock(ViewDispatcher.class);
+ private static final FunctionDispatcher functionDispatcher =
mock(FunctionDispatcher.class);
private static final String JDBC_STORE_PATH =
"/tmp/gravitino_jdbc_entityStore_" +
UUID.randomUUID().toString().replace("-", "");
private static final String DB_DIR = JDBC_STORE_PATH + "/testdb";
@@ -125,6 +137,9 @@ public class TestPolicyManager {
FieldUtils.writeField(GravitinoEnv.getInstance(), "catalogDispatcher",
catalogDispatcher, true);
FieldUtils.writeField(GravitinoEnv.getInstance(), "schemaDispatcher",
schemaDispatcher, true);
FieldUtils.writeField(GravitinoEnv.getInstance(), "tableDispatcher",
tableDispatcher, true);
+ FieldUtils.writeField(GravitinoEnv.getInstance(), "viewDispatcher",
viewDispatcher, true);
+ FieldUtils.writeField(
+ GravitinoEnv.getInstance(), "functionDispatcher", functionDispatcher,
true);
AuditInfo audit =
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build();
BaseMetalake metalake =
@@ -184,6 +199,31 @@ public class TestPolicyManager {
.build();
entityStore.put(table, false /* overwritten */);
when(tableDispatcher.tableExists(any())).thenReturn(true);
+ when(viewDispatcher.viewExists(any())).thenReturn(true);
+ when(functionDispatcher.functionExists(any())).thenReturn(true);
+
+ ViewEntity view =
+ ViewEntity.builder()
+ .withId(idGenerator.nextId())
+ .withName(VIEW)
+ .withNamespace(Namespace.of(METALAKE, CATALOG, SCHEMA))
+ .withColumns(new Column[0])
+ .withRepresentations(new Representation[0])
+ .withAuditInfo(audit)
+ .build();
+ entityStore.put(view, false /* overwritten */);
+
+ FunctionEntity function =
+ FunctionEntity.builder()
+ .withId(idGenerator.nextId())
+ .withName(FUNCTION)
+ .withNamespace(Namespace.of(METALAKE, CATALOG, SCHEMA))
+ .withFunctionType(FunctionType.SCALAR)
+ .withDeterministic(true)
+ .withDefinitions(new FunctionDefinition[0])
+ .withAuditInfo(audit)
+ .build();
+ entityStore.put(function, false /* overwritten */);
}
private static Config mockConfig() {
@@ -561,6 +601,45 @@ public class TestPolicyManager {
Assertions.assertEquals(2, policies6.length);
Assertions.assertEquals(
ImmutableSet.of(policyName1, policyName3),
ImmutableSet.copyOf(policies6));
+
+ // Test associate policies for view
+ MetadataObject viewObject =
+ NameIdentifierUtil.toMetadataObject(
+ NameIdentifierUtil.ofView(METALAKE, CATALOG, SCHEMA, VIEW),
Entity.EntityType.VIEW);
+ String[] policies7 =
+ policyManager.associatePoliciesForMetadataObject(
+ METALAKE, viewObject, new String[] {policyName1}, null);
+
+ Assertions.assertEquals(1, policies7.length);
+ Assertions.assertEquals(ImmutableSet.of(policyName1),
ImmutableSet.copyOf(policies7));
+
+ // Test associate and disassociate policies for view
+ String[] policies8 =
+ policyManager.associatePoliciesForMetadataObject(
+ METALAKE, viewObject, new String[] {policyName2}, new String[]
{policyName1});
+
+ Assertions.assertEquals(1, policies8.length);
+ Assertions.assertEquals(ImmutableSet.of(policyName2),
ImmutableSet.copyOf(policies8));
+
+ // Test associate policies for function
+ MetadataObject functionObject =
+ NameIdentifierUtil.toMetadataObject(
+ NameIdentifierUtil.ofFunction(METALAKE, CATALOG, SCHEMA, FUNCTION),
+ Entity.EntityType.FUNCTION);
+ String[] policies9 =
+ policyManager.associatePoliciesForMetadataObject(
+ METALAKE, functionObject, new String[] {policyName1}, null);
+
+ Assertions.assertEquals(1, policies9.length);
+ Assertions.assertEquals(ImmutableSet.of(policyName1),
ImmutableSet.copyOf(policies9));
+
+ // Test associate and disassociate policies for function
+ String[] policies10 =
+ policyManager.associatePoliciesForMetadataObject(
+ METALAKE, functionObject, new String[] {policyName3}, new String[]
{policyName1});
+
+ Assertions.assertEquals(1, policies10.length);
+ Assertions.assertEquals(ImmutableSet.of(policyName3),
ImmutableSet.copyOf(policies10));
}
@Test
diff --git a/docs/manage-policies-in-gravitino.md
b/docs/manage-policies-in-gravitino.md
index f9e60ea4e5..a8face785f 100644
--- a/docs/manage-policies-in-gravitino.md
+++ b/docs/manage-policies-in-gravitino.md
@@ -220,7 +220,7 @@ client.deletePolicy("retention_30d");
### Attach and Detach Policies
Both happen in one request, and either list can be omitted. Catalogs, schemas,
tables, filesets,
-topics, and models can carry a policy.
+topics, models, views, and functions can carry a policy.
<Tabs groupId='language' queryString>
<TabItem value="shell" label="REST">
diff --git a/docs/open-api/policies.yaml b/docs/open-api/policies.yaml
index ba864638ed..102e2aa57b 100644
--- a/docs/open-api/policies.yaml
+++ b/docs/open-api/policies.yaml
@@ -249,7 +249,7 @@ paths:
tags:
- policy
summary: Associate policies with metadata object
- description: Associate and disassociate policies with metadata object,
please be aware that supported metadata objects are CATALOG, SCHEMA, TABLE,
FILESET, TOPIC, COLUMN
+ description: Associate and disassociate policies with metadata object,
please be aware that supported metadata objects are CATALOG, SCHEMA, TABLE,
FILESET, TOPIC, MODEL, VIEW, FUNCTION
operationId: associatePoliciesForObject
requestBody:
content:
@@ -390,7 +390,7 @@ components:
minItems: 1
items:
type: string
- enum: [ "CATALOG", "SCHEMA", "TABLE", "FILESET", "TOPIC", "MODEL" ]
+ enum: [ "CATALOG", "SCHEMA", "TABLE", "FILESET", "TOPIC", "MODEL",
"VIEW", "FUNCTION" ]
properties:
type: object
description: A map of string-to-string properties for the policy
content.
@@ -497,6 +497,8 @@ components:
- "FILESET"
- "TOPIC"
- "MODEL"
+ - "VIEW"
+ - "FUNCTION"
- "COLUMN"
PolicyCreateRequestBase:
diff --git a/docs/policies.md b/docs/policies.md
index 828c436831..b48f24f64b 100644
--- a/docs/policies.md
+++ b/docs/policies.md
@@ -33,7 +33,7 @@ Common uses:
policies. A policy needs a name, the object types it supports, and its rules.
Built-in policies are
created over REST.
-**2. Attach it to an object.** Open the catalog, schema, table, fileset,
topic, or model you want to
+**2. Attach it to an object.** Open the catalog, schema, table, fileset,
topic, model, view, or function you want to
govern and add the policy from its policy control. Only policies that already
exist in the metalake
are offered.
@@ -64,7 +64,7 @@ shape.
### What Can Carry a Policy
A metadata object is identified by a type and a name, with each level below
the catalog separated by
-a dot. Six object types can carry a policy.
+a dot. Eight object types can carry a policy.
| Object type | Name form |
|-------------|-----------------------------------------------|
@@ -74,8 +74,10 @@ a dot. Six object types can carry a policy.
| `FILESET` | `{catalog_name}.{schema_name}.{fileset_name}` |
| `TOPIC` | `{catalog_name}.{schema_name}.{topic_name}` |
| `MODEL` | `{catalog_name}.{schema_name}.{model_name}` |
+| `VIEW` | `{catalog_name}.{schema_name}.{view_name}` |
+| `FUNCTION` | `{catalog_name}.{schema_name}.{function_name}`|
-Columns, views, and functions cannot carry a policy, which is narrower than
+Columns cannot carry a policy, which is narrower than
[tags](./tags.md). A metalake cannot carry one either, so to reach every object
in a catalog, attach the policy to the catalog.
@@ -128,7 +130,7 @@ whoever reads the policy, useful for holding a policy
through review without del
### Inheritance
An object shows the policies attached to it plus the policies attached to each
of its ancestors, so
-a policy on a catalog applies to every schema, table, fileset, topic, and
model beneath it. For
+a policy on a catalog applies to every schema, table, fileset, topic, model,
view, and function beneath it. For
catalogs that support multi-level schemas, the intermediate schemas are
ancestors too.
Each policy appears once, whether it reaches the object through one ancestor
or several. A policy