From 967398ca6bc98546f2a787e61f52f81d025f153c Mon Sep 17 00:00:00 2001 From: Honah J Date: Thu, 6 Mar 2025 19:23:15 -0800 Subject: [PATCH 1/7] PolicyEntity and PolicyType --- .../core/entity/PolarisEntityType.java | 3 +- .../polaris/core/policy/PolicyEntity.java | 128 ++++++++++++++++++ .../polaris/core/policy/PolicyType.java | 84 ++++++++++++ .../core/policy/PredefinedPolicyType.java | 108 +++++++++++++++ .../polaris/core/policy/PolicyEntityTest.java | 53 ++++++++ .../polaris/core/policy/PolicyTypeTest.java | 56 ++++++++ 6 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java create mode 100644 polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java create mode 100644 polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyType.java create mode 100644 polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java create mode 100644 polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyTypeTest.java diff --git a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityType.java b/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityType.java index af50eed6ff..4a3eada34a 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityType.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityType.java @@ -34,7 +34,8 @@ public enum PolarisEntityType { // generic table is either a view or a real table TABLE_LIKE(7, NAMESPACE, false, false), TASK(8, ROOT, false, false), - FILE(9, TABLE_LIKE, false, false); + FILE(9, TABLE_LIKE, false, false), + POLICY(10, NAMESPACE, false, false); // to efficiently map a code to its corresponding entity type, use a reverse array which // is initialized below diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java new file mode 100644 index 0000000000..c66b79bef5 --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java @@ -0,0 +1,128 @@ +/* + * 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.polaris.core.policy; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.base.Preconditions; +import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.rest.RESTUtil; +import org.apache.polaris.core.entity.NamespaceEntity; +import org.apache.polaris.core.entity.PolarisBaseEntity; +import org.apache.polaris.core.entity.PolarisEntity; +import org.apache.polaris.core.entity.PolarisEntityType; + +public class PolicyEntity extends PolarisEntity { + + public static final String POLICY_TYPE_CODE_KEY = "policy-type-code"; + public static final String POLICY_DESCRIPTION_KEY = "policy-description"; + public static final String POLICY_VERSION_KEY = "policy-version"; + public static final String POLICY_CONTENT_KEY = "policy-content"; + + PolicyEntity(PolarisBaseEntity sourceEntity) { + super(sourceEntity); + } + + public static PolicyEntity of(PolarisBaseEntity sourceEntity) { + if (sourceEntity != null) { + return new PolicyEntity(sourceEntity); + } + + return null; + } + + @JsonIgnore + public PolicyType getPolicyType() { + return PolicyType.fromCode(getPolicyTypeCode()); + } + + @JsonIgnore + public int getPolicyTypeCode() { + String policyTypeCode = getPropertiesAsMap().get(POLICY_TYPE_CODE_KEY); + if (policyTypeCode != null) { + return Integer.parseInt(policyTypeCode); + } + + return -1; + } + + @JsonIgnore + public String getDescription() { + return getPropertiesAsMap().get(POLICY_DESCRIPTION_KEY); + } + + @JsonIgnore + public String getContent() { + return getPropertiesAsMap().get(POLICY_CONTENT_KEY); + } + + @JsonIgnore + public String getPolicyVersion() { + return getPropertiesAsMap().get(POLICY_VERSION_KEY); + } + + public static class Builder extends PolarisEntity.BaseBuilder { + public Builder(Namespace namespace, String policyName) { + super(); + setType(PolarisEntityType.POLICY); + setParentNamespace(namespace); + setName(policyName); + setPolicyVersion(0); + } + + public Builder(PolicyEntity original) { + super(original); + } + + @Override + public PolicyEntity build() { + Preconditions.checkArgument( + properties.get(POLICY_TYPE_CODE_KEY) != null, "Policy type must be specified"); + + return new PolicyEntity(buildBase()); + } + + public Builder setParentNamespace(Namespace namespace) { + if (namespace != null && !namespace.isEmpty()) { + internalProperties.put( + NamespaceEntity.PARENT_NAMESPACE_KEY, RESTUtil.encodeNamespace(namespace)); + } + return this; + } + + public Builder setPolicyType(PolicyType policyType) { + properties.put(POLICY_TYPE_CODE_KEY, Integer.toString(policyType.getCode())); + return this; + } + + public Builder setDescription(String description) { + properties.put(POLICY_DESCRIPTION_KEY, description); + return this; + } + + public Builder setPolicyVersion(long version) { + properties.put(POLICY_VERSION_KEY, Long.toString(version)); + return this; + } + + public Builder setContent(String content) { + properties.put(POLICY_CONTENT_KEY, content); + return this; + } + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java new file mode 100644 index 0000000000..b280ffe8f4 --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java @@ -0,0 +1,84 @@ +/* + * 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.polaris.core.policy; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import jakarta.annotation.Nullable; + +/** + * Represents a policy type in Polaris. A policy type defines a category of policies that may be + * either predefined or custom (user-defined). + * + *

A policy type can be either inheritable or non-inheritable. Inheritable policies are passed + * down to lower-level entities (e.g., from a namespace to a table). + */ +public interface PolicyType { + + /** + * Retrieves the unique type code associated with this policy type. + * + * @return the type code of the policy type + */ + @JsonValue + int getCode(); + + /** + * Retrieves the human-readable name of this policy type. + * + * @return the name of the policy type + */ + String getName(); + + /** + * Determines whether this policy type is inheritable. + * + * @return {@code true} if the policy type is inheritable, otherwise {@code false} + */ + boolean isInheritable(); + + /** + * Retrieves a {@link PolicyType} instance corresponding to the given type code. + * + *

This method searches for the policy type in predefined policy types. If a custom policy type + * storage mechanism is implemented in the future, it may also check registered custom policy + * types. + * + * @param code the type code of the policy type + * @return the corresponding {@link PolicyType}, or {@code null} if no matching type is found + */ + @JsonCreator + static @Nullable PolicyType fromCode(int code) { + return PredefinedPolicyType.fromCode(code); + } + + /** + * Retrieves a {@link PolicyType} instance corresponding to the given policy name. + * + *

This method searches for the policy type in predefined policy types. If a custom policy type + * storage mechanism is implemented in the future, it may also check registered custom policy + * types. + * + * @param name the name of the policy type + * @return the corresponding {@link PolicyType}, or {@code null} if no matching type is found + */ + static @Nullable PolicyType fromName(String name) { + return PredefinedPolicyType.fromName(name); + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyType.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyType.java new file mode 100644 index 0000000000..c43bc82f84 --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyType.java @@ -0,0 +1,108 @@ +/* + * 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.polaris.core.policy; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.ImmutableMap; +import jakarta.annotation.Nullable; + +/* Represents all predefined policy types in Polaris */ +public enum PredefinedPolicyType implements PolicyType { + DATA_COMPACTION(0, "system.data-compaction", true), + METADATA_COMPACTION(1, "system.metadata-compaction", true), + ORPHAN_FILE_REMOVAL(2, "system.orphan-file-removal", true), + SNAPSHOT_RETENTION(3, "system.snapshot-retention", true); + + private final int code; + private final String name; + private final boolean isInheritable; + private static final PredefinedPolicyType[] REVERSE_CODE_MAPPING_ARRAY; + private static final ImmutableMap REVERSE_NAME_MAPPING_ARRAY; + + static { + int maxId = 0; + for (PredefinedPolicyType policyType : PredefinedPolicyType.values()) { + if (maxId < policyType.code) { + maxId = policyType.code; + } + } + + REVERSE_CODE_MAPPING_ARRAY = new PredefinedPolicyType[maxId + 1]; + ImmutableMap.Builder builder = ImmutableMap.builder(); + // populate both + for (PredefinedPolicyType policyType : PredefinedPolicyType.values()) { + REVERSE_CODE_MAPPING_ARRAY[policyType.code] = policyType; + builder.put(policyType.name, policyType); + } + REVERSE_NAME_MAPPING_ARRAY = builder.build(); + } + + PredefinedPolicyType(int code, String name, boolean isInheritable) { + this.code = code; + this.name = name; + this.isInheritable = isInheritable; + } + + /** {@inheritDoc} */ + @Override + @JsonValue + public int getCode() { + return code; + } + + /** {@inheritDoc} */ + @Override + public String getName() { + return name; + } + + /** {@inheritDoc} */ + @Override + public boolean isInheritable() { + return isInheritable; + } + + /** + * Retrieves a {@link PredefinedPolicyType} instance corresponding to the given type code. + * + * @param code the type code of the predefined policy type + * @return the corresponding {@link PredefinedPolicyType}, or {@code null} if no matching type is + * found + */ + @JsonCreator + public static @Nullable PredefinedPolicyType fromCode(int code) { + if (code >= REVERSE_CODE_MAPPING_ARRAY.length) { + return null; + } + + return REVERSE_CODE_MAPPING_ARRAY[code]; + } + + /** + * Retrieves a {@link PredefinedPolicyType} instance corresponding to the given policy name. + * + * @param name the name of the predefined policy type + * @return the corresponding {@link PredefinedPolicyType}, or {@code null} if no matching type is + * found + */ + public static @Nullable PredefinedPolicyType fromName(String name) { + return REVERSE_NAME_MAPPING_ARRAY.get(name); + } +} diff --git a/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java new file mode 100644 index 0000000000..c9ff8f0e1a --- /dev/null +++ b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java @@ -0,0 +1,53 @@ +/* + * 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.polaris.core.policy; + +import java.util.stream.Stream; +import org.apache.iceberg.catalog.Namespace; +import org.apache.polaris.core.entity.PolarisEntityType; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class PolicyEntityTest { + + static Stream policyTypes() { + return Stream.of( + Arguments.of(PredefinedPolicyType.DATA_COMPACTION), + Arguments.of(PredefinedPolicyType.METADATA_COMPACTION), + Arguments.of(PredefinedPolicyType.ORPHAN_FILE_REMOVAL), + Arguments.of(PredefinedPolicyType.METADATA_COMPACTION)); + } + + @ParameterizedTest + @MethodSource("policyTypes") + public void testPolicyEntity(PolicyType policyType) { + PolicyEntity entity = + new PolicyEntity.Builder(Namespace.of("NS1"), "testPolicy") + .setPolicyType(policyType) + .setContent("test_content") + .setPolicyVersion(0) + .build(); + Assertions.assertThat(entity.getType()).isEqualTo(PolarisEntityType.POLICY); + Assertions.assertThat(entity.getPolicyType()).isEqualTo(policyType); + Assertions.assertThat(entity.getPolicyTypeCode()).isEqualTo(policyType.getCode()); + Assertions.assertThat(entity.getContent()).isEqualTo("test_content"); + } +} diff --git a/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyTypeTest.java b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyTypeTest.java new file mode 100644 index 0000000000..a690cfd06d --- /dev/null +++ b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyTypeTest.java @@ -0,0 +1,56 @@ +/* + * 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.polaris.core.policy; + +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class PolicyTypeTest { + + static Stream predefinedPolicyTypes() { + return Stream.of( + Arguments.of(0, "system.data-compaction", true), + Arguments.of(1, "system.metadata-compaction", true), + Arguments.of(2, "system.orphan-file-removal", true), + Arguments.of(3, "system.snapshot-retention", true)); + } + + @ParameterizedTest + @MethodSource("predefinedPolicyTypes") + public void testPredefinedPolicyTypeFromCode(int code, String name, boolean isInheritable) { + PolicyType policyType = PolicyType.fromCode(code); + Assertions.assertThat(policyType).isNotNull(); + Assertions.assertThat(policyType.getCode()).isEqualTo(code); + Assertions.assertThat(policyType.getName()).isEqualTo(name); + Assertions.assertThat(policyType.isInheritable()).isEqualTo(isInheritable); + } + + @ParameterizedTest + @MethodSource("predefinedPolicyTypes") + public void testPredefinedPolicyTypeFromName(int code, String name, boolean isInheritable) { + PolicyType policyType = PolicyType.fromName(name); + Assertions.assertThat(policyType).isNotNull(); + Assertions.assertThat(policyType.getCode()).isEqualTo(code); + Assertions.assertThat(policyType.getName()).isEqualTo(name); + Assertions.assertThat(policyType.isInheritable()).isEqualTo(isInheritable); + } +} From 5a5c73ee386cae99a99e0fd01f27462681cae8dd Mon Sep 17 00:00:00 2001 From: Honah J Date: Thu, 13 Mar 2025 14:54:02 -0700 Subject: [PATCH 2/7] add more tests --- .../core/persistence/ResolverTest.java | 17 ++++ .../PolarisTestMetaStoreManager.java | 84 ++++++++++++++++++- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java b/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java index fbcb2f9c94..4c0918a358 100644 --- a/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java +++ b/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java @@ -105,6 +105,9 @@ public class ResolverTest { * - (N1/N4) * - N5/N6/T5 * - N5/N6/T6 + * - N7/N8/POL1 + * - N7/N8/POL2 + * - N7/POL3 * - R1(TABLE_READ on N1/N2, VIEW_CREATE on C, TABLE_LIST on N2, TABLE_DROP on N5/N6/T5) * - R2(TABLE_WRITE_DATA on N5, VIEW_LIST on C) * - PR1(R1, R2) @@ -230,6 +233,20 @@ void testResolvePath(boolean useCache) { new ResolverPath(List.of("N5", "N6", "T5"), PolarisEntityType.TABLE_LIKE); this.resolveDriver(this.cache, "test", N5_N6_T5, null, null); + // N7/N8 which exists + ResolverPath N7_N8 = new ResolverPath(List.of("N7", "N8"), PolarisEntityType.NAMESPACE); + this.resolveDriver(this.cache, "test", N7_N8, null, null); + + // N7/N8/POL1 which exists + ResolverPath N7_N8_POL1 = + new ResolverPath(List.of("N7", "N8", "POL1"), PolarisEntityType.POLICY); + this.resolveDriver(this.cache, "test", N7_N8_POL1, null, null); + + // N7/POL3 which exists + ResolverPath N7_POL3 = + new ResolverPath(List.of("N7", "POL3"), PolarisEntityType.POLICY); + this.resolveDriver(this.cache, "test", N7_POL3, null, null); + // Error scenarios: N5/N6/T8 which does not exists ResolverPath N5_N6_T8 = new ResolverPath(List.of("N5", "N6", "T8"), PolarisEntityType.TABLE_LIKE); diff --git a/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java b/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java index 55849baf44..158010c306 100644 --- a/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java +++ b/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java @@ -49,6 +49,9 @@ import org.apache.polaris.core.persistence.dao.entity.EntityResult; import org.apache.polaris.core.persistence.dao.entity.LoadGrantsResult; import org.apache.polaris.core.persistence.dao.entity.ResolvedEntityResult; +import org.apache.polaris.core.policy.PolicyEntity; +import org.apache.polaris.core.policy.PolicyType; +import org.apache.polaris.core.policy.PredefinedPolicyType; import org.assertj.core.api.Assertions; /** Test the Polaris persistence layer */ @@ -583,12 +586,22 @@ public PolarisBaseEntity createEntity( PolarisEntityType entityType, PolarisEntitySubType entitySubType, String name) { + return createEntity(catalogPath, entityType, entitySubType, name, null); + } + + public PolarisBaseEntity createEntity( + List catalogPath, + PolarisEntityType entityType, + PolarisEntitySubType entitySubType, + String name, + Map properties) { return createEntity( catalogPath, entityType, entitySubType, name, - polarisMetaStoreManager.generateNewEntityId(this.polarisCallContext).getId()); + polarisMetaStoreManager.generateNewEntityId(this.polarisCallContext).getId(), + properties); } PolarisBaseEntity createEntity( @@ -597,6 +610,16 @@ PolarisBaseEntity createEntity( PolarisEntitySubType entitySubType, String name, long entityId) { + return createEntity(catalogPath, entityType, entitySubType, name, entityId, null); + } + + PolarisBaseEntity createEntity( + List catalogPath, + PolarisEntityType entityType, + PolarisEntitySubType entitySubType, + String name, + long entityId, + Map properties) { long parentId; long catalogId; if (catalogPath != null) { @@ -608,6 +631,7 @@ PolarisBaseEntity createEntity( } PolarisBaseEntity newEntity = new PolarisBaseEntity(catalogId, entityId, entityType, entitySubType, parentId, name); + newEntity.setPropertiesAsMap(properties); PolarisBaseEntity entity = polarisMetaStoreManager .createEntityIfNotExists(this.polarisCallContext, catalogPath, newEntity) @@ -650,6 +674,26 @@ PolarisBaseEntity createEntity( return createEntity(catalogPath, entityType, PolarisEntitySubType.NULL_SUBTYPE, name); } + PolarisBaseEntity createEntity( + List catalogPath, + PolarisEntityType entityType, + String name, + Map properties) { + return createEntity( + catalogPath, entityType, PolarisEntitySubType.NULL_SUBTYPE, name, properties); + } + + /** Create a policy entity */ + PolicyEntity createPolicy( + List catalogPath, String name, PolicyType policyType) { + return PolicyEntity.of( + createEntity( + catalogPath, + PolarisEntityType.POLICY, + name, + Map.of("policy-type-code", Integer.toString(policyType.getCode())))); + } + /** Drop the entity if it exists. */ void dropEntity(List catalogPath, PolarisBaseEntity entityToDrop) { // see if the entity exists @@ -922,7 +966,7 @@ void revokeToGrantee( /** * Create a test catalog. This is a new catalog which will have the following objects (N is for a - * namespace, T for a table, V for a view, R for a role, P for a principal): + * namespace, T for a table, V for a view, R for a role, P for a principal, POL for a policy): * *

    * - C
@@ -935,6 +979,9 @@ void revokeToGrantee(
    * - (N1/N4)
    * - N5/N6/T5
    * - N5/N6/T6
+   * - N7/N8/POL1
+   * - N7/N8/POL2
+   * - N7/POL3
    * - R1(TABLE_READ on N1/N2, VIEW_CREATE on C, TABLE_LIST on N1/N2, TABLE_DROP on N5/N6/T5)
    * - R2(TABLE_WRITE_DATA on N5, VIEW_LIST on C)
    * - PR1(R1, R2)
@@ -1001,6 +1048,14 @@ PolarisBaseEntity createTestCatalog(String catalogName) {
         PolarisEntitySubType.TABLE,
         "T6");
 
+    PolarisBaseEntity N7 = this.createEntity(List.of(catalog), PolarisEntityType.NAMESPACE, "N7");
+    PolarisBaseEntity N7_N8 =
+        this.createEntity(List.of(catalog, N7), PolarisEntityType.NAMESPACE, "N8");
+    this.createPolicy(List.of(catalog, N7, N7_N8), "POL1", PredefinedPolicyType.DATA_COMPACTION);
+    this.createPolicy(
+        List.of(catalog, N7, N7_N8), "POL2", PredefinedPolicyType.METADATA_COMPACTION);
+    this.createPolicy(List.of(catalog, N7), "POL3", PredefinedPolicyType.SNAPSHOT_RETENTION);
+
     // the two catalog roles
     PolarisBaseEntity R1 =
         this.createEntity(List.of(catalog), PolarisEntityType.CATALOG_ROLE, "R1");
@@ -1670,6 +1725,17 @@ void testCreateTestCatalog() {
         PolarisEntityType.TABLE_LIKE,
         PolarisEntitySubType.TABLE,
         "T6");
+    PolarisBaseEntity N7 =
+        this.ensureExistsByName(List.of(catalog), PolarisEntityType.NAMESPACE, "N7");
+    PolarisBaseEntity N7_N8 =
+        this.ensureExistsByName(
+            List.of(catalog, N7),
+            PolarisEntityType.NAMESPACE,
+            PolarisEntitySubType.ANY_SUBTYPE,
+            "N8");
+    this.ensureExistsByName(List.of(catalog, N7, N7_N8), PolarisEntityType.POLICY, "POL1");
+    this.ensureExistsByName(List.of(catalog, N7, N7_N8), PolarisEntityType.POLICY, "POL2");
+    this.ensureExistsByName(List.of(catalog, N7), PolarisEntityType.POLICY, "POL3");
     PolarisBaseEntity R1 =
         this.ensureExistsByName(List.of(catalog), PolarisEntityType.CATALOG_ROLE, "R1");
     PolarisBaseEntity R2 =
@@ -1703,7 +1769,8 @@ void testBrowse() {
         PolarisEntityType.NAMESPACE,
         List.of(
             ImmutablePair.of("N1", PolarisEntitySubType.NULL_SUBTYPE),
-            ImmutablePair.of("N5", PolarisEntitySubType.NULL_SUBTYPE)));
+            ImmutablePair.of("N5", PolarisEntitySubType.NULL_SUBTYPE),
+                ImmutablePair.of("N7", PolarisEntitySubType.NULL_SUBTYPE)));
 
     // should see 3 top-level catalog roles including the admin one
     this.validateListReturn(
@@ -1804,6 +1871,17 @@ void testBrowse() {
                 PolarisEntitySubType.NULL_SUBTYPE),
             ImmutablePair.of("PR1", PolarisEntitySubType.NULL_SUBTYPE),
             ImmutablePair.of("PR2", PolarisEntitySubType.NULL_SUBTYPE)));
+
+    // list 2 policies under N7_N8
+    PolarisBaseEntity N7 =
+        this.ensureExistsByName(List.of(catalog), PolarisEntityType.NAMESPACE, "N7");
+    PolarisBaseEntity N7_N8 =
+        this.ensureExistsByName(List.of(catalog, N7), PolarisEntityType.NAMESPACE, "N8");
+    this.validateListReturn(
+        List.of(catalog, N7, N7_N8),
+        PolarisEntityType.POLICY,
+        List.of(ImmutablePair.of("POL1", PolarisEntitySubType.NULL_SUBTYPE),
+            ImmutablePair.of("POL2", PolarisEntitySubType.NULL_SUBTYPE)));
   }
 
   /** Test that entity updates works well */

From 4cfb7f11b4a339bfbc8dec22b2944c1aa7af531b Mon Sep 17 00:00:00 2001
From: Honah J 
Date: Thu, 13 Mar 2025 14:58:51 -0700
Subject: [PATCH 3/7] use plural form

---
 .../polaris/core/policy/PolicyType.java       |  4 +--
 ...cyType.java => PredefinedPolicyTypes.java} | 28 +++++++++----------
 .../polaris/core/policy/PolicyEntityTest.java |  8 +++---
 .../PolarisTestMetaStoreManager.java          | 13 +++++----
 4 files changed, 27 insertions(+), 26 deletions(-)
 rename polaris-core/src/main/java/org/apache/polaris/core/policy/{PredefinedPolicyType.java => PredefinedPolicyTypes.java} (70%)

diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java
index b280ffe8f4..029d5c37ed 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyType.java
@@ -65,7 +65,7 @@ public interface PolicyType {
    */
   @JsonCreator
   static @Nullable PolicyType fromCode(int code) {
-    return PredefinedPolicyType.fromCode(code);
+    return PredefinedPolicyTypes.fromCode(code);
   }
 
   /**
@@ -79,6 +79,6 @@ public interface PolicyType {
    * @return the corresponding {@link PolicyType}, or {@code null} if no matching type is found
    */
   static @Nullable PolicyType fromName(String name) {
-    return PredefinedPolicyType.fromName(name);
+    return PredefinedPolicyTypes.fromName(name);
   }
 }
diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyType.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyTypes.java
similarity index 70%
rename from polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyType.java
rename to polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyTypes.java
index c43bc82f84..babe1ac7a9 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyType.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PredefinedPolicyTypes.java
@@ -24,7 +24,7 @@
 import jakarta.annotation.Nullable;
 
 /* Represents all predefined policy types in Polaris */
-public enum PredefinedPolicyType implements PolicyType {
+public enum PredefinedPolicyTypes implements PolicyType {
   DATA_COMPACTION(0, "system.data-compaction", true),
   METADATA_COMPACTION(1, "system.metadata-compaction", true),
   ORPHAN_FILE_REMOVAL(2, "system.orphan-file-removal", true),
@@ -33,28 +33,28 @@ public enum PredefinedPolicyType implements PolicyType {
   private final int code;
   private final String name;
   private final boolean isInheritable;
-  private static final PredefinedPolicyType[] REVERSE_CODE_MAPPING_ARRAY;
-  private static final ImmutableMap REVERSE_NAME_MAPPING_ARRAY;
+  private static final PredefinedPolicyTypes[] REVERSE_CODE_MAPPING_ARRAY;
+  private static final ImmutableMap REVERSE_NAME_MAPPING_ARRAY;
 
   static {
     int maxId = 0;
-    for (PredefinedPolicyType policyType : PredefinedPolicyType.values()) {
+    for (PredefinedPolicyTypes policyType : PredefinedPolicyTypes.values()) {
       if (maxId < policyType.code) {
         maxId = policyType.code;
       }
     }
 
-    REVERSE_CODE_MAPPING_ARRAY = new PredefinedPolicyType[maxId + 1];
-    ImmutableMap.Builder builder = ImmutableMap.builder();
+    REVERSE_CODE_MAPPING_ARRAY = new PredefinedPolicyTypes[maxId + 1];
+    ImmutableMap.Builder builder = ImmutableMap.builder();
     // populate both
-    for (PredefinedPolicyType policyType : PredefinedPolicyType.values()) {
+    for (PredefinedPolicyTypes policyType : PredefinedPolicyTypes.values()) {
       REVERSE_CODE_MAPPING_ARRAY[policyType.code] = policyType;
       builder.put(policyType.name, policyType);
     }
     REVERSE_NAME_MAPPING_ARRAY = builder.build();
   }
 
-  PredefinedPolicyType(int code, String name, boolean isInheritable) {
+  PredefinedPolicyTypes(int code, String name, boolean isInheritable) {
     this.code = code;
     this.name = name;
     this.isInheritable = isInheritable;
@@ -80,14 +80,14 @@ public boolean isInheritable() {
   }
 
   /**
-   * Retrieves a {@link PredefinedPolicyType} instance corresponding to the given type code.
+   * Retrieves a {@link PredefinedPolicyTypes} instance corresponding to the given type code.
    *
    * @param code the type code of the predefined policy type
-   * @return the corresponding {@link PredefinedPolicyType}, or {@code null} if no matching type is
+   * @return the corresponding {@link PredefinedPolicyTypes}, or {@code null} if no matching type is
    *     found
    */
   @JsonCreator
-  public static @Nullable PredefinedPolicyType fromCode(int code) {
+  public static @Nullable PredefinedPolicyTypes fromCode(int code) {
     if (code >= REVERSE_CODE_MAPPING_ARRAY.length) {
       return null;
     }
@@ -96,13 +96,13 @@ public boolean isInheritable() {
   }
 
   /**
-   * Retrieves a {@link PredefinedPolicyType} instance corresponding to the given policy name.
+   * Retrieves a {@link PredefinedPolicyTypes} instance corresponding to the given policy name.
    *
    * @param name the name of the predefined policy type
-   * @return the corresponding {@link PredefinedPolicyType}, or {@code null} if no matching type is
+   * @return the corresponding {@link PredefinedPolicyTypes}, or {@code null} if no matching type is
    *     found
    */
-  public static @Nullable PredefinedPolicyType fromName(String name) {
+  public static @Nullable PredefinedPolicyTypes fromName(String name) {
     return REVERSE_NAME_MAPPING_ARRAY.get(name);
   }
 }
diff --git a/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java
index c9ff8f0e1a..2114a1d4f2 100644
--- a/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java
+++ b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java
@@ -30,10 +30,10 @@ public class PolicyEntityTest {
 
   static Stream policyTypes() {
     return Stream.of(
-        Arguments.of(PredefinedPolicyType.DATA_COMPACTION),
-        Arguments.of(PredefinedPolicyType.METADATA_COMPACTION),
-        Arguments.of(PredefinedPolicyType.ORPHAN_FILE_REMOVAL),
-        Arguments.of(PredefinedPolicyType.METADATA_COMPACTION));
+        Arguments.of(PredefinedPolicyTypes.DATA_COMPACTION),
+        Arguments.of(PredefinedPolicyTypes.METADATA_COMPACTION),
+        Arguments.of(PredefinedPolicyTypes.ORPHAN_FILE_REMOVAL),
+        Arguments.of(PredefinedPolicyTypes.METADATA_COMPACTION));
   }
 
   @ParameterizedTest
diff --git a/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java b/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java
index 158010c306..f2cb605e5b 100644
--- a/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java
+++ b/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java
@@ -51,7 +51,7 @@
 import org.apache.polaris.core.persistence.dao.entity.ResolvedEntityResult;
 import org.apache.polaris.core.policy.PolicyEntity;
 import org.apache.polaris.core.policy.PolicyType;
-import org.apache.polaris.core.policy.PredefinedPolicyType;
+import org.apache.polaris.core.policy.PredefinedPolicyTypes;
 import org.assertj.core.api.Assertions;
 
 /** Test the Polaris persistence layer */
@@ -1051,10 +1051,10 @@ PolarisBaseEntity createTestCatalog(String catalogName) {
     PolarisBaseEntity N7 = this.createEntity(List.of(catalog), PolarisEntityType.NAMESPACE, "N7");
     PolarisBaseEntity N7_N8 =
         this.createEntity(List.of(catalog, N7), PolarisEntityType.NAMESPACE, "N8");
-    this.createPolicy(List.of(catalog, N7, N7_N8), "POL1", PredefinedPolicyType.DATA_COMPACTION);
+    this.createPolicy(List.of(catalog, N7, N7_N8), "POL1", PredefinedPolicyTypes.DATA_COMPACTION);
     this.createPolicy(
-        List.of(catalog, N7, N7_N8), "POL2", PredefinedPolicyType.METADATA_COMPACTION);
-    this.createPolicy(List.of(catalog, N7), "POL3", PredefinedPolicyType.SNAPSHOT_RETENTION);
+        List.of(catalog, N7, N7_N8), "POL2", PredefinedPolicyTypes.METADATA_COMPACTION);
+    this.createPolicy(List.of(catalog, N7), "POL3", PredefinedPolicyTypes.SNAPSHOT_RETENTION);
 
     // the two catalog roles
     PolarisBaseEntity R1 =
@@ -1770,7 +1770,7 @@ void testBrowse() {
         List.of(
             ImmutablePair.of("N1", PolarisEntitySubType.NULL_SUBTYPE),
             ImmutablePair.of("N5", PolarisEntitySubType.NULL_SUBTYPE),
-                ImmutablePair.of("N7", PolarisEntitySubType.NULL_SUBTYPE)));
+            ImmutablePair.of("N7", PolarisEntitySubType.NULL_SUBTYPE)));
 
     // should see 3 top-level catalog roles including the admin one
     this.validateListReturn(
@@ -1880,7 +1880,8 @@ void testBrowse() {
     this.validateListReturn(
         List.of(catalog, N7, N7_N8),
         PolarisEntityType.POLICY,
-        List.of(ImmutablePair.of("POL1", PolarisEntitySubType.NULL_SUBTYPE),
+        List.of(
+            ImmutablePair.of("POL1", PolarisEntitySubType.NULL_SUBTYPE),
             ImmutablePair.of("POL2", PolarisEntitySubType.NULL_SUBTYPE)));
   }
 

From f2f64dde9cf545f5727c991f687abfbe29c66819 Mon Sep 17 00:00:00 2001
From: Honah J 
Date: Thu, 13 Mar 2025 15:01:41 -0700
Subject: [PATCH 4/7] fix test

---
 .../main/java/org/apache/polaris/core/policy/PolicyEntity.java | 3 ++-
 .../java/org/apache/polaris/core/persistence/ResolverTest.java | 3 +--
 .../java/org/apache/polaris/core/policy/PolicyEntityTest.java  | 3 +--
 3 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java
index c66b79bef5..18b67d4fb7 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java
@@ -77,11 +77,12 @@ public String getPolicyVersion() {
   }
 
   public static class Builder extends PolarisEntity.BaseBuilder {
-    public Builder(Namespace namespace, String policyName) {
+    public Builder(Namespace namespace, String policyName, PolicyType policyType) {
       super();
       setType(PolarisEntityType.POLICY);
       setParentNamespace(namespace);
       setName(policyName);
+      setPolicyType(policyType);
       setPolicyVersion(0);
     }
 
diff --git a/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java b/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java
index 4c0918a358..dd21a16f3b 100644
--- a/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java
+++ b/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java
@@ -243,8 +243,7 @@ void testResolvePath(boolean useCache) {
     this.resolveDriver(this.cache, "test", N7_N8_POL1, null, null);
 
     // N7/POL3 which exists
-    ResolverPath N7_POL3 =
-        new ResolverPath(List.of("N7", "POL3"), PolarisEntityType.POLICY);
+    ResolverPath N7_POL3 = new ResolverPath(List.of("N7", "POL3"), PolarisEntityType.POLICY);
     this.resolveDriver(this.cache, "test", N7_POL3, null, null);
 
     // Error scenarios: N5/N6/T8 which does not exists
diff --git a/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java
index 2114a1d4f2..0a909e0ebd 100644
--- a/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java
+++ b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java
@@ -40,8 +40,7 @@ static Stream policyTypes() {
   @MethodSource("policyTypes")
   public void testPolicyEntity(PolicyType policyType) {
     PolicyEntity entity =
-        new PolicyEntity.Builder(Namespace.of("NS1"), "testPolicy")
-            .setPolicyType(policyType)
+        new PolicyEntity.Builder(Namespace.of("NS1"), "testPolicy", policyType)
             .setContent("test_content")
             .setPolicyVersion(0)
             .build();

From 45dd562dc776ac5f4e3b3fe4f162b8516d8789cf Mon Sep 17 00:00:00 2001
From: Honah J 
Date: Thu, 13 Mar 2025 15:17:44 -0700
Subject: [PATCH 5/7] fix test

---
 .../PolarisTestMetaStoreManager.java          | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java b/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java
index f2cb605e5b..2fd2865db7 100644
--- a/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java
+++ b/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java
@@ -2051,6 +2051,26 @@ void testDropEntities() {
     this.dropEntity(List.of(catalog, N5), N5_N6);
     this.dropEntity(List.of(catalog), N5);
 
+    PolarisBaseEntity N7 =
+        this.ensureExistsByName(List.of(catalog), PolarisEntityType.NAMESPACE, "N7");
+    PolarisBaseEntity N7_N8 =
+        this.ensureExistsByName(
+            List.of(catalog, N7),
+            PolarisEntityType.NAMESPACE,
+            PolarisEntitySubType.ANY_SUBTYPE,
+            "N8");
+    PolarisBaseEntity POL1 =
+        this.ensureExistsByName(List.of(catalog, N7, N7_N8), PolarisEntityType.POLICY, "POL1");
+    PolarisBaseEntity POL2 =
+        this.ensureExistsByName(List.of(catalog, N7, N7_N8), PolarisEntityType.POLICY, "POL2");
+    PolarisBaseEntity POL3 =
+        this.ensureExistsByName(List.of(catalog, N7), PolarisEntityType.POLICY, "POL3");
+    this.dropEntity(List.of(catalog, N7, N7_N8), POL1);
+    this.dropEntity(List.of(catalog, N7, N7_N8), POL2);
+    this.dropEntity(List.of(catalog, N7), POL3);
+    this.dropEntity(List.of(catalog, N7), N7_N8);
+    this.dropEntity(List.of(catalog), N7);
+
     // attempt to drop the catalog again, should fail because of role R1
     this.dropEntity(null, catalog);
 

From 875caf2f843a18c4ac9d43c8f6f3e38782c660f9 Mon Sep 17 00:00:00 2001
From: Honah J 
Date: Thu, 13 Mar 2025 16:35:32 -0700
Subject: [PATCH 6/7] Throw error instead of -1

---
 .../apache/polaris/core/policy/PolicyEntity.java    | 12 ++++++------
 .../polaris/core/policy/PolicyEntityTest.java       | 13 +++++++++++++
 2 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java
index 18b67d4fb7..7e7125379b 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java
@@ -53,12 +53,11 @@ public PolicyType getPolicyType() {
 
   @JsonIgnore
   public int getPolicyTypeCode() {
+    Preconditions.checkArgument(
+        getPropertiesAsMap().containsKey(POLICY_TYPE_CODE_KEY),
+        "Invalid policy entity: policy type must exist");
     String policyTypeCode = getPropertiesAsMap().get(POLICY_TYPE_CODE_KEY);
-    if (policyTypeCode != null) {
-      return Integer.parseInt(policyTypeCode);
-    }
-
-    return -1;
+    return Integer.parseInt(policyTypeCode);
   }
 
   @JsonIgnore
@@ -93,7 +92,7 @@ public Builder(PolicyEntity original) {
     @Override
     public PolicyEntity build() {
       Preconditions.checkArgument(
-          properties.get(POLICY_TYPE_CODE_KEY) != null, "Policy type must be specified");
+          properties.containsKey(POLICY_TYPE_CODE_KEY), "Policy type must be specified");
 
       return new PolicyEntity(buildBase());
     }
@@ -107,6 +106,7 @@ public Builder setParentNamespace(Namespace namespace) {
     }
 
     public Builder setPolicyType(PolicyType policyType) {
+      Preconditions.checkArgument(policyType != null, "Policy type must be specified");
       properties.put(POLICY_TYPE_CODE_KEY, Integer.toString(policyType.getCode()));
       return this;
     }
diff --git a/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java
index 0a909e0ebd..68be34bb69 100644
--- a/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java
+++ b/polaris-core/src/test/java/org/apache/polaris/core/policy/PolicyEntityTest.java
@@ -22,6 +22,7 @@
 import org.apache.iceberg.catalog.Namespace;
 import org.apache.polaris.core.entity.PolarisEntityType;
 import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
@@ -49,4 +50,16 @@ public void testPolicyEntity(PolicyType policyType) {
     Assertions.assertThat(entity.getPolicyTypeCode()).isEqualTo(policyType.getCode());
     Assertions.assertThat(entity.getContent()).isEqualTo("test_content");
   }
+
+  @Test
+  public void testBuildPolicyEntityWithoutPolicyTye() {
+    Assertions.assertThatThrownBy(
+            () ->
+                new PolicyEntity.Builder(Namespace.of("NS1"), "testPolicy", null)
+                    .setContent("test_content")
+                    .setPolicyVersion(0)
+                    .build())
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Policy type must be specified");
+  }
 }

From 444fd1b9a38b4ab2cf7616a5cda93ee04e0d8203 Mon Sep 17 00:00:00 2001
From: Honah J 
Date: Mon, 17 Mar 2025 17:02:42 -0700
Subject: [PATCH 7/7] change return type to int

---
 .../java/org/apache/polaris/core/policy/PolicyEntity.java | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java
index 7e7125379b..470822bb41 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyEntity.java
@@ -71,8 +71,8 @@ public String getContent() {
   }
 
   @JsonIgnore
-  public String getPolicyVersion() {
-    return getPropertiesAsMap().get(POLICY_VERSION_KEY);
+  public int getPolicyVersion() {
+    return Integer.parseInt(getPropertiesAsMap().get(POLICY_VERSION_KEY));
   }
 
   public static class Builder extends PolarisEntity.BaseBuilder {
@@ -116,8 +116,8 @@ public Builder setDescription(String description) {
       return this;
     }
 
-    public Builder setPolicyVersion(long version) {
-      properties.put(POLICY_VERSION_KEY, Long.toString(version));
+    public Builder setPolicyVersion(int version) {
+      properties.put(POLICY_VERSION_KEY, Integer.toString(version));
       return this;
     }