This is an automated email from the ASF dual-hosted git repository.

mchades pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new 7f59be2615 [Cherry-pick to branch-1.3] [#12501][#12500] feat(core): 
Support FUNCTION and VIEW metadata object policies (#12503) (#12904)
7f59be2615 is described below

commit 7f59be26157fda4020ab846cc074ce904c36c14f
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Sep 4 17:02:31 2026 +0800

    [Cherry-pick to branch-1.3] [#12501][#12500] feat(core): Support FUNCTION 
and VIEW metadata object policies (#12503) (#12904)
    
    **Cherry-pick Information:**
    - Original commit: 3afc9f16832029c7ad95f394250bd56b77d42220
    - Target branch: `branch-1.3`
    - Status: ✅ Conflicts resolved manually
    
    **Resolution:**
    - Kept the existing `branch-1.3` deletion timestamp implementation and
    added only the mapper imports required by the VIEW/FUNCTION cleanup SQL.
    - Preserved the original FUNCTION/VIEW policy association, direct
    cleanup, and catalog/schema cascade cleanup without introducing the
    main-only `DatabaseTimeSQL` dependency.
    
    **Validation:**
    - `./gradlew :core:spotlessApply :clients:client-java:spotlessApply
    :docs:spotlessApply --no-daemon`
    - `./gradlew :core:test --tests
    org.apache.gravitino.policy.TestPolicyManager -PskipITs
    -PskipDockerTests=true --no-daemon`
    - `./gradlew :docs:build --no-daemon`
    - `./gradlew :clients:client-java:test --tests
    org.apache.gravitino.client.integration.test.PolicyIT
    -PskipDockerTests=false -PtestMode=embedded --no-daemon` (14 tests, 0
    failures, 0 errors)
    - Conflict-marker scan and `git diff --check`
    
    ---------
    
    Co-authored-by: Arvin <[email protected]>
    Co-authored-by: mchades <[email protected]>
    Co-authored-by: Jerry Shao <[email protected]>
---
 .../client/integration/test/PolicyIT.java          | 742 ++++++++++++++++++++-
 .../org/apache/gravitino/policy/PolicyManager.java |   4 +-
 .../PolicyMetadataObjectRelBaseSQLProvider.java    |  22 +
 .../PolicyMetadataObjectRelPostgreSQLProvider.java |  23 +
 .../relational/service/FunctionMetaService.java    |   6 +
 .../relational/service/ViewMetaService.java        |   6 +
 .../apache/gravitino/policy/TestPolicyManager.java |  79 +++
 docs/manage-policies-in-gravitino.md               |   2 +-
 docs/open-api/policies.yaml                        |   6 +-
 docs/policies.md                                   |  10 +-
 10 files changed, 877 insertions(+), 23 deletions(-)

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

Reply via email to