This is an automated email from the ASF dual-hosted git repository.
mchades 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 3afc9f1683 [#12501][#12500] feat(core): Support FUNCTION and VIEW
metadata object policies (#12503)
3afc9f1683 is described below
commit 3afc9f16832029c7ad95f394250bd56b77d42220
Author: Arvin <[email protected]>
AuthorDate: Fri Sep 4 10:27:21 2026 +0800
[#12501][#12500] feat(core): Support FUNCTION and VIEW metadata object
policies (#12503)
### What changes were proposed in this pull request?
Add FUNCTION and VIEW to the supported metadata object types for policy
association in PolicyManager, and add PolicyMetadataObjectRelMapper
cleanup when functions and views are deleted.
### Why are the changes needed?
Fixes #12501
Fixes #12500.
Currently, FUNCTION and VIEW exist in MetadataObject.Type but are
excluded from
PolicyManager.SUPPORTED_METADATA_OBJECT_TYPES_FOR_POLICIES. As a result,
policy association requests for functions and views are rejected with
"Cannot associate policies for unsupported metadata object type
FUNCTION/VIEW".
Additionally, when a function or view is deleted, its policy relations
are not cleaned up, leaving orphaned records.
### How was this patch tested?
Existing tests cover the policy association, listing, retrieval, and
removal flows. The changes follow the same pattern as existing supported
types (FILESET, TOPIC, MODEL).
### Does this PR introduce any user-facing changes?
Yes - users can now associate policies with FUNCTION and VIEW metadata
objects via the REST API.
---------
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 e4dc65746c..e452110661 100644
--- a/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
+++ b/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
@@ -65,7 +65,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 43b19493d8..dbd444b0c3 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.mapper.provider.DatabaseTimeSQL;
import org.apache.gravitino.storage.relational.po.PolicyMetadataObjectRelPO;
import org.apache.ibatis.annotations.Param;
@@ -193,6 +195,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}))"
+ " )";
}
@@ -237,6 +245,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 5e1b37a471..0978873a1e 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.DatabaseTimeSQL;
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 04976bed87..2d6fcfe444 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;
@@ -181,6 +182,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 50ea6f72f0..2a46018446 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
@@ -39,6 +39,7 @@ import org.apache.gravitino.exceptions.NoSuchEntityException;
import org.apache.gravitino.meta.ViewEntity;
import org.apache.gravitino.metrics.Monitored;
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;
@@ -232,6 +233,11 @@ public class ViewMetaService {
mapper ->
mapper.softDeleteTagMetadataObjectRelsByMetadataObject(
viewId, MetadataObject.Type.VIEW.name()));
+ SessionUtils.doWithoutCommit(
+ PolicyMetadataObjectRelMapper.class,
+ mapper ->
+ mapper.softDeletePolicyMetadataObjectRelsByMetadataObject(
+ viewId, MetadataObject.Type.VIEW.name()));
}
});
return deleteResult.get() > 0;
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 d7bbf0da11..076b1e6464 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