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

Reply via email to