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

jshao 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 5de0772668 [#7552] feat(policy): Add policy entity and POConverters 
(part-2) (#7361)
5de0772668 is described below

commit 5de07726684b281059b65a4dc486f2cb113d7e0a
Author: mchades <[email protected]>
AuthorDate: Wed Jul 9 16:13:49 2025 +0800

    [#7552] feat(policy): Add policy entity and POConverters (part-2) (#7361)
    
    ### What changes were proposed in this pull request?
    
    Add policy entity and POConverters
    
    ### Why are the changes needed?
    
    Fix: #7552
    
    ### Does this PR introduce _any_ user-facing change?
    
    no
    
    ### How was this patch tested?
    
    tests added
---
 .../java/org/apache/gravitino/policy/Policy.java   | 144 +++++++--
 .../org/apache/gravitino/policy/PolicyChange.java  |   8 +-
 .../PolicyContent.java}                            |  22 +-
 .../apache/gravitino/policy/PolicyContents.java    | 101 +++++++
 .../apache/gravitino/policy/PolicyOperations.java  |   4 +-
 .../src/main/java/org/apache/gravitino/Entity.java |   4 +
 .../org/apache/gravitino/meta/PolicyEntity.java    | 328 +++++++++++++++++++++
 .../gravitino/storage/relational/JDBCBackend.java  |   7 +
 .../gravitino/storage/relational/po/PolicyPO.java  | 195 ++++++++++++
 .../storage/relational/po/PolicyVersionPO.java     | 137 +++++++++
 .../storage/relational/utils/POConverters.java     |  34 +++
 .../org/apache/gravitino/utils/NamespaceUtil.java  |  10 +
 .../apache/gravitino/meta/TestPolicyEntity.java    | 125 ++++++++
 .../storage/relational/utils/TestPOConverters.java | 122 ++++++++
 14 files changed, 1203 insertions(+), 38 deletions(-)

diff --git a/api/src/main/java/org/apache/gravitino/policy/Policy.java 
b/api/src/main/java/org/apache/gravitino/policy/Policy.java
index d8c9b6fd38..05faabdbb7 100644
--- a/api/src/main/java/org/apache/gravitino/policy/Policy.java
+++ b/api/src/main/java/org/apache/gravitino/policy/Policy.java
@@ -18,13 +18,14 @@
  */
 package org.apache.gravitino.policy;
 
-import java.util.Map;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
 import java.util.Optional;
 import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.Auditable;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.annotation.Evolving;
-import org.apache.gravitino.exceptions.IllegalPolicyException;
 
 /**
  * The interface of the policy. The policy is a set of rules that can be 
associated with a metadata
@@ -33,6 +34,125 @@ import 
org.apache.gravitino.exceptions.IllegalPolicyException;
 @Evolving
 public interface Policy extends Auditable {
 
+  /** The set of metadata object types that the policy can be applied to. */
+  Set<MetadataObject.Type> SUPPORTS_ALL_OBJECT_TYPES =
+      ImmutableSet.of(
+          MetadataObject.Type.CATALOG,
+          MetadataObject.Type.SCHEMA,
+          MetadataObject.Type.FILESET,
+          MetadataObject.Type.TABLE,
+          MetadataObject.Type.TOPIC,
+          MetadataObject.Type.MODEL);
+
+  /**
+   * The prefix for built-in policy types. All built-in policy types should 
start with this prefix.
+   */
+  String BUILT_IN_TYPE_PREFIX = "system_";
+
+  /** The built-in policy types. Predefined policy types that are provided by 
the system. */
+  enum BuiltInType {
+    // todo: add built-in policies, such as:
+    //    DATA_COMPACTION(BUILT_IN_TYPE_PREFIX + "data_compaction", true, true,
+    // SUPPORTS_ALL_OBJECT_TYPES
+    //      PolicyContent.DataCompactionContent.class)
+
+    /**
+     * Custom policy type. "custom" is a dummy type for custom policies, all 
non-built-in types are
+     * custom types.
+     */
+    CUSTOM("custom", null, null, null, PolicyContents.CustomContent.class);
+
+    private final String policyType;
+    private final Boolean exclusive;
+    private final Boolean inheritable;
+    private final ImmutableSet<MetadataObject.Type> supportedObjectTypes;
+    private final Class<? extends PolicyContent> contentClass;
+
+    BuiltInType(
+        String policyType,
+        Boolean exclusive,
+        Boolean inheritable,
+        Set<MetadataObject.Type> supportedObjectTypes,
+        Class<? extends PolicyContent> contentClass) {
+      this.policyType = policyType;
+      this.exclusive = exclusive;
+      this.inheritable = inheritable;
+      this.supportedObjectTypes =
+          supportedObjectTypes == null
+              ? ImmutableSet.of()
+              : ImmutableSet.copyOf(supportedObjectTypes);
+      this.contentClass = contentClass;
+    }
+
+    /**
+     * Get the built-in policy type from the policy type string.
+     *
+     * @param policyType the policy type string
+     * @return the built-in policy type if it matches, otherwise returns 
CUSTOM type
+     */
+    public static BuiltInType fromPolicyType(String policyType) {
+      Preconditions.checkArgument(StringUtils.isNotBlank(policyType), 
"policyType cannot be blank");
+      for (BuiltInType type : BuiltInType.values()) {
+        if (type.policyType.equalsIgnoreCase(policyType)) {
+          return type;
+        }
+      }
+
+      if (policyType.startsWith(BUILT_IN_TYPE_PREFIX)) {
+        throw new IllegalArgumentException(
+            String.format("Unknown built-in policy type: %s", policyType));
+      }
+
+      // If the policy type is not a built-in type, it is a custom type.
+      return CUSTOM;
+    }
+
+    /**
+     * Get the policy type string.
+     *
+     * @return the policy type string
+     */
+    public String policyType() {
+      return policyType;
+    }
+
+    /**
+     * Check if the policy is exclusive.
+     *
+     * @return true if the policy is exclusive, false otherwise
+     */
+    public Boolean exclusive() {
+      return exclusive;
+    }
+
+    /**
+     * Check if the policy is inheritable.
+     *
+     * @return true if the policy is inheritable, false otherwise
+     */
+    public Boolean inheritable() {
+      return inheritable;
+    }
+
+    /**
+     * Get the set of metadata object types that the policy can be associated 
with.
+     *
+     * @return the set of metadata object types that the policy can be 
associated with
+     */
+    public Set<MetadataObject.Type> supportedObjectTypes() {
+      return supportedObjectTypes;
+    }
+
+    /**
+     * Get the content class of the policy.
+     *
+     * @return the content class of the policy
+     */
+    public Class<? extends PolicyContent> contentClass() {
+      return contentClass;
+    }
+  }
+
   /**
    * Get the name of the policy.
    *
@@ -45,7 +165,7 @@ public interface Policy extends Auditable {
    *
    * @return The type of the policy.
    */
-  String type();
+  String policyType();
 
   /**
    * Get the comment of the policy.
@@ -91,7 +211,7 @@ public interface Policy extends Auditable {
    *
    * @return The content of the policy.
    */
-  Content content();
+  PolicyContent content();
 
   /**
    * Check if the policy is inherited from a parent object or not.
@@ -105,27 +225,11 @@ public interface Policy extends Auditable {
    */
   Optional<Boolean> inherited();
 
-  /**
-   * Validate the policy. This method should be called when the policy is 
created or updated. It
-   * will check if the policy is valid or not. If the policy is not valid, it 
will throw an
-   * IllegalPolicyException.
-   *
-   * @throws IllegalPolicyException if the policy is not valid.
-   */
-  void validate() throws IllegalPolicyException;
-
   /** @return The associated objects of the policy. */
   default AssociatedObjects associatedObjects() {
     throw new UnsupportedOperationException("The associatedObjects method is 
not supported.");
   }
 
-  /** The interface of the content of the policy. */
-  interface Content {
-
-    /** @return The additional properties of the policy. */
-    Map<String, String> properties();
-  }
-
   /** The interface of the associated objects of the policy. */
   interface AssociatedObjects {
 
diff --git a/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java 
b/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java
index db4199aa23..297572b076 100644
--- a/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java
+++ b/api/src/main/java/org/apache/gravitino/policy/PolicyChange.java
@@ -54,7 +54,7 @@ public interface PolicyChange {
    * @param content The new content for the policy.
    * @return The policy change.
    */
-  static PolicyChange updateContent(Policy.Content content) {
+  static PolicyChange updateContent(PolicyContent content) {
     return new UpdateContent(content);
   }
 
@@ -160,9 +160,9 @@ public interface PolicyChange {
 
   /** A policy change to update the content of the policy. */
   final class UpdateContent implements PolicyChange {
-    private final Policy.Content content;
+    private final PolicyContent content;
 
-    private UpdateContent(Policy.Content content) {
+    private UpdateContent(PolicyContent content) {
       this.content = content;
     }
 
@@ -171,7 +171,7 @@ public interface PolicyChange {
      *
      * @return The content of the policy change.
      */
-    public Policy.Content getContent() {
+    public PolicyContent getContent() {
       return content;
     }
 
diff --git 
a/api/src/main/java/org/apache/gravitino/exceptions/IllegalPolicyException.java 
b/api/src/main/java/org/apache/gravitino/policy/PolicyContent.java
similarity index 59%
rename from 
api/src/main/java/org/apache/gravitino/exceptions/IllegalPolicyException.java
rename to api/src/main/java/org/apache/gravitino/policy/PolicyContent.java
index 027ad4bee9..6f1302687b 100644
--- 
a/api/src/main/java/org/apache/gravitino/exceptions/IllegalPolicyException.java
+++ b/api/src/main/java/org/apache/gravitino/policy/PolicyContent.java
@@ -16,22 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.gravitino.exceptions;
+package org.apache.gravitino.policy;
 
-import com.google.errorprone.annotations.FormatMethod;
-import com.google.errorprone.annotations.FormatString;
+import java.util.Map;
 
-/** An exception thrown when a policy is invalid. */
-public class IllegalPolicyException extends IllegalArgumentException {
+/** The interface of the content of the policy. */
+public interface PolicyContent {
+
+  /** @return The additional properties of the policy. */
+  Map<String, String> properties();
 
   /**
-   * Constructs a new exception with the specified detail message.
+   * Validates the policy content.
    *
-   * @param message the detail message.
-   * @param args the arguments to the message.
+   * @throws IllegalArgumentException if the content is invalid.
    */
-  @FormatMethod
-  public IllegalPolicyException(@FormatString String message, Object... args) {
-    super(String.format(message, args));
-  }
+  void validate() throws IllegalArgumentException;
 }
diff --git a/api/src/main/java/org/apache/gravitino/policy/PolicyContents.java 
b/api/src/main/java/org/apache/gravitino/policy/PolicyContents.java
new file mode 100644
index 0000000000..f95a186db4
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/policy/PolicyContents.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.policy;
+
+import java.util.Map;
+import java.util.Objects;
+
+/** Utility class for creating instances of {@link PolicyContent}. */
+public class PolicyContents {
+
+  /**
+   * Creates a custom policy content with the given rules and properties.
+   *
+   * @param rules The custom rules of the policy.
+   * @param properties The additional properties of the policy.
+   * @return A new instance of {@link PolicyContent} with the specified rules 
and properties.
+   */
+  public static PolicyContent custom(Map<String, Object> rules, Map<String, 
String> properties) {
+    return new CustomContent(rules, properties);
+  }
+
+  private PolicyContents() {}
+
+  /**
+   * A custom content implementation of {@link PolicyContent} that holds 
custom rules and
+   * properties.
+   */
+  public static class CustomContent implements PolicyContent {
+    private final Map<String, Object> customRules;
+    private final Map<String, String> properties;
+
+    /** Default constructor for Jackson deserialization only. */
+    private CustomContent() {
+      this(null, null);
+    }
+
+    /**
+     * Constructor for CustomContent.
+     *
+     * @param customRules the custom rules of the policy
+     * @param properties the additional properties of the policy
+     */
+    private CustomContent(Map<String, Object> customRules, Map<String, String> 
properties) {
+      this.customRules = customRules;
+      this.properties = properties;
+    }
+
+    /**
+     * Returns the custom rules of the policy.
+     *
+     * @return a map of custom rules
+     */
+    public Map<String, Object> customRules() {
+      return customRules;
+    }
+
+    @Override
+    public Map<String, String> properties() {
+      return properties;
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      // nothing to validate for custom content
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof CustomContent)) return false;
+      CustomContent that = (CustomContent) o;
+      return Objects.equals(customRules, that.customRules)
+          && Objects.equals(properties, that.properties);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(customRules, properties);
+    }
+
+    @Override
+    public String toString() {
+      return "CustomContent{" + "customRules=" + customRules + ", properties=" 
+ properties + '}';
+    }
+  }
+}
diff --git 
a/api/src/main/java/org/apache/gravitino/policy/PolicyOperations.java 
b/api/src/main/java/org/apache/gravitino/policy/PolicyOperations.java
index 22cc51dab3..440c4860e4 100644
--- a/api/src/main/java/org/apache/gravitino/policy/PolicyOperations.java
+++ b/api/src/main/java/org/apache/gravitino/policy/PolicyOperations.java
@@ -71,7 +71,7 @@ public interface PolicyOperations {
    * @throws PolicyAlreadyExistsException If the policy already exists.
    */
   Policy createPolicy(
-      String name, String type, String comment, boolean enabled, 
Policy.Content content)
+      String name, String type, String comment, boolean enabled, PolicyContent 
content)
       throws PolicyAlreadyExistsException;
 
   /**
@@ -103,7 +103,7 @@ public interface PolicyOperations {
       boolean exclusive,
       boolean inheritable,
       Set<MetadataObject.Type> supportedObjectTypes,
-      Policy.Content content)
+      PolicyContent content)
       throws PolicyAlreadyExistsException;
 
   /**
diff --git a/core/src/main/java/org/apache/gravitino/Entity.java 
b/core/src/main/java/org/apache/gravitino/Entity.java
index 102be50365..ab1ca3bc10 100644
--- a/core/src/main/java/org/apache/gravitino/Entity.java
+++ b/core/src/main/java/org/apache/gravitino/Entity.java
@@ -55,6 +55,9 @@ public interface Entity extends Serializable {
   /** The tag schema name in the system catalog. */
   String TAG_SCHEMA_NAME = "tag";
 
+  /** The policy schema name in the system catalog. */
+  String POLICY_SCHEMA_NAME = "policy";
+
   /** Enumeration defining the types of entities in the Gravitino framework. */
   @Getter
   enum EntityType {
@@ -71,6 +74,7 @@ public interface Entity extends Serializable {
     TAG("ta", 10),
     MODEL("mo", 11),
     MODEL_VERSION("mv", 12),
+    POLICY("po", 13),
 
     AUDIT("au", 65534);
 
diff --git a/core/src/main/java/org/apache/gravitino/meta/PolicyEntity.java 
b/core/src/main/java/org/apache/gravitino/meta/PolicyEntity.java
new file mode 100644
index 0000000000..dbfedc5627
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/meta/PolicyEntity.java
@@ -0,0 +1,328 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.meta;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Audit;
+import org.apache.gravitino.Auditable;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.Field;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyContent;
+import org.apache.gravitino.policy.PolicyContents;
+
+@ToString
+public class PolicyEntity implements Policy, Entity, Auditable, HasIdentifier {
+
+  public static final Field ID =
+      Field.required("id", Long.class, "The unique id of the policy entity.");
+  public static final Field NAME =
+      Field.required("name", String.class, "The name of the policy entity.");
+  public static final Field POLICY_TYPE =
+      Field.required("policyType", String.class, "The type of the policy 
entity.");
+  public static final Field COMMENT =
+      Field.optional("comment", String.class, "The comment of the policy 
entity.");
+  public static final Field ENABLED =
+      Field.required("enabled", Boolean.class, "The policy entity is 
enabled.");
+  public static final Field EXCLUSIVE =
+      Field.required("exclusive", Boolean.class, "The policy entity is 
exclusive.");
+  public static final Field INHERITABLE =
+      Field.required("inheritable", Boolean.class, "The policy entity is 
inheritable.");
+  public static final Field SUPPORTED_OBJECT_TYPES =
+      Field.required(
+          "supportedObjectTypes", Set.class, "The supported object types of 
the policy entity.");
+  public static final Field CONTENT =
+      Field.required("content", PolicyContent.class, "The content of the 
policy entity.");
+  public static final Field AUDIT_INFO =
+      Field.required("audit_info", AuditInfo.class, "The audit details of the 
policy entity.");
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  private Long id;
+  private String name;
+  private Namespace namespace;
+  private String policyType;
+  private String comment;
+  private boolean enabled;
+  private boolean exclusive;
+  private boolean inheritable;
+  private Set<MetadataObject.Type> supportedObjectTypes;
+  private PolicyContent content;
+  private AuditInfo auditInfo;
+
+  private PolicyEntity() {}
+
+  @Override
+  public Map<Field, Object> fields() {
+    Map<Field, Object> fields = Maps.newHashMap();
+    fields.put(ID, id);
+    fields.put(NAME, name);
+    fields.put(POLICY_TYPE, policyType);
+    fields.put(COMMENT, comment);
+    fields.put(ENABLED, enabled);
+    fields.put(EXCLUSIVE, exclusive);
+    fields.put(INHERITABLE, inheritable);
+    fields.put(SUPPORTED_OBJECT_TYPES, supportedObjectTypes);
+    fields.put(CONTENT, content);
+    fields.put(AUDIT_INFO, auditInfo);
+
+    return Collections.unmodifiableMap(fields);
+  }
+
+  @Override
+  public Long id() {
+    return id;
+  }
+
+  @Override
+  public String name() {
+    return name;
+  }
+
+  @Override
+  public Namespace namespace() {
+    return namespace;
+  }
+
+  @Override
+  public EntityType type() {
+    return EntityType.POLICY;
+  }
+
+  @Override
+  public Audit auditInfo() {
+    return auditInfo;
+  }
+
+  @Override
+  public String policyType() {
+    return policyType;
+  }
+
+  @Override
+  public String comment() {
+    return comment;
+  }
+
+  @Override
+  public boolean enabled() {
+    return enabled;
+  }
+
+  @Override
+  public boolean exclusive() {
+    return exclusive;
+  }
+
+  @Override
+  public boolean inheritable() {
+    return inheritable;
+  }
+
+  @Override
+  public Set<MetadataObject.Type> supportedObjectTypes() {
+    return supportedObjectTypes;
+  }
+
+  @Override
+  public PolicyContent content() {
+    return content;
+  }
+
+  @Override
+  public Optional<Boolean> inherited() {
+    return Optional.empty();
+  }
+
+  @Override
+  public void validate() throws IllegalArgumentException {
+    Entity.super.validate();
+    validatePolicy();
+    content().validate();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof PolicyEntity)) return false;
+    PolicyEntity that = (PolicyEntity) o;
+    return enabled == that.enabled
+        && exclusive == that.exclusive
+        && inheritable == that.inheritable
+        && Objects.equals(id, that.id)
+        && Objects.equals(name, that.name)
+        && Objects.equals(namespace, that.namespace)
+        && Objects.equals(policyType, that.policyType)
+        && Objects.equals(comment, that.comment)
+        && Objects.equals(supportedObjectTypes, that.supportedObjectTypes)
+        && Objects.equals(content, that.content)
+        && Objects.equals(auditInfo, that.auditInfo);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(
+        id,
+        name,
+        namespace,
+        policyType,
+        comment,
+        enabled,
+        exclusive,
+        inheritable,
+        supportedObjectTypes,
+        content,
+        auditInfo);
+  }
+
+  private void validatePolicy() {
+    Preconditions.checkArgument(StringUtils.isNotBlank(name()), "Policy name 
cannot be blank");
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(policyType()), "Policy type cannot be blank");
+    Preconditions.checkArgument(content() != null, "Policy content cannot be 
null");
+    Preconditions.checkArgument(
+        supportedObjectTypes() != null && !supportedObjectTypes().isEmpty(),
+        "Policy must support at least one metadata object type");
+
+    BuiltInType builtInType = BuiltInType.fromPolicyType(policyType());
+    Preconditions.checkArgument(
+        builtInType != BuiltInType.CUSTOM || content() instanceof 
PolicyContents.CustomContent,
+        "Expected CustomContent for custom policy type, but got %s",
+        content().getClass().getName());
+    if (builtInType != BuiltInType.CUSTOM) {
+      Preconditions.checkArgument(
+          exclusive() == builtInType.exclusive(),
+          "Expected exclusive value %s for built-in policy type %s, but got 
%s",
+          builtInType.exclusive(),
+          policyType(),
+          exclusive());
+      Preconditions.checkArgument(
+          inheritable() == builtInType.inheritable(),
+          "Expected inheritable value %s for built-in policy type %s, but got 
%s",
+          builtInType.inheritable(),
+          policyType(),
+          inheritable());
+      Preconditions.checkArgument(
+          supportedObjectTypes().equals(builtInType.supportedObjectTypes()),
+          "Expected supported object types %s for built-in policy type %s, but 
got %s",
+          builtInType.supportedObjectTypes(),
+          policyType(),
+          supportedObjectTypes());
+    }
+  }
+
+  public static class Builder {
+    private Long id;
+    private String name;
+    private Namespace namespace;
+    private String policyType;
+    private String comment;
+    private boolean enabled = true;
+    private boolean exclusive;
+    private boolean inheritable;
+    private Set<MetadataObject.Type> supportedObjectTypes;
+    private PolicyContent content;
+    private AuditInfo auditInfo;
+
+    public Builder withId(Long id) {
+      this.id = id;
+      return this;
+    }
+
+    public Builder withName(String name) {
+      this.name = name;
+      return this;
+    }
+
+    public Builder withNamespace(Namespace namespace) {
+      this.namespace = namespace;
+      return this;
+    }
+
+    public Builder withPolicyType(String policyType) {
+      this.policyType = policyType;
+      return this;
+    }
+
+    public Builder withComment(String comment) {
+      this.comment = comment;
+      return this;
+    }
+
+    public Builder withEnabled(boolean enabled) {
+      this.enabled = enabled;
+      return this;
+    }
+
+    public Builder withExclusive(boolean exclusive) {
+      this.exclusive = exclusive;
+      return this;
+    }
+
+    public Builder withInheritable(boolean inheritable) {
+      this.inheritable = inheritable;
+      return this;
+    }
+
+    public Builder withSupportedObjectTypes(Set<MetadataObject.Type> 
supportedObjectTypes) {
+      this.supportedObjectTypes = supportedObjectTypes;
+      return this;
+    }
+
+    public Builder withContent(PolicyContent content) {
+      this.content = content;
+      return this;
+    }
+
+    public Builder withAuditInfo(AuditInfo auditInfo) {
+      this.auditInfo = auditInfo;
+      return this;
+    }
+
+    public PolicyEntity build() {
+      PolicyEntity policyEntity = new PolicyEntity();
+      policyEntity.id = id;
+      policyEntity.name = name;
+      policyEntity.namespace = namespace;
+      policyEntity.policyType = policyType;
+      policyEntity.comment = comment;
+      policyEntity.enabled = enabled;
+      policyEntity.exclusive = exclusive;
+      policyEntity.inheritable = inheritable;
+      policyEntity.supportedObjectTypes = supportedObjectTypes;
+      policyEntity.content = content;
+      policyEntity.auditInfo = auditInfo;
+      policyEntity.validate();
+
+      return policyEntity;
+    }
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java 
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
index 07965901dd..966265c79f 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
@@ -326,6 +326,9 @@ public class JDBCBackend implements RelationalBackend {
         return TagMetaService.getInstance()
             .deleteTagMetasByLegacyTimeline(
                 legacyTimeline, GARBAGE_COLLECTOR_SINGLE_DELETION_LIMIT);
+      case POLICY:
+        // todo: Implement hard delete logic for policies.
+        return 0;
       case COLUMN:
         return TableColumnMetaService.getInstance()
             .deleteColumnsByLegacyTimeline(legacyTimeline, 
GARBAGE_COLLECTOR_SINGLE_DELETION_LIMIT);
@@ -372,6 +375,10 @@ public class JDBCBackend implements RelationalBackend {
             .deleteFilesetVersionsByRetentionCount(
                 versionRetentionCount, 
GARBAGE_COLLECTOR_SINGLE_DELETION_LIMIT);
 
+      case POLICY:
+        // todo: Implement delete old version logic for policies.
+        return 0;
+
       default:
         throw new IllegalArgumentException(
             "Unsupported entity type when collectAndRemoveOldVersionData: " + 
entityType);
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/po/PolicyPO.java 
b/core/src/main/java/org/apache/gravitino/storage/relational/po/PolicyPO.java
new file mode 100644
index 0000000000..1241ed1636
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/po/PolicyPO.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.storage.relational.po;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import lombok.Getter;
+
+@Getter
+public class PolicyPO {
+  private Long policyId;
+  private String policyName;
+  private String policyType;
+  private Long metalakeId;
+  private boolean inheritable;
+  private boolean exclusive;
+  private String supportedObjectTypes;
+  private String auditInfo;
+  private Long currentVersion;
+  private Long lastVersion;
+  private Long deletedAt;
+  private PolicyVersionPO policyVersionPO;
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof PolicyPO)) {
+      return false;
+    }
+    PolicyPO policyPO = (PolicyPO) o;
+    return Objects.equal(policyId, policyPO.policyId)
+        && Objects.equal(policyName, policyPO.policyName)
+        && Objects.equal(policyType, policyPO.policyType)
+        && Objects.equal(metalakeId, policyPO.metalakeId)
+        && Objects.equal(inheritable, policyPO.inheritable)
+        && Objects.equal(exclusive, policyPO.exclusive)
+        && Objects.equal(supportedObjectTypes, policyPO.supportedObjectTypes)
+        && Objects.equal(auditInfo, policyPO.auditInfo)
+        && Objects.equal(currentVersion, policyPO.currentVersion)
+        && Objects.equal(lastVersion, policyPO.lastVersion)
+        && Objects.equal(policyVersionPO, policyPO.policyVersionPO)
+        && Objects.equal(deletedAt, policyPO.deletedAt);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(
+        policyId,
+        policyName,
+        policyType,
+        metalakeId,
+        inheritable,
+        exclusive,
+        supportedObjectTypes,
+        auditInfo,
+        currentVersion,
+        lastVersion,
+        policyVersionPO,
+        deletedAt);
+  }
+
+  public static class Builder {
+    private Long policyId;
+    private String policyName;
+    private String policyType;
+    private Long metalakeId;
+    private boolean inheritable;
+    private boolean exclusive;
+    private String supportedObjectTypes;
+    private String auditInfo;
+    private Long currentVersion;
+    private Long lastVersion;
+    private Long deletedAt;
+    private PolicyVersionPO policyVersionPO;
+
+    public Builder withPolicyId(Long policyId) {
+      this.policyId = policyId;
+      return this;
+    }
+
+    public Builder withPolicyName(String policyName) {
+      this.policyName = policyName;
+      return this;
+    }
+
+    public Builder withMetalakeId(Long metalakeId) {
+      this.metalakeId = metalakeId;
+      return this;
+    }
+
+    public Builder withPolicyType(String policyType) {
+      this.policyType = policyType;
+      return this;
+    }
+
+    public Builder withInheritable(boolean inheritable) {
+      this.inheritable = inheritable;
+      return this;
+    }
+
+    public Builder withExclusive(boolean exclusive) {
+      this.exclusive = exclusive;
+      return this;
+    }
+
+    public Builder withSupportedObjectTypes(String supportedObjectTypes) {
+      this.supportedObjectTypes = supportedObjectTypes;
+      return this;
+    }
+
+    public Builder withAuditInfo(String auditInfo) {
+      this.auditInfo = auditInfo;
+      return this;
+    }
+
+    public Builder withCurrentVersion(Long currentVersion) {
+      this.currentVersion = currentVersion;
+      return this;
+    }
+
+    public Builder withLastVersion(Long lastVersion) {
+      this.lastVersion = lastVersion;
+      return this;
+    }
+
+    public Builder withDeletedAt(Long deletedAt) {
+      this.deletedAt = deletedAt;
+      return this;
+    }
+
+    public Builder withPolicyVersionPO(PolicyVersionPO policyVersionPO) {
+      this.policyVersionPO = policyVersionPO;
+      return this;
+    }
+
+    public Long getMetalakeId() {
+      Preconditions.checkArgument(metalakeId != null, "Metalake id is 
required");
+      return metalakeId;
+    }
+
+    public PolicyPO build() {
+      validate();
+      PolicyPO policyPO = new PolicyPO();
+      policyPO.policyId = policyId;
+      policyPO.policyName = policyName;
+      policyPO.metalakeId = metalakeId;
+      policyPO.policyType = policyType;
+      policyPO.inheritable = inheritable;
+      policyPO.exclusive = exclusive;
+      policyPO.supportedObjectTypes = supportedObjectTypes;
+      policyPO.auditInfo = auditInfo;
+      policyPO.currentVersion = currentVersion;
+      policyPO.lastVersion = lastVersion;
+      policyPO.deletedAt = deletedAt;
+      policyPO.policyVersionPO = policyVersionPO;
+      return policyPO;
+    }
+
+    private void validate() {
+      Preconditions.checkArgument(policyId != null, "Policy id is required");
+      Preconditions.checkArgument(policyName != null, "Policy name is 
required");
+      Preconditions.checkArgument(metalakeId != null, "Metalake id is 
required");
+      Preconditions.checkArgument(policyType != null, "Policy type is 
required");
+      Preconditions.checkArgument(
+          supportedObjectTypes != null, "Supported object types is required");
+      Preconditions.checkArgument(currentVersion != null, "Current version is 
required");
+      Preconditions.checkArgument(lastVersion != null, "Last version is 
required");
+      Preconditions.checkArgument(deletedAt != null, "Deleted at is required");
+      Preconditions.checkArgument(auditInfo != null, "Audit info is required");
+      Preconditions.checkArgument(policyVersionPO != null, "Policy version is 
required");
+    }
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/po/PolicyVersionPO.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/po/PolicyVersionPO.java
new file mode 100644
index 0000000000..620d8f75af
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/po/PolicyVersionPO.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.storage.relational.po;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import lombok.Getter;
+
+@Getter
+public class PolicyVersionPO {
+  private Long id;
+  private Long metalakeId;
+  private Long policyId;
+  private Long version;
+  private String policyComment;
+  private boolean enabled;
+  private String content;
+  private Long deletedAt;
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof PolicyVersionPO)) {
+      return false;
+    }
+    PolicyVersionPO that = (PolicyVersionPO) o;
+    return Objects.equal(id, that.id)
+        && Objects.equal(metalakeId, that.metalakeId)
+        && Objects.equal(policyId, that.policyId)
+        && Objects.equal(version, that.version)
+        && Objects.equal(policyComment, that.policyComment)
+        && Objects.equal(enabled, that.enabled)
+        && Objects.equal(content, that.content)
+        && Objects.equal(deletedAt, that.deletedAt);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(
+        id, metalakeId, policyId, version, policyComment, enabled, content, 
deletedAt);
+  }
+
+  public static class Builder {
+    private Long id;
+    private Long metalakeId;
+    private Long policyId;
+    private Long version;
+    private String policyComment;
+    private boolean enabled;
+    private String content;
+    private Long deletedAt;
+
+    public Builder withId(Long id) {
+      this.id = id;
+      return this;
+    }
+
+    public Builder withMetalakeId(Long metalakeId) {
+      this.metalakeId = metalakeId;
+      return this;
+    }
+
+    public Builder withPolicyId(Long policyId) {
+      this.policyId = policyId;
+      return this;
+    }
+
+    public Builder withVersion(Long version) {
+      this.version = version;
+      return this;
+    }
+
+    public Builder withPolicyComment(String policyComment) {
+      this.policyComment = policyComment;
+      return this;
+    }
+
+    public Builder withEnabled(boolean enabled) {
+      this.enabled = enabled;
+      return this;
+    }
+
+    public Builder withContent(String content) {
+      this.content = content;
+      return this;
+    }
+
+    public Builder withDeletedAt(Long deletedAt) {
+      this.deletedAt = deletedAt;
+      return this;
+    }
+
+    public PolicyVersionPO build() {
+      validate();
+      PolicyVersionPO policyVersionPO = new PolicyVersionPO();
+      policyVersionPO.id = this.id;
+      policyVersionPO.metalakeId = this.metalakeId;
+      policyVersionPO.policyId = this.policyId;
+      policyVersionPO.version = this.version;
+      policyVersionPO.policyComment = this.policyComment;
+      policyVersionPO.enabled = this.enabled;
+      policyVersionPO.content = this.content;
+      policyVersionPO.deletedAt = this.deletedAt;
+      return policyVersionPO;
+    }
+
+    private void validate() {
+      Preconditions.checkArgument(metalakeId != null, "metalakeId is 
required");
+      Preconditions.checkArgument(policyId != null, "policyId is required");
+      Preconditions.checkArgument(version != null, "version is required");
+      Preconditions.checkArgument(content != null, "content is required");
+      Preconditions.checkArgument(deletedAt != null, "deletedAt is required");
+    }
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
index fc447a06d0..2b299886ee 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
@@ -20,12 +20,14 @@
 package org.apache.gravitino.storage.relational.utils;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
 import com.google.common.collect.Lists;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.Catalog;
@@ -48,6 +50,7 @@ import org.apache.gravitino.meta.FilesetEntity;
 import org.apache.gravitino.meta.GroupEntity;
 import org.apache.gravitino.meta.ModelEntity;
 import org.apache.gravitino.meta.ModelVersionEntity;
+import org.apache.gravitino.meta.PolicyEntity;
 import org.apache.gravitino.meta.RoleEntity;
 import org.apache.gravitino.meta.SchemaEntity;
 import org.apache.gravitino.meta.SchemaVersion;
@@ -55,6 +58,7 @@ import org.apache.gravitino.meta.TableEntity;
 import org.apache.gravitino.meta.TagEntity;
 import org.apache.gravitino.meta.TopicEntity;
 import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.policy.Policy;
 import org.apache.gravitino.rel.Column;
 import org.apache.gravitino.rel.expressions.Expression;
 import org.apache.gravitino.rel.types.Type;
@@ -71,6 +75,7 @@ import org.apache.gravitino.storage.relational.po.ModelPO;
 import org.apache.gravitino.storage.relational.po.ModelVersionAliasRelPO;
 import org.apache.gravitino.storage.relational.po.ModelVersionPO;
 import org.apache.gravitino.storage.relational.po.OwnerRelPO;
+import org.apache.gravitino.storage.relational.po.PolicyPO;
 import org.apache.gravitino.storage.relational.po.RolePO;
 import org.apache.gravitino.storage.relational.po.SchemaPO;
 import org.apache.gravitino.storage.relational.po.SecurableObjectPO;
@@ -1305,6 +1310,35 @@ public class POConverters {
     }
   }
 
+  public static PolicyEntity fromPolicyPO(PolicyPO policyPO, Namespace 
namespace) {
+    try {
+      return PolicyEntity.builder()
+          .withId(policyPO.getPolicyId())
+          .withName(policyPO.getPolicyName())
+          .withNamespace(namespace)
+          .withPolicyType(policyPO.getPolicyType())
+          .withComment(policyPO.getPolicyVersionPO().getPolicyComment())
+          .withEnabled(policyPO.getPolicyVersionPO().isEnabled())
+          .withExclusive(policyPO.isExclusive())
+          .withInheritable(policyPO.isInheritable())
+          .withSupportedObjectTypes(
+              JsonUtils.anyFieldMapper()
+                  .readValue(
+                      policyPO.getSupportedObjectTypes(),
+                      new TypeReference<Set<MetadataObject.Type>>() {}))
+          .withContent(
+              JsonUtils.anyFieldMapper()
+                  .readValue(
+                      policyPO.getPolicyVersionPO().getContent(),
+                      
Policy.BuiltInType.fromPolicyType(policyPO.getPolicyType()).contentClass()))
+          .withAuditInfo(
+              JsonUtils.anyFieldMapper().readValue(policyPO.getAuditInfo(), 
AuditInfo.class))
+          .build();
+    } catch (JsonProcessingException e) {
+      throw new RuntimeException("Failed to deserialize json object:", e);
+    }
+  }
+
   public static OwnerRelPO initializeOwnerRelPOsWithVersion(
       Long metalakeId,
       String ownerType,
diff --git a/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java 
b/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
index f904592789..63743c635b 100644
--- a/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
+++ b/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
@@ -82,6 +82,16 @@ public class NamespaceUtil {
     return Namespace.of(metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME, 
Entity.TAG_SCHEMA_NAME);
   }
 
+  /**
+   * Create a namespace for policy.
+   *
+   * @param metalake The metalake name
+   * @return A namespace for policy
+   */
+  public static Namespace ofPolicy(String metalake) {
+    return Namespace.of(metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME, 
Entity.POLICY_SCHEMA_NAME);
+  }
+
   /**
    * Create a namespace for user.
    *
diff --git a/core/src/test/java/org/apache/gravitino/meta/TestPolicyEntity.java 
b/core/src/test/java/org/apache/gravitino/meta/TestPolicyEntity.java
new file mode 100644
index 0000000000..dfbd33a85e
--- /dev/null
+++ b/core/src/test/java/org/apache/gravitino/meta/TestPolicyEntity.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.meta;
+
+import static org.apache.gravitino.policy.Policy.SUPPORTS_ALL_OBJECT_TYPES;
+
+import com.google.common.collect.ImmutableMap;
+import java.time.Instant;
+import java.util.Map;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.policy.PolicyContent;
+import org.apache.gravitino.policy.PolicyContents;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestPolicyEntity {
+
+  @Test
+  public void testPolicyEntityFields() {
+    AuditInfo auditInfo =
+        
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build();
+    Map<String, String> properties = ImmutableMap.of("k1", "v1");
+
+    ImmutableMap<String, Object> contentFields = 
ImmutableMap.of("target_file_size_bytes", 1000);
+    Namespace namespace = Namespace.of("m1", "c1", "s1");
+    PolicyContent content = PolicyContents.custom(contentFields, properties);
+    PolicyEntity policyEntity =
+        PolicyEntity.builder()
+            .withId(1L)
+            .withName("test")
+            .withNamespace(namespace)
+            .withComment("test comment")
+            .withPolicyType("my_compaction")
+            .withEnabled(false)
+            .withExclusive(true)
+            .withInheritable(true)
+            .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+            .withContent(content)
+            .withAuditInfo(auditInfo)
+            .build();
+
+    Assertions.assertEquals(1L, policyEntity.id());
+    Assertions.assertEquals("test", policyEntity.name());
+    Assertions.assertEquals(namespace, policyEntity.namespace());
+    Assertions.assertEquals("test comment", policyEntity.comment());
+    Assertions.assertEquals("my_compaction", policyEntity.policyType());
+    Assertions.assertFalse(policyEntity.enabled());
+    Assertions.assertTrue(policyEntity.exclusive());
+    Assertions.assertTrue(policyEntity.inheritable());
+    Assertions.assertEquals(SUPPORTS_ALL_OBJECT_TYPES, 
policyEntity.supportedObjectTypes());
+    Assertions.assertEquals(content, policyEntity.content());
+    Assertions.assertEquals(auditInfo, policyEntity.auditInfo());
+
+    PolicyEntity policyEntity2 =
+        PolicyEntity.builder()
+            .withId(1L)
+            .withName("test")
+            .withNamespace(namespace)
+            .withPolicyType("my_compaction")
+            .withEnabled(false)
+            .withExclusive(true)
+            .withInheritable(true)
+            .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+            .withContent(content)
+            .withAuditInfo(auditInfo)
+            .build();
+    Assertions.assertNull(policyEntity2.comment());
+  }
+
+  @Test
+  public void testWithoutRequiredFields() {
+    Assertions.assertThrows(IllegalArgumentException.class, () -> 
PolicyEntity.builder().build());
+
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () ->
+            PolicyEntity.builder()
+                .withId(1L)
+                .withNamespace(Namespace.of("m1", "c1", "s1"))
+                .withAuditInfo(
+                    
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build())
+                .build());
+
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () ->
+            PolicyEntity.builder()
+                .withId(1L)
+                .withName("test")
+                .withNamespace(Namespace.of("m1", "c1", "s1"))
+                .withPolicyType("my_compaction")
+                .withAuditInfo(
+                    
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build())
+                .build());
+
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () ->
+            PolicyEntity.builder()
+                .withId(1L)
+                .withName("test")
+                .withNamespace(Namespace.of("m1", "c1", "s1"))
+                .withPolicyType("my_compaction")
+                .withSupportedObjectTypes(SUPPORTS_ALL_OBJECT_TYPES)
+                .withAuditInfo(
+                    
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build())
+                .build());
+  }
+}
diff --git 
a/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
 
b/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
index 858b1a8dd7..2397ca99e5 100644
--- 
a/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
+++ 
b/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
@@ -20,6 +20,7 @@
 package org.apache.gravitino.storage.relational.utils;
 
 import static org.apache.gravitino.file.Fileset.LOCATION_NAME_UNKNOWN;
+import static org.apache.gravitino.policy.Policy.SUPPORTS_ALL_OBJECT_TYPES;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
@@ -36,6 +37,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.gravitino.Catalog;
 import org.apache.gravitino.Entity;
@@ -52,11 +54,14 @@ import org.apache.gravitino.meta.ColumnEntity;
 import org.apache.gravitino.meta.FilesetEntity;
 import org.apache.gravitino.meta.ModelEntity;
 import org.apache.gravitino.meta.ModelVersionEntity;
+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.TagEntity;
 import org.apache.gravitino.meta.TopicEntity;
+import org.apache.gravitino.policy.PolicyContent;
+import org.apache.gravitino.policy.PolicyContents;
 import org.apache.gravitino.rel.expressions.Expression;
 import org.apache.gravitino.rel.expressions.literals.Literals;
 import org.apache.gravitino.rel.types.Type;
@@ -70,6 +75,8 @@ import org.apache.gravitino.storage.relational.po.ModelPO;
 import org.apache.gravitino.storage.relational.po.ModelVersionAliasRelPO;
 import org.apache.gravitino.storage.relational.po.ModelVersionPO;
 import org.apache.gravitino.storage.relational.po.OwnerRelPO;
+import org.apache.gravitino.storage.relational.po.PolicyPO;
+import org.apache.gravitino.storage.relational.po.PolicyVersionPO;
 import org.apache.gravitino.storage.relational.po.SchemaPO;
 import org.apache.gravitino.storage.relational.po.TablePO;
 import org.apache.gravitino.storage.relational.po.TagMetadataObjectRelPO;
@@ -768,6 +775,44 @@ public class TestPOConverters {
     assertEquals("test1", updatePO2.getFilesetName());
   }
 
+  @Test
+  public void testFromPolicyPO() throws JsonProcessingException {
+    PolicyContent content = PolicyContents.custom(null, null);
+    PolicyVersionPO policyVersionPO =
+        createPolicyVersionPO(1L, 1L, 1L, "test comment", true, content);
+    PolicyPO policyPO =
+        createPolicyPO(
+            1L, "test", "my_type", 1L, true, true, SUPPORTS_ALL_OBJECT_TYPES, 
policyVersionPO);
+
+    PolicyEntity expectedPolicy =
+        createPolicy(
+            1L,
+            "test",
+            NamespaceUtil.ofPolicy("test_metalake"),
+            "my_type",
+            "test comment",
+            true,
+            true,
+            true,
+            SUPPORTS_ALL_OBJECT_TYPES,
+            content);
+
+    PolicyEntity convertedPolicy =
+        POConverters.fromPolicyPO(policyPO, 
NamespaceUtil.ofPolicy("test_metalake"));
+
+    assertEquals(expectedPolicy.id(), convertedPolicy.id());
+    assertEquals(expectedPolicy.name(), convertedPolicy.name());
+    assertEquals(expectedPolicy.namespace(), convertedPolicy.namespace());
+    assertEquals(expectedPolicy.auditInfo().creator(), 
convertedPolicy.auditInfo().creator());
+    assertEquals(expectedPolicy.policyType(), convertedPolicy.policyType());
+    assertEquals(expectedPolicy.comment(), convertedPolicy.comment());
+    assertEquals(expectedPolicy.enabled(), convertedPolicy.enabled());
+    assertEquals(expectedPolicy.inheritable(), convertedPolicy.inheritable());
+    assertEquals(expectedPolicy.exclusive(), convertedPolicy.exclusive());
+    assertEquals(expectedPolicy.supportedObjectTypes(), 
convertedPolicy.supportedObjectTypes());
+    assertEquals(expectedPolicy.content(), convertedPolicy.content());
+  }
+
   @Test
   public void testFromTagPO() throws JsonProcessingException {
     TagPO tagPO = createTagPO(1L, "test", 1L, "this is test");
@@ -1487,6 +1532,83 @@ public class TestPOConverters {
         .build();
   }
 
+  private static PolicyEntity createPolicy(
+      Long id,
+      String name,
+      Namespace namespace,
+      String type,
+      String comment,
+      boolean enabled,
+      boolean exclusive,
+      boolean inheritable,
+      Set<MetadataObject.Type> supportedObjectTypes,
+      PolicyContent content) {
+    AuditInfo auditInfo =
+        
AuditInfo.builder().withCreator("creator").withCreateTime(FIX_INSTANT).build();
+    return PolicyEntity.builder()
+        .withId(id)
+        .withName(name)
+        .withNamespace(namespace)
+        .withPolicyType(type)
+        .withComment(comment)
+        .withEnabled(enabled)
+        .withExclusive(exclusive)
+        .withInheritable(inheritable)
+        .withSupportedObjectTypes(supportedObjectTypes)
+        .withContent(content)
+        .withAuditInfo(auditInfo)
+        .build();
+  }
+
+  private static PolicyPO createPolicyPO(
+      Long id,
+      String name,
+      String policyType,
+      Long metalakeId,
+      boolean inheritable,
+      boolean exclusive,
+      Set<MetadataObject.Type> supportedObjectTypes,
+      PolicyVersionPO policyVersionPO)
+      throws JsonProcessingException {
+    AuditInfo auditInfo =
+        
AuditInfo.builder().withCreator("creator").withCreateTime(FIX_INSTANT).build();
+    return PolicyPO.builder()
+        .withPolicyId(id)
+        .withPolicyName(name)
+        .withPolicyType(policyType)
+        .withMetalakeId(metalakeId)
+        .withInheritable(inheritable)
+        .withExclusive(exclusive)
+        .withSupportedObjectTypes(
+            
JsonUtils.anyFieldMapper().writeValueAsString(supportedObjectTypes))
+        
.withAuditInfo(JsonUtils.anyFieldMapper().writeValueAsString(auditInfo))
+        .withCurrentVersion(1L)
+        .withLastVersion(1L)
+        .withDeletedAt(0L)
+        .withPolicyVersionPO(policyVersionPO)
+        .build();
+  }
+
+  private static PolicyVersionPO createPolicyVersionPO(
+      Long id,
+      Long metalakeId,
+      Long policyId,
+      String comment,
+      boolean enabled,
+      PolicyContent content)
+      throws JsonProcessingException {
+    return PolicyVersionPO.builder()
+        .withId(id)
+        .withMetalakeId(metalakeId)
+        .withPolicyId(policyId)
+        .withVersion(1L)
+        .withPolicyComment(comment)
+        .withEnabled(enabled)
+        .withContent(JsonUtils.anyFieldMapper().writeValueAsString(content))
+        .withDeletedAt(0L)
+        .build();
+  }
+
   private static TagPO createTagPO(Long id, String name, Long metalakeId, 
String comment)
       throws JsonProcessingException {
     AuditInfo auditInfo =

Reply via email to