This is an automated email from the ASF dual-hosted git repository.
yufei pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new 21d1c235d Policy Store: PolicyMappingRecord with Persistence Impl
(#1104)
21d1c235d is described below
commit 21d1c235d9b0c0fcd1ef1332e96a196f7808ca44
Author: Honah (Jonas) J. <[email protected]>
AuthorDate: Tue Apr 1 15:40:12 2025 -0500
Policy Store: PolicyMappingRecord with Persistence Impl (#1104)
---
.../PolarisEclipseLinkMetaStoreSessionImpl.java | 84 +++++++
.../impl/eclipselink/PolarisEclipseLinkStore.java | 116 ++++++++++
.../src/main/resources/META-INF/persistence.xml | 1 +
.../jpa/models/ModelPolicyMappingRecord.java | 153 +++++++++++++
.../AtomicOperationMetaStoreManager.java | 156 +++++++++++++
.../polaris/core/persistence/BasePersistence.java | 3 +-
.../core/persistence/PolarisMetaStoreManager.java | 6 +-
.../PolicyMappingAlreadyExistsException.java | 41 ++++
.../TransactionWorkspaceMetaStoreManager.java | 51 +++++
.../core/persistence/dao/entity/BaseResult.java | 6 +
.../dao/entity/LoadPolicyMappingsResult.java | 102 +++++++++
.../dao/entity/PolicyAttachmentResult.java | 77 +++++++
.../AbstractTransactionalPersistence.java | 116 ++++++++++
.../TransactionalMetaStoreManagerImpl.java | 228 +++++++++++++++++++
.../transactional/TransactionalPersistence.java | 4 +-
.../transactional/TreeMapMetaStore.java | 42 ++++
.../TreeMapTransactionalPersistenceImpl.java | 83 +++++++
.../core/policy/PolarisPolicyMappingManager.java | 103 +++++++++
.../core/policy/PolarisPolicyMappingRecord.java | 215 ++++++++++++++++++
.../core/policy/PolicyMappingPersistence.java | 151 +++++++++++++
.../TransactionalPolicyMappingPersistence.java | 98 ++++++++
.../BasePolarisMetaStoreManagerTest.java | 6 +
.../persistence/PolarisTestMetaStoreManager.java | 250 +++++++++++++++++++++
23 files changed, 2089 insertions(+), 3 deletions(-)
diff --git
a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java
b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java
index 7005d5cfe..a8492384e 100644
---
a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java
+++
b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkMetaStoreSessionImpl.java
@@ -53,6 +53,7 @@ import
org.apache.polaris.core.persistence.BaseMetaStoreManager;
import org.apache.polaris.core.persistence.PrincipalSecretsGenerator;
import org.apache.polaris.core.persistence.RetryOnConcurrencyException;
import
org.apache.polaris.core.persistence.transactional.AbstractTransactionalPersistence;
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
import org.apache.polaris.core.storage.PolarisStorageIntegration;
import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider;
@@ -60,6 +61,7 @@ import org.apache.polaris.jpa.models.ModelEntity;
import org.apache.polaris.jpa.models.ModelEntityActive;
import org.apache.polaris.jpa.models.ModelEntityChangeTracking;
import org.apache.polaris.jpa.models.ModelGrantRecord;
+import org.apache.polaris.jpa.models.ModelPolicyMappingRecord;
import org.apache.polaris.jpa.models.ModelPrincipalSecrets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -664,6 +666,88 @@ public class PolarisEclipseLinkMetaStoreSessionImpl
extends AbstractTransactiona
return
storageIntegrationProvider.getStorageIntegrationForConfig(storageConfig);
}
+ /** {@inheritDoc} */
+ @Override
+ public void writeToPolicyMappingRecordsInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+
+ this.store.writeToPolicyMappingRecords(localSession.get(), record);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void deleteFromPolicyMappingRecordsInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+ this.store.deleteFromPolicyMappingRecords(localSession.get(), record);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void deleteAllEntityPolicyMappingRecordsInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull PolarisEntityCore entity,
+ @Nonnull List<PolarisPolicyMappingRecord> mappingOnTarget,
+ @Nonnull List<PolarisPolicyMappingRecord> mappingOnPolicy) {
+ this.store.deleteAllEntityPolicyMappingRecords(localSession.get(), entity);
+ }
+
+ /** {@inheritDoc} */
+ @Nullable
+ @Override
+ public PolarisPolicyMappingRecord lookupPolicyMappingRecordInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx,
+ long targetCatalogId,
+ long targetId,
+ int policyTypeCode,
+ long policyCatalogId,
+ long policyId) {
+ return ModelPolicyMappingRecord.toPolicyMappingRecord(
+ this.store.lookupPolicyMappingRecord(
+ localSession.get(),
+ targetCatalogId,
+ targetId,
+ policyTypeCode,
+ policyCatalogId,
+ policyId));
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull
+ @Override
+ public List<PolarisPolicyMappingRecord>
loadPoliciesOnTargetByTypeInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx,
+ long targetCatalogId,
+ long targetId,
+ int policyTypeCode) {
+ return this.store
+ .loadPoliciesOnTargetByType(localSession.get(), targetCatalogId,
targetId, policyTypeCode)
+ .stream()
+ .map(ModelPolicyMappingRecord::toPolicyMappingRecord)
+ .toList();
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull
+ @Override
+ public List<PolarisPolicyMappingRecord> loadAllPoliciesOnTargetInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, long targetCatalogId, long
targetId) {
+ return this.store
+ .loadAllPoliciesOnTarget(localSession.get(), targetCatalogId, targetId)
+ .stream()
+ .map(ModelPolicyMappingRecord::toPolicyMappingRecord)
+ .toList();
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull
+ @Override
+ public List<PolarisPolicyMappingRecord> loadAllTargetsOnPolicyInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, long policyCatalogId, long
policyId) {
+ return this.store.loadAllTargetsOnPolicy(localSession.get(),
policyCatalogId, policyId).stream()
+ .map(ModelPolicyMappingRecord::toPolicyMappingRecord)
+ .toList();
+ }
+
@Override
public void rollback() {
EntityManager session = localSession.get();
diff --git
a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkStore.java
b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkStore.java
index bfc83ae37..fc889656b 100644
---
a/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkStore.java
+++
b/extension/persistence/eclipselink/src/main/java/org/apache/polaris/extension/persistence/impl/eclipselink/PolarisEclipseLinkStore.java
@@ -35,10 +35,12 @@ import org.apache.polaris.core.entity.PolarisEntityId;
import org.apache.polaris.core.entity.PolarisEntityType;
import org.apache.polaris.core.entity.PolarisGrantRecord;
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
import org.apache.polaris.jpa.models.ModelEntity;
import org.apache.polaris.jpa.models.ModelEntityActive;
import org.apache.polaris.jpa.models.ModelEntityChangeTracking;
import org.apache.polaris.jpa.models.ModelGrantRecord;
+import org.apache.polaris.jpa.models.ModelPolicyMappingRecord;
import org.apache.polaris.jpa.models.ModelPrincipalSecrets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -411,6 +413,120 @@ public class PolarisEclipseLinkStore {
session.remove(modelPrincipalSecrets);
}
+ void writeToPolicyMappingRecords(
+ EntityManager session, PolarisPolicyMappingRecord mappingRecord) {
+ diagnosticServices.check(session != null, "session_is_null");
+ checkInitialized();
+
+
session.persist(ModelPolicyMappingRecord.fromPolicyMappingRecord(mappingRecord));
+ }
+
+ void deleteFromPolicyMappingRecords(
+ EntityManager session, PolarisPolicyMappingRecord mappingRecord) {
+ diagnosticServices.check(session != null, "session_is_null");
+ checkInitialized();
+
+ ModelPolicyMappingRecord lookupPolicyMappingRecord =
+ lookupPolicyMappingRecord(
+ session,
+ mappingRecord.getTargetCatalogId(),
+ mappingRecord.getTargetId(),
+ mappingRecord.getPolicyTypeCode(),
+ mappingRecord.getPolicyCatalogId(),
+ mappingRecord.getPolicyId());
+
+ diagnosticServices.check(lookupPolicyMappingRecord != null,
"policy_mapping_record_not_found");
+ session.remove(lookupPolicyMappingRecord);
+ }
+
+ void deleteAllEntityPolicyMappingRecords(EntityManager session,
PolarisEntityCore entity) {
+ diagnosticServices.check(session != null, "session_is_null");
+ checkInitialized();
+
+ loadAllTargetsOnPolicy(session, entity.getCatalogId(),
entity.getId()).forEach(session::remove);
+ loadAllPoliciesOnTarget(session, entity.getCatalogId(), entity.getId())
+ .forEach(session::remove);
+ }
+
+ ModelPolicyMappingRecord lookupPolicyMappingRecord(
+ EntityManager session,
+ long targetCatalogId,
+ long targetId,
+ long policyTypeCode,
+ long policyCatalogId,
+ long policyId) {
+ diagnosticServices.check(session != null, "session_is_null");
+ checkInitialized();
+
+ return session
+ .createQuery(
+ "SELECT m from ModelPolicyMappingRecord m "
+ + "where m.targetCatalogId=:targetCatalogId "
+ + "and m.targetId=:targetId "
+ + "and m.policyTypeCode=:policyTypeCode "
+ + "and m.policyCatalogId=:policyCatalogId "
+ + "and m.policyId=:policyId",
+ ModelPolicyMappingRecord.class)
+ .setParameter("targetCatalogId", targetCatalogId)
+ .setParameter("targetId", targetId)
+ .setParameter("policyTypeCode", policyTypeCode)
+ .setParameter("policyCatalogId", policyCatalogId)
+ .setParameter("policyId", policyId)
+ .getResultStream()
+ .findFirst()
+ .orElse(null);
+ }
+
+ List<ModelPolicyMappingRecord> loadPoliciesOnTargetByType(
+ EntityManager session, long targetCatalogId, long targetId, int
policyTypeCode) {
+ diagnosticServices.check(session != null, "session_is_null");
+ checkInitialized();
+
+ return session
+ .createQuery(
+ "SELECT m from ModelPolicyMappingRecord m "
+ + "where m.targetCatalogId=:targetCatalogId "
+ + "and m.targetId=:targetId "
+ + "and m.policyTypeCode=:policyTypeCode",
+ ModelPolicyMappingRecord.class)
+ .setParameter("targetCatalogId", targetCatalogId)
+ .setParameter("targetId", targetId)
+ .setParameter("policyTypeCode", policyTypeCode)
+ .getResultList();
+ }
+
+ List<ModelPolicyMappingRecord> loadAllPoliciesOnTarget(
+ EntityManager session, long targetCatalogId, long targetId) {
+ diagnosticServices.check(session != null, "session_is_null");
+ checkInitialized();
+
+ return session
+ .createQuery(
+ "SELECT m from ModelPolicyMappingRecord m "
+ + " where m.targetCatalogId=:targetCatalogId "
+ + "and m.targetId=:targetId",
+ ModelPolicyMappingRecord.class)
+ .setParameter("targetCatalogId", targetCatalogId)
+ .setParameter("targetId", targetId)
+ .getResultList();
+ }
+
+ List<ModelPolicyMappingRecord> loadAllTargetsOnPolicy(
+ EntityManager session, long policyCatalogId, long policyId) {
+ diagnosticServices.check(session != null, "session_is_null");
+ checkInitialized();
+
+ return session
+ .createQuery(
+ "SELECT m from ModelPolicyMappingRecord m "
+ + "where m.policyCatalogId=:policyCatalogId "
+ + "and m.policyId=:policyId",
+ ModelPolicyMappingRecord.class)
+ .setParameter("policyCatalogId", policyCatalogId)
+ .setParameter("policyId", policyId)
+ .getResultList();
+ }
+
private void checkInitialized() {
diagnosticServices.check(this.initialized.get(), "store_not_initialized");
}
diff --git
a/extension/persistence/eclipselink/src/main/resources/META-INF/persistence.xml
b/extension/persistence/eclipselink/src/main/resources/META-INF/persistence.xml
index d435b3d33..cd8610599 100644
---
a/extension/persistence/eclipselink/src/main/resources/META-INF/persistence.xml
+++
b/extension/persistence/eclipselink/src/main/resources/META-INF/persistence.xml
@@ -29,6 +29,7 @@
<class>org.apache.polaris.jpa.models.ModelEntityActive</class>
<class>org.apache.polaris.jpa.models.ModelEntityChangeTracking</class>
<class>org.apache.polaris.jpa.models.ModelGrantRecord</class>
+ <class>org.apache.polaris.jpa.models.ModelPolicyMappingRecord</class>
<class>org.apache.polaris.jpa.models.ModelPrincipalSecrets</class>
<class>org.apache.polaris.jpa.models.ModelSequenceId</class>
<shared-cache-mode>NONE</shared-cache-mode>
diff --git
a/extension/persistence/jpa-model/src/main/java/org/apache/polaris/jpa/models/ModelPolicyMappingRecord.java
b/extension/persistence/jpa-model/src/main/java/org/apache/polaris/jpa/models/ModelPolicyMappingRecord.java
new file mode 100644
index 000000000..122eeadb8
--- /dev/null
+++
b/extension/persistence/jpa-model/src/main/java/org/apache/polaris/jpa/models/ModelPolicyMappingRecord.java
@@ -0,0 +1,153 @@
+/*
+ * 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.jpa.models;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Index;
+import jakarta.persistence.Table;
+import jakarta.persistence.Version;
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
+
+@Entity
+@Table(
+ name = "POLICY_MAPPING_RECORDS",
+ indexes = {
+ @Index(
+ name = "POLICY_MAPPING_RECORDS_BY_POLICY_INDEX",
+ columnList = "policyCatalogId,policyId,targetCatalogId,targetId")
+ })
+public class ModelPolicyMappingRecord {
+ // id of the catalog where target entity resides
+ @Id private long targetCatalogId;
+
+ // id of the target entity
+ @Id private long targetId;
+
+ // id associated to the policy type
+ @Id private int policyTypeCode;
+
+ // id of the catalog where the policy entity resides
+ @Id private long policyCatalogId;
+
+ // id of the policy
+ @Id private long policyId;
+
+ // additional parameters of the mapping
+ private String parameters;
+
+ // Used for Optimistic Locking to handle concurrent reads and updates
+ @Version private long version;
+
+ public long getTargetCatalogId() {
+ return targetCatalogId;
+ }
+
+ public long getTargetId() {
+ return targetId;
+ }
+
+ public int getPolicyTypeCode() {
+ return policyTypeCode;
+ }
+
+ public long getPolicyCatalogId() {
+ return policyCatalogId;
+ }
+
+ public long getPolicyId() {
+ return policyId;
+ }
+
+ public String getParameters() {
+ return parameters;
+ }
+
+ public static ModelPolicyMappingRecord.Builder builder() {
+ return new ModelPolicyMappingRecord.Builder();
+ }
+
+ public static final class Builder {
+ private final ModelPolicyMappingRecord policyMappingRecord;
+
+ private Builder() {
+ policyMappingRecord = new ModelPolicyMappingRecord();
+ }
+
+ public Builder targetCatalogId(long targetCatalogId) {
+ policyMappingRecord.targetCatalogId = targetCatalogId;
+ return this;
+ }
+
+ public Builder targetId(long targetId) {
+ policyMappingRecord.targetId = targetId;
+ return this;
+ }
+
+ public Builder policyTypeCode(int policyTypeCode) {
+ policyMappingRecord.policyTypeCode = policyTypeCode;
+ return this;
+ }
+
+ public Builder policyCatalogId(long policyCatalogId) {
+ policyMappingRecord.policyCatalogId = policyCatalogId;
+ return this;
+ }
+
+ public Builder policyId(long policyId) {
+ policyMappingRecord.policyId = policyId;
+ return this;
+ }
+
+ public Builder parameters(String parameters) {
+ policyMappingRecord.parameters = parameters;
+ return this;
+ }
+
+ public ModelPolicyMappingRecord build() {
+ return policyMappingRecord;
+ }
+ }
+
+ public static ModelPolicyMappingRecord fromPolicyMappingRecord(
+ PolarisPolicyMappingRecord record) {
+ if (record == null) return null;
+
+ return ModelPolicyMappingRecord.builder()
+ .targetCatalogId(record.getTargetCatalogId())
+ .targetId(record.getTargetId())
+ .policyTypeCode(record.getPolicyTypeCode())
+ .policyCatalogId(record.getPolicyCatalogId())
+ .policyId(record.getPolicyId())
+ .parameters(record.getParameters())
+ .build();
+ }
+
+ public static PolarisPolicyMappingRecord
toPolicyMappingRecord(ModelPolicyMappingRecord model) {
+ if (model == null) return null;
+
+ return new PolarisPolicyMappingRecord(
+ model.getTargetCatalogId(),
+ model.getTargetId(),
+ model.getPolicyCatalogId(),
+ model.getPolicyId(),
+ model.getPolicyTypeCode(),
+ model.getParameters());
+ }
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/AtomicOperationMetaStoreManager.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/AtomicOperationMetaStoreManager.java
index 7dcc9f0ac..979607376 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/AtomicOperationMetaStoreManager.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/AtomicOperationMetaStoreManager.java
@@ -56,11 +56,16 @@ import
org.apache.polaris.core.persistence.dao.entity.EntityResult;
import org.apache.polaris.core.persistence.dao.entity.EntityWithPath;
import org.apache.polaris.core.persistence.dao.entity.ListEntitiesResult;
import org.apache.polaris.core.persistence.dao.entity.LoadGrantsResult;
+import org.apache.polaris.core.persistence.dao.entity.LoadPolicyMappingsResult;
+import org.apache.polaris.core.persistence.dao.entity.PolicyAttachmentResult;
import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult;
import org.apache.polaris.core.persistence.dao.entity.PrivilegeResult;
import org.apache.polaris.core.persistence.dao.entity.ResolvedEntityResult;
import org.apache.polaris.core.persistence.dao.entity.ScopedCredentialsResult;
import org.apache.polaris.core.persistence.dao.entity.ValidateAccessResult;
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
+import org.apache.polaris.core.policy.PolicyEntity;
+import org.apache.polaris.core.policy.PolicyType;
import org.apache.polaris.core.storage.PolarisCredentialProperty;
import org.apache.polaris.core.storage.PolarisStorageActions;
import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
@@ -1821,4 +1826,155 @@ public class AtomicOperationMetaStoreManager extends
BaseMetaStoreManager {
// return the result
return new ResolvedEntityResult(entity,
entityVersions.getGrantRecordsVersion(), grantRecords);
}
+
+ @Override
+ public @Nonnull PolicyAttachmentResult attachPolicyToEntity(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull List<PolarisEntityCore> targetCatalogPath,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull List<PolarisEntityCore> policyCatalogPath,
+ @Nonnull PolicyEntity policy,
+ Map<String, String> parameters) {
+ // get metastore we should be using
+ BasePersistence ms = callCtx.getMetaStore();
+
+ return this.persistNewPolicyMappingRecord(callCtx, ms, target, policy,
parameters);
+ }
+
+ @Override
+ public @Nonnull PolicyAttachmentResult detachPolicyFromEntity(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull List<PolarisEntityCore> catalogPath,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull List<PolarisEntityCore> policyCatalogPath,
+ @Nonnull PolicyEntity policy) {
+ // get metastore we should be using
+ BasePersistence ms = callCtx.getMetaStore();
+
+ PolarisPolicyMappingRecord mappingRecord =
+ ms.lookupPolicyMappingRecord(
+ callCtx,
+ target.getCatalogId(),
+ target.getId(),
+ policy.getPolicyTypeCode(),
+ policy.getCatalogId(),
+ policy.getId());
+ if (mappingRecord == null) {
+ return new
PolicyAttachmentResult(BaseResult.ReturnStatus.POLICY_MAPPING_NOT_FOUND, null);
+ }
+
+ ms.deleteFromPolicyMappingRecords(callCtx, mappingRecord);
+
+ return new PolicyAttachmentResult(mappingRecord);
+ }
+
+ @Override
+ public @Nonnull LoadPolicyMappingsResult loadPoliciesOnEntity(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisEntityCore target) {
+ // get metastore we should be using
+ BasePersistence ms = callCtx.getMetaStore();
+
+ PolarisBaseEntity entity =
+ ms.lookupEntity(callCtx, target.getCatalogId(), target.getId(),
target.getTypeCode());
+ if (entity == null) {
+ // Target entity does not exist
+ return new
LoadPolicyMappingsResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null);
+ }
+
+ final List<PolarisPolicyMappingRecord> policyMappingRecords =
+ ms.loadAllPoliciesOnTarget(callCtx, target.getCatalogId(),
target.getId());
+
+ List<PolarisBaseEntity> policyEntities =
+ loadPoliciesFromMappingRecords(callCtx, ms, policyMappingRecords);
+ return new LoadPolicyMappingsResult(policyMappingRecords, policyEntities);
+ }
+
+ @Override
+ public @Nonnull LoadPolicyMappingsResult loadPoliciesOnEntityByType(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull PolicyType policyType) {
+ // get metastore we should be using
+ BasePersistence ms = callCtx.getMetaStore();
+
+ PolarisBaseEntity entity =
+ ms.lookupEntity(callCtx, target.getCatalogId(), target.getId(),
target.getTypeCode());
+ if (entity == null) {
+ // Target entity does not exist
+ return new
LoadPolicyMappingsResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null);
+ }
+
+ final List<PolarisPolicyMappingRecord> policyMappingRecords =
+ ms.loadPoliciesOnTargetByType(
+ callCtx, target.getCatalogId(), target.getId(),
policyType.getCode());
+
+ List<PolarisBaseEntity> policyEntities =
+ loadPoliciesFromMappingRecords(callCtx, ms, policyMappingRecords);
+ return new LoadPolicyMappingsResult(policyMappingRecords, policyEntities);
+ }
+
+ /**
+ * Create and persist a new policy mapping record
+ *
+ * @param callCtx call context
+ * @param ms meta store in read/write mode
+ * @param target target
+ * @param policy policy
+ * @param parameters optional parameters
+ * @return new policy mapping record which was created and persisted
+ */
+ private @Nonnull PolicyAttachmentResult persistNewPolicyMappingRecord(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull BasePersistence ms,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull PolicyEntity policy,
+ Map<String, String> parameters) {
+ callCtx.getDiagServices().checkNotNull(target, "unexpected_null_target");
+ callCtx.getDiagServices().checkNotNull(policy, "unexpected_null_policy");
+
+ PolarisPolicyMappingRecord mappingRecord =
+ new PolarisPolicyMappingRecord(
+ target.getCatalogId(),
+ target.getId(),
+ policy.getCatalogId(),
+ policy.getId(),
+ policy.getPolicyTypeCode(),
+ parameters);
+ try {
+ ms.writeToPolicyMappingRecords(callCtx, mappingRecord);
+ } catch (IllegalArgumentException e) {
+ return new PolicyAttachmentResult(
+ BaseResult.ReturnStatus.UNEXPECTED_ERROR_SIGNALED, "Unknown policy
type");
+ } catch (PolicyMappingAlreadyExistsException e) {
+ return new PolicyAttachmentResult(
+ BaseResult.ReturnStatus.POLICY_MAPPING_OF_SAME_TYPE_ALREADY_EXISTS,
+ e.getExistingRecord().getPolicyTypeCode());
+ }
+
+ return new PolicyAttachmentResult(mappingRecord);
+ }
+
+ /**
+ * Load policies from a list of policy mapping records
+ *
+ * @param callCtx call context
+ * @param ms meta store
+ * @param policyMappingRecords a list of policy mapping records
+ * @return a list of policy entities
+ */
+ private List<PolarisBaseEntity> loadPoliciesFromMappingRecords(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull BasePersistence ms,
+ @Nonnull List<PolarisPolicyMappingRecord> policyMappingRecords) {
+ List<PolarisEntityId> policyEntityIds =
+ policyMappingRecords.stream()
+ .map(
+ policyMappingRecord ->
+ new PolarisEntityId(
+ policyMappingRecord.getPolicyCatalogId(),
+ policyMappingRecord.getPolicyId()))
+ .distinct()
+ .collect(Collectors.toList());
+ return ms.lookupEntities(callCtx, policyEntityIds);
+ }
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/BasePersistence.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/BasePersistence.java
index 88e5143cf..45460eb46 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/BasePersistence.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/BasePersistence.java
@@ -31,6 +31,7 @@ import org.apache.polaris.core.entity.PolarisEntityCore;
import org.apache.polaris.core.entity.PolarisEntityId;
import org.apache.polaris.core.entity.PolarisEntityType;
import org.apache.polaris.core.entity.PolarisGrantRecord;
+import org.apache.polaris.core.policy.PolicyMappingPersistence;
/**
* Interface to the Polaris persistence backend, with which to persist and
retrieve all the data
@@ -41,7 +42,7 @@ import org.apache.polaris.core.entity.PolarisGrantRecord;
* the underlying data store. The goal is to make it really easy to back this
using databases like
* Postgres or simpler KV store.
*/
-public interface BasePersistence {
+public interface BasePersistence extends PolicyMappingPersistence {
/**
* The returned id must be fully unique within a realm and never reused once
generated, whether or
* not anything ends up committing an entity with the generated id.
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManager.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManager.java
index cc082bc27..da2ab521e 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManager.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManager.java
@@ -42,6 +42,7 @@ import
org.apache.polaris.core.persistence.dao.entity.EntityWithPath;
import org.apache.polaris.core.persistence.dao.entity.GenerateEntityIdResult;
import org.apache.polaris.core.persistence.dao.entity.ListEntitiesResult;
import org.apache.polaris.core.persistence.dao.entity.ResolvedEntityResult;
+import org.apache.polaris.core.policy.PolarisPolicyMappingManager;
import org.apache.polaris.core.storage.PolarisCredentialVendor;
/**
@@ -49,7 +50,10 @@ import
org.apache.polaris.core.storage.PolarisCredentialVendor;
* authorization. It uses the underlying persistent metastore to store and
retrieve Polaris metadata
*/
public interface PolarisMetaStoreManager
- extends PolarisSecretsManager, PolarisGrantManager,
PolarisCredentialVendor {
+ extends PolarisSecretsManager,
+ PolarisGrantManager,
+ PolarisCredentialVendor,
+ PolarisPolicyMappingManager {
/**
* Bootstrap the Polaris service, creating the root catalog, root principal,
and associated
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolicyMappingAlreadyExistsException.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolicyMappingAlreadyExistsException.java
new file mode 100644
index 000000000..2cd714f25
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolicyMappingAlreadyExistsException.java
@@ -0,0 +1,41 @@
+/*
+ * 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.persistence;
+
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
+
+/**
+ * Exception raised when an existing policy mapping preveents the attempted
creation of a new policy
+ * mapping record.
+ */
+public class PolicyMappingAlreadyExistsException extends RuntimeException {
+ private PolarisPolicyMappingRecord existingRecord;
+
+ /**
+ * @param existingRecord The conflicting record that caused creation to fail.
+ */
+ public PolicyMappingAlreadyExistsException(PolarisPolicyMappingRecord
existingRecord) {
+ super("Existing Policy Mapping Record: " + existingRecord);
+ this.existingRecord = existingRecord;
+ }
+
+ public PolarisPolicyMappingRecord getExistingRecord() {
+ return this.existingRecord;
+ }
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/TransactionWorkspaceMetaStoreManager.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/TransactionWorkspaceMetaStoreManager.java
index 4f26c51fa..4c3778e98 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/TransactionWorkspaceMetaStoreManager.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/TransactionWorkspaceMetaStoreManager.java
@@ -44,11 +44,15 @@ import
org.apache.polaris.core.persistence.dao.entity.EntityWithPath;
import org.apache.polaris.core.persistence.dao.entity.GenerateEntityIdResult;
import org.apache.polaris.core.persistence.dao.entity.ListEntitiesResult;
import org.apache.polaris.core.persistence.dao.entity.LoadGrantsResult;
+import org.apache.polaris.core.persistence.dao.entity.LoadPolicyMappingsResult;
+import org.apache.polaris.core.persistence.dao.entity.PolicyAttachmentResult;
import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult;
import org.apache.polaris.core.persistence.dao.entity.PrivilegeResult;
import org.apache.polaris.core.persistence.dao.entity.ResolvedEntityResult;
import org.apache.polaris.core.persistence.dao.entity.ScopedCredentialsResult;
import org.apache.polaris.core.persistence.dao.entity.ValidateAccessResult;
+import org.apache.polaris.core.policy.PolicyEntity;
+import org.apache.polaris.core.policy.PolicyType;
import org.apache.polaris.core.storage.PolarisStorageActions;
/**
@@ -394,4 +398,51 @@ public class TransactionWorkspaceMetaStoreManager
implements PolarisMetaStoreMan
.fail("illegal_method_in_transaction_workspace",
"refreshResolvedEntity");
return null;
}
+
+ @Override
+ public @Nonnull PolicyAttachmentResult attachPolicyToEntity(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull List<PolarisEntityCore> targetCatalogPath,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull List<PolarisEntityCore> policyCatalogPath,
+ @Nonnull PolicyEntity policy,
+ Map<String, String> parameters) {
+ callCtx
+ .getDiagServices()
+ .fail("illegal_method_in_transaction_workspace",
"attachPolicyToEntity");
+ return null;
+ }
+
+ @Override
+ public @Nonnull PolicyAttachmentResult detachPolicyFromEntity(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull List<PolarisEntityCore> catalogPath,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull List<PolarisEntityCore> policyCatalogPath,
+ @Nonnull PolicyEntity policy) {
+ callCtx
+ .getDiagServices()
+ .fail("illegal_method_in_transaction_workspace",
"detachPolicyFromEntity");
+ return null;
+ }
+
+ @Override
+ public @Nonnull LoadPolicyMappingsResult loadPoliciesOnEntity(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisEntityCore target) {
+ callCtx
+ .getDiagServices()
+ .fail("illegal_method_in_transaction_workspace",
"loadPoliciesOnEntity");
+ return null;
+ }
+
+ @Override
+ public @Nonnull LoadPolicyMappingsResult loadPoliciesOnEntityByType(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull PolicyType policyType) {
+ callCtx
+ .getDiagServices()
+ .fail("illegal_method_in_transaction_workspace",
"loadPoliciesOnEntityByType");
+ return null;
+ }
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/BaseResult.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/BaseResult.java
index b4e1757de..a4eee22cc 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/BaseResult.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/BaseResult.java
@@ -111,6 +111,12 @@ public class BaseResult {
// error caught while sub-scoping credentials. Error message will be
returned
SUBSCOPE_CREDS_ERROR(13),
+
+ // policy mapping not found
+ POLICY_MAPPING_NOT_FOUND(14),
+
+ // policy mapping of same type already exists
+ POLICY_MAPPING_OF_SAME_TYPE_ALREADY_EXISTS(15),
;
// code for the enum
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/LoadPolicyMappingsResult.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/LoadPolicyMappingsResult.java
new file mode 100644
index 000000000..48a0277dd
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/LoadPolicyMappingsResult.java
@@ -0,0 +1,102 @@
+/*
+ * 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.persistence.dao.entity;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.polaris.core.entity.PolarisBaseEntity;
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
+
+/** result of a load policy mapping call */
+public class LoadPolicyMappingsResult extends BaseResult {
+ // null if not success. Else set of policy mapping records on a target or
from a policy
+ private final List<PolarisPolicyMappingRecord> mappingRecords;
+
+ // null if not success. Else, for each policy mapping record, list of target
or policy entities
+ private final List<PolarisBaseEntity> entities;
+
+ /**
+ * Constructor for an error
+ *
+ * @param errorCode error code, cannot be SUCCESS
+ * @param extraInformation extra information
+ */
+ public LoadPolicyMappingsResult(
+ @Nonnull ReturnStatus errorCode, @Nullable String extraInformation) {
+ super(errorCode, extraInformation);
+ this.mappingRecords = null;
+ this.entities = null;
+ }
+
+ /**
+ * Constructor for success
+ *
+ * @param mappingRecords policy mapping records
+ * @param entities policy entities
+ */
+ public LoadPolicyMappingsResult(
+ @Nonnull List<PolarisPolicyMappingRecord> mappingRecords,
+ @Nonnull List<PolarisBaseEntity> entities) {
+ super(ReturnStatus.SUCCESS);
+ this.mappingRecords = mappingRecords;
+ this.entities = entities;
+ }
+
+ @JsonCreator
+ private LoadPolicyMappingsResult(
+ @JsonProperty("returnStatus") @Nonnull ReturnStatus returnStatus,
+ @JsonProperty("extraInformation") String extraInformation,
+ @JsonProperty("policyMappingRecords") List<PolarisPolicyMappingRecord>
mappingRecords,
+ @JsonProperty("policyEntities") List<PolarisBaseEntity> entities) {
+ super(returnStatus, extraInformation);
+ this.mappingRecords = mappingRecords;
+ this.entities = entities;
+ }
+
+ public List<PolarisPolicyMappingRecord> getPolicyMappingRecords() {
+ return mappingRecords;
+ }
+
+ public List<PolarisBaseEntity> getEntities() {
+ return entities;
+ }
+
+ @JsonIgnore
+ public Map<Long, PolarisBaseEntity> getEntitiesAsMap() {
+ return entities == null
+ ? null
+ : entities.stream().collect(Collectors.toMap(PolarisBaseEntity::getId,
entity -> entity));
+ }
+
+ @Override
+ public String toString() {
+ return "LoadPolicyMappingsResult{"
+ + "mappingRecords="
+ + mappingRecords
+ + ", entities="
+ + entities
+ + '}';
+ }
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/PolicyAttachmentResult.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/PolicyAttachmentResult.java
new file mode 100644
index 000000000..ffc7eabdd
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/PolicyAttachmentResult.java
@@ -0,0 +1,77 @@
+/*
+ * 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.persistence.dao.entity;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
+
+/** result of an attach/detach operation */
+public class PolicyAttachmentResult extends BaseResult {
+ // null if not success
+ private final PolarisPolicyMappingRecord mappingRecord;
+
+ /**
+ * Constructor for an error
+ *
+ * @param errorStatus error code, cannot be SUCCESS
+ * @param extraInformation extra information
+ */
+ public PolicyAttachmentResult(
+ @Nonnull ReturnStatus errorStatus, @Nullable String extraInformation) {
+ super(errorStatus, extraInformation);
+ this.mappingRecord = null;
+ }
+
+ /**
+ * Constructor for an error
+ *
+ * @param errorStatus error code, cannot be SUCCESS
+ * @param policyTypeCode existing policy mapping record's policy type code
+ */
+ public PolicyAttachmentResult(@Nonnull ReturnStatus errorStatus, int
policyTypeCode) {
+ super(errorStatus, Integer.toString(policyTypeCode));
+ this.mappingRecord = null;
+ }
+
+ /**
+ * Constructor for success
+ *
+ * @param mappingRecord policy mapping record being attached/detached
+ */
+ public PolicyAttachmentResult(@Nonnull PolarisPolicyMappingRecord
mappingRecord) {
+ super(ReturnStatus.SUCCESS);
+ this.mappingRecord = mappingRecord;
+ }
+
+ @JsonCreator
+ private PolicyAttachmentResult(
+ @JsonProperty("returnStatus") @Nonnull ReturnStatus returnStatus,
+ @JsonProperty("extraInformation") String extraInformation,
+ @JsonProperty("policyMappingRecord") PolarisPolicyMappingRecord
mappingRecord) {
+ super(returnStatus, extraInformation);
+ this.mappingRecord = mappingRecord;
+ }
+
+ public PolarisPolicyMappingRecord getPolicyMappingRecord() {
+ return mappingRecord;
+ }
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/AbstractTransactionalPersistence.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/AbstractTransactionalPersistence.java
index c673c4849..e949b33fe 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/AbstractTransactionalPersistence.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/AbstractTransactionalPersistence.java
@@ -18,6 +18,7 @@
*/
package org.apache.polaris.core.persistence.transactional;
+import com.google.common.base.Preconditions;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import java.util.List;
@@ -34,7 +35,10 @@ import org.apache.polaris.core.entity.PolarisEntityType;
import org.apache.polaris.core.entity.PolarisGrantRecord;
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
import org.apache.polaris.core.persistence.EntityAlreadyExistsException;
+import org.apache.polaris.core.persistence.PolicyMappingAlreadyExistsException;
import org.apache.polaris.core.persistence.RetryOnConcurrencyException;
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
+import org.apache.polaris.core.policy.PolicyType;
import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
import org.apache.polaris.core.storage.PolarisStorageIntegration;
@@ -660,4 +664,116 @@ public abstract class AbstractTransactionalPersistence
implements TransactionalP
new PolarisEntitiesActiveKey(catalogId, parentId, typeCode, name);
return this.lookupEntityActiveInCurrentTxn(callCtx, entityActiveKey);
}
+
+ /** {@inheritDoc} */
+ @Override
+ public void writeToPolicyMappingRecords(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+ this.runActionInTransaction(
+ callCtx,
+ () -> {
+
this.checkConditionsForWriteToPolicyMappingRecordsInCurrentTxn(callCtx, record);
+ this.writeToPolicyMappingRecordsInCurrentTxn(callCtx, record);
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void checkConditionsForWriteToPolicyMappingRecordsInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+
+ PolicyType policyType = PolicyType.fromCode(record.getPolicyTypeCode());
+ Preconditions.checkArgument(
+ policyType != null, "Invalid policy type code: %s",
record.getPolicyTypeCode());
+
+ if (!policyType.isInheritable()) {
+ return;
+ }
+
+ List<PolarisPolicyMappingRecord> existingRecords =
+ this.loadPoliciesOnTargetByTypeInCurrentTxn(
+ callCtx, record.getTargetCatalogId(), record.getTargetId(),
record.getPolicyTypeCode());
+ if (existingRecords.size() > 1) {
+ throw new PolicyMappingAlreadyExistsException(existingRecords.get(0));
+ } else if (existingRecords.size() == 1) {
+ PolarisPolicyMappingRecord existingRecord = existingRecords.get(0);
+ if (existingRecord.getPolicyCatalogId() != record.getPolicyCatalogId()
+ || existingRecord.getPolicyId() != record.getPolicyId()) {
+ throw new PolicyMappingAlreadyExistsException(existingRecord);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void deleteFromPolicyMappingRecords(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+ this.runActionInTransaction(
+ callCtx, () ->
this.deleteFromPolicyMappingRecordsInCurrentTxn(callCtx, record));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void deleteAllEntityPolicyMappingRecords(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull PolarisEntityCore entity,
+ @Nonnull List<PolarisPolicyMappingRecord> mappingOnTarget,
+ @Nonnull List<PolarisPolicyMappingRecord> mappingOnPolicy) {
+ this.runActionInTransaction(
+ callCtx,
+ () ->
+ this.deleteAllEntityPolicyMappingRecordsInCurrentTxn(
+ callCtx, entity, mappingOnTarget, mappingOnPolicy));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public PolarisPolicyMappingRecord lookupPolicyMappingRecord(
+ @Nonnull PolarisCallContext callCtx,
+ long targetCatalogId,
+ long targetId,
+ int policyTypeCode,
+ long policyCatalogId,
+ long policyId) {
+ return this.runInReadTransaction(
+ callCtx,
+ () ->
+ this.lookupPolicyMappingRecordInCurrentTxn(
+ callCtx, targetCatalogId, targetId, policyTypeCode,
policyCatalogId, policyId));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public List<PolarisPolicyMappingRecord> loadPoliciesOnTargetByType(
+ @Nonnull PolarisCallContext callCtx,
+ long targetCatalogId,
+ long targetId,
+ int policyTypeCode) {
+ return this.runInReadTransaction(
+ callCtx,
+ () ->
+ this.loadPoliciesOnTargetByTypeInCurrentTxn(
+ callCtx, targetCatalogId, targetId, policyTypeCode));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public List<PolarisPolicyMappingRecord> loadAllPoliciesOnTarget(
+ @Nonnull PolarisCallContext callCtx, long targetCatalogId, long
targetId) {
+ return this.runInReadTransaction(
+ callCtx,
+ () -> this.loadAllPoliciesOnTargetInCurrentTxn(callCtx,
targetCatalogId, targetId));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public List<PolarisPolicyMappingRecord> loadAllTargetsOnPolicy(
+ @Nonnull PolarisCallContext callCtx, long policyCatalogId, long
policyId) {
+ return this.runInReadTransaction(
+ callCtx, () -> this.loadAllTargetsOnPolicyInCurrentTxn(callCtx,
policyCatalogId, policyId));
+ }
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalMetaStoreManagerImpl.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalMetaStoreManagerImpl.java
index cf1e54b53..27b049e91 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalMetaStoreManagerImpl.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalMetaStoreManagerImpl.java
@@ -57,11 +57,16 @@ import
org.apache.polaris.core.persistence.dao.entity.EntityResult;
import org.apache.polaris.core.persistence.dao.entity.EntityWithPath;
import org.apache.polaris.core.persistence.dao.entity.ListEntitiesResult;
import org.apache.polaris.core.persistence.dao.entity.LoadGrantsResult;
+import org.apache.polaris.core.persistence.dao.entity.LoadPolicyMappingsResult;
+import org.apache.polaris.core.persistence.dao.entity.PolicyAttachmentResult;
import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult;
import org.apache.polaris.core.persistence.dao.entity.PrivilegeResult;
import org.apache.polaris.core.persistence.dao.entity.ResolvedEntityResult;
import org.apache.polaris.core.persistence.dao.entity.ScopedCredentialsResult;
import org.apache.polaris.core.persistence.dao.entity.ValidateAccessResult;
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
+import org.apache.polaris.core.policy.PolicyEntity;
+import org.apache.polaris.core.policy.PolicyType;
import org.apache.polaris.core.storage.PolarisCredentialProperty;
import org.apache.polaris.core.storage.PolarisStorageActions;
import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
@@ -2316,4 +2321,227 @@ public class TransactionalMetaStoreManagerImpl extends
BaseMetaStoreManager {
entityCatalogId,
entityId));
}
+
+ /** {@inheritDoc} */
+ @Override
+ public @Nonnull PolicyAttachmentResult attachPolicyToEntity(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull List<PolarisEntityCore> targetCatalogPath,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull List<PolarisEntityCore> policyCatalogPath,
+ @Nonnull PolicyEntity policy,
+ Map<String, String> parameters) {
+ // get metastore we should be using
+ TransactionalPersistence ms = ((TransactionalPersistence)
callCtx.getMetaStore());
+
+ return ms.runInTransaction(
+ callCtx,
+ () ->
+ this.doAttachPolicyToEntity(
+ callCtx, ms, targetCatalogPath, target, policyCatalogPath,
policy, parameters));
+ }
+
+ /**
+ * See {@link #attachPolicyToEntity(PolarisCallContext, List,
PolarisEntityCore, List,
+ * PolicyEntity, Map)}
+ */
+ private @Nonnull PolicyAttachmentResult doAttachPolicyToEntity(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull TransactionalPersistence ms,
+ @Nonnull List<PolarisEntityCore> targetCatalogPath,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull List<PolarisEntityCore> policyCatalogPath,
+ @Nonnull PolicyEntity policy,
+ Map<String, String> parameters) {
+ PolarisEntityResolver targetResolver =
+ new PolarisEntityResolver(callCtx, ms, targetCatalogPath, target);
+ PolarisEntityResolver policyResolver =
+ new PolarisEntityResolver(callCtx, ms, policyCatalogPath, policy);
+ if (targetResolver.isFailure() || policyResolver.isFailure()) {
+ return new
PolicyAttachmentResult(BaseResult.ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null);
+ }
+
+ return this.persistNewPolicyMappingRecord(callCtx, ms, target, policy,
parameters);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public @Nonnull PolicyAttachmentResult detachPolicyFromEntity(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull List<PolarisEntityCore> targetCatalogPath,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull List<PolarisEntityCore> policyCatalogPath,
+ @Nonnull PolicyEntity policy) {
+ TransactionalPersistence ms = ((TransactionalPersistence)
callCtx.getMetaStore());
+ return ms.runInTransaction(
+ callCtx,
+ () ->
+ this.doDetachPolicyFromEntity(
+ callCtx, ms, targetCatalogPath, target, policyCatalogPath,
policy));
+ }
+
+ /**
+ * See {@link #detachPolicyFromEntity(PolarisCallContext, List,
PolarisEntityCore,
+ * List,PolicyEntity)}
+ */
+ private PolicyAttachmentResult doDetachPolicyFromEntity(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull TransactionalPersistence ms,
+ @Nonnull List<PolarisEntityCore> targetCatalogPath,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull List<PolarisEntityCore> policyCatalogPath,
+ @Nonnull PolicyEntity policy) {
+ PolarisEntityResolver targetResolver =
+ new PolarisEntityResolver(callCtx, ms, targetCatalogPath, target);
+ PolarisEntityResolver policyResolver =
+ new PolarisEntityResolver(callCtx, ms, policyCatalogPath, policy);
+ if (targetResolver.isFailure() || policyResolver.isFailure()) {
+ return new
PolicyAttachmentResult(BaseResult.ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null);
+ }
+
+ PolarisPolicyMappingRecord mappingRecord =
+ ms.lookupPolicyMappingRecordInCurrentTxn(
+ callCtx,
+ target.getCatalogId(),
+ target.getId(),
+ policy.getPolicyTypeCode(),
+ policy.getCatalogId(),
+ policy.getId());
+ if (mappingRecord == null) {
+ return new
PolicyAttachmentResult(BaseResult.ReturnStatus.POLICY_MAPPING_NOT_FOUND, null);
+ }
+
+ ms.deleteFromPolicyMappingRecordsInCurrentTxn(callCtx, mappingRecord);
+
+ return new PolicyAttachmentResult(mappingRecord);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public @Nonnull LoadPolicyMappingsResult loadPoliciesOnEntity(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisEntityCore target) {
+ TransactionalPersistence ms = ((TransactionalPersistence)
callCtx.getMetaStore());
+ return ms.runInReadTransaction(callCtx, () ->
this.doLoadPoliciesOnEntity(callCtx, ms, target));
+ }
+
+ /** See {@link #loadPoliciesOnEntity(PolarisCallContext, PolarisEntityCore)}
*/
+ private LoadPolicyMappingsResult doLoadPoliciesOnEntity(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull TransactionalPersistence ms,
+ @Nonnull PolarisEntityCore target) {
+ PolarisBaseEntity entity =
+ ms.lookupEntityInCurrentTxn(
+ callCtx, target.getCatalogId(), target.getId(),
target.getTypeCode());
+ if (entity == null) {
+ // Target entity does not exists
+ return new
LoadPolicyMappingsResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null);
+ }
+
+ final List<PolarisPolicyMappingRecord> policyMappingRecords =
+ ms.loadAllPoliciesOnTargetInCurrentTxn(callCtx, target.getCatalogId(),
target.getId());
+
+ List<PolarisBaseEntity> policyEntities =
+ loadPoliciesFromMappingRecords(callCtx, ms, policyMappingRecords);
+ return new LoadPolicyMappingsResult(policyMappingRecords, policyEntities);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public @Nonnull LoadPolicyMappingsResult loadPoliciesOnEntityByType(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull PolicyType policyType) {
+ TransactionalPersistence ms = ((TransactionalPersistence)
callCtx.getMetaStore());
+ return ms.runInReadTransaction(
+ callCtx, () -> this.doLoadPoliciesOnEntityByType(callCtx, ms, target,
policyType));
+ }
+
+ /** See {@link #loadPoliciesOnEntityByType(PolarisCallContext,
PolarisEntityCore, PolicyType)} */
+ public LoadPolicyMappingsResult doLoadPoliciesOnEntityByType(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull TransactionalPersistence ms,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull PolicyType policyType) {
+ PolarisBaseEntity entity =
+ ms.lookupEntityInCurrentTxn(
+ callCtx, target.getCatalogId(), target.getId(),
target.getTypeCode());
+ if (entity == null) {
+ // Target entity does not exists
+ return new
LoadPolicyMappingsResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null);
+ }
+
+ final List<PolarisPolicyMappingRecord> policyMappingRecords =
+ ms.loadPoliciesOnTargetByTypeInCurrentTxn(
+ callCtx, target.getCatalogId(), target.getId(),
policyType.getCode());
+ List<PolarisBaseEntity> policyEntities =
+ loadPoliciesFromMappingRecords(callCtx, ms, policyMappingRecords);
+ return new LoadPolicyMappingsResult(policyMappingRecords, policyEntities);
+ }
+
+ /**
+ * Create and persist a new policy mapping record
+ *
+ * @param callCtx call context
+ * @param ms meta store in read/write mode
+ * @param target target
+ * @param policy policy
+ * @param parameters optional parameters
+ * @return new policy mapping record which was created and persisted
+ */
+ private @Nonnull PolicyAttachmentResult persistNewPolicyMappingRecord(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull TransactionalPersistence ms,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull PolicyEntity policy,
+ Map<String, String> parameters) {
+ callCtx.getDiagServices().checkNotNull(target, "unexpected_null_target");
+ callCtx.getDiagServices().checkNotNull(policy, "unexpected_null_policy");
+
+ PolarisPolicyMappingRecord mappingRecord =
+ new PolarisPolicyMappingRecord(
+ target.getCatalogId(),
+ target.getId(),
+ policy.getCatalogId(),
+ policy.getId(),
+ policy.getPolicyTypeCode(),
+ parameters);
+
+ try {
+ ms.checkConditionsForWriteToPolicyMappingRecordsInCurrentTxn(callCtx,
mappingRecord);
+ ms.writeToPolicyMappingRecordsInCurrentTxn(callCtx, mappingRecord);
+ } catch (IllegalArgumentException e) {
+ return new PolicyAttachmentResult(
+ BaseResult.ReturnStatus.UNEXPECTED_ERROR_SIGNALED, "Unknown policy
type");
+ } catch (PolicyMappingAlreadyExistsException e) {
+ return new PolicyAttachmentResult(
+ BaseResult.ReturnStatus.POLICY_MAPPING_OF_SAME_TYPE_ALREADY_EXISTS,
+ e.getExistingRecord().getPolicyTypeCode());
+ }
+
+ return new PolicyAttachmentResult(mappingRecord);
+ }
+
+ /**
+ * Load policies from a list of policy mapping records
+ *
+ * @param callCtx call context
+ * @param ms meta store
+ * @param policyMappingRecords a list of policy mapping records
+ * @return a list of policy entities
+ */
+ private List<PolarisBaseEntity> loadPoliciesFromMappingRecords(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull TransactionalPersistence ms,
+ @Nonnull List<PolarisPolicyMappingRecord> policyMappingRecords) {
+ List<PolarisEntityId> policyEntityIds =
+ policyMappingRecords.stream()
+ .map(
+ policyMappingRecord ->
+ new PolarisEntityId(
+ policyMappingRecord.getPolicyCatalogId(),
+ policyMappingRecord.getPolicyId()))
+ .distinct()
+ .collect(Collectors.toList());
+ return ms.lookupEntitiesInCurrentTxn(callCtx, policyEntityIds);
+ }
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalPersistence.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalPersistence.java
index f3d3842bb..2057991db 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalPersistence.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalPersistence.java
@@ -36,6 +36,7 @@ import org.apache.polaris.core.entity.PolarisGrantRecord;
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
import org.apache.polaris.core.persistence.BasePersistence;
import org.apache.polaris.core.persistence.IntegrationPersistence;
+import org.apache.polaris.core.policy.TransactionalPolicyMappingPersistence;
import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
import org.apache.polaris.core.storage.PolarisStorageIntegration;
@@ -44,7 +45,8 @@ import
org.apache.polaris.core.storage.PolarisStorageIntegration;
* which can support a runInTransaction semantic, while providing default
implementations of some of
* the BasePersistence methods in terms of lower-level methods that subclasses
must implement.
*/
-public interface TransactionalPersistence extends BasePersistence,
IntegrationPersistence {
+public interface TransactionalPersistence
+ extends BasePersistence, IntegrationPersistence,
TransactionalPolicyMappingPersistence {
/**
* Run the specified transaction code (a Supplier lambda type) in a database
read/write
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TreeMapMetaStore.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TreeMapMetaStore.java
index b37f6e840..c91414040 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TreeMapMetaStore.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TreeMapMetaStore.java
@@ -31,6 +31,7 @@ import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.entity.PolarisEntityCore;
import org.apache.polaris.core.entity.PolarisGrantRecord;
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
/** Implements a simple in-memory store for Polaris, using tree-map */
public class TreeMapMetaStore {
@@ -209,6 +210,10 @@ public class TreeMapMetaStore {
// slice to store principal secrets
private final Slice<PolarisPrincipalSecrets> slicePrincipalSecrets;
+ private final Slice<PolarisPolicyMappingRecord> slicePolicyMappingRecords;
+
+ private final Slice<PolarisPolicyMappingRecord>
slicePolicyMappingRecordsByPolicy;
+
// next id generator
private final AtomicLong nextId = new AtomicLong();
@@ -266,6 +271,29 @@ public class TreeMapMetaStore {
principalSecrets -> String.format("%s",
principalSecrets.getPrincipalClientId()),
PolarisPrincipalSecrets::new);
+ this.slicePolicyMappingRecords =
+ new Slice<>(
+ policyMappingRecord ->
+ String.format(
+ "%d::%d::%d::%d::%d",
+ policyMappingRecord.getTargetCatalogId(),
+ policyMappingRecord.getTargetId(),
+ policyMappingRecord.getPolicyTypeCode(),
+ policyMappingRecord.getPolicyCatalogId(),
+ policyMappingRecord.getPolicyId()),
+ PolarisPolicyMappingRecord::new);
+
+ this.slicePolicyMappingRecordsByPolicy =
+ new Slice<>(
+ policyMappingRecord ->
+ String.format(
+ "%d::%d::%d::%d",
+ policyMappingRecord.getPolicyCatalogId(),
+ policyMappingRecord.getPolicyId(),
+ policyMappingRecord.getTargetCatalogId(),
+ policyMappingRecord.getTargetId()),
+ PolarisPolicyMappingRecord::new);
+
// no transaction open yet
this.diagnosticServices = diagnostics;
this.tr = null;
@@ -345,6 +373,8 @@ public class TreeMapMetaStore {
this.sliceGrantRecords.startWriteTransaction();
this.sliceGrantRecordsByGrantee.startWriteTransaction();
this.slicePrincipalSecrets.startWriteTransaction();
+ this.slicePolicyMappingRecords.startWriteTransaction();
+ this.slicePolicyMappingRecordsByPolicy.startWriteTransaction();
}
/** Rollback transaction */
@@ -355,6 +385,8 @@ public class TreeMapMetaStore {
this.sliceGrantRecords.rollback();
this.sliceGrantRecordsByGrantee.rollback();
this.slicePrincipalSecrets.rollback();
+ this.slicePolicyMappingRecords.rollback();
+ this.slicePolicyMappingRecordsByPolicy.rollback();
}
/** Ensure that a read/write FDB transaction has been started */
@@ -497,6 +529,14 @@ public class TreeMapMetaStore {
return slicePrincipalSecrets;
}
+ public Slice<PolarisPolicyMappingRecord> getSlicePolicyMappingRecords() {
+ return slicePolicyMappingRecords;
+ }
+
+ public Slice<PolarisPolicyMappingRecord>
getSlicePolicyMappingRecordsByPolicy() {
+ return slicePolicyMappingRecordsByPolicy;
+ }
+
/**
* Next sequence number generator
*
@@ -516,5 +556,7 @@ public class TreeMapMetaStore {
this.sliceGrantRecordsByGrantee.deleteAll();
this.sliceGrantRecords.deleteAll();
this.slicePrincipalSecrets.deleteAll();
+ this.slicePolicyMappingRecords.deleteAll();
+ this.slicePolicyMappingRecordsByPolicy.deleteAll();
}
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TreeMapTransactionalPersistenceImpl.java
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TreeMapTransactionalPersistenceImpl.java
index 8d2e59c96..822045673 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TreeMapTransactionalPersistenceImpl.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TreeMapTransactionalPersistenceImpl.java
@@ -38,6 +38,7 @@ import org.apache.polaris.core.entity.PolarisGrantRecord;
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
import org.apache.polaris.core.persistence.BaseMetaStoreManager;
import org.apache.polaris.core.persistence.PrincipalSecretsGenerator;
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
import org.apache.polaris.core.storage.PolarisStorageIntegration;
import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider;
@@ -551,4 +552,86 @@ public class TreeMapTransactionalPersistenceImpl extends
AbstractTransactionalPe
public void rollback() {
this.store.rollback();
}
+
+ /** {@inheritDoc} */
+ @Override
+ public void writeToPolicyMappingRecordsInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+ this.store.getSlicePolicyMappingRecords().write(record);
+ this.store.getSlicePolicyMappingRecordsByPolicy().write(record);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void deleteFromPolicyMappingRecordsInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+ this.store.getSlicePolicyMappingRecords().delete(record);
+ this.store.getSlicePolicyMappingRecordsByPolicy().delete(record);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void deleteAllEntityPolicyMappingRecordsInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull PolarisEntityCore entity,
+ @Nonnull List<PolarisPolicyMappingRecord> mappingOnTarget,
+ @Nonnull List<PolarisPolicyMappingRecord> mappingOnPolicy) {
+ // build composite prefix key and delete policy mapping records on the
indexed side of each
+ // mapping table
+ String prefix = this.store.buildPrefixKeyComposite(entity.getCatalogId(),
entity.getId());
+ this.store.getSlicePolicyMappingRecords().deleteRange(prefix);
+ this.store.getSlicePolicyMappingRecordsByPolicy().deleteRange(prefix);
+
+ // also delete the other side. We need to delete these mapping one at a
time versus doing a
+ // range delete
+ mappingOnTarget.forEach(record ->
this.store.getSlicePolicyMappingRecords().delete(record));
+ mappingOnPolicy.forEach(
+ record ->
this.store.getSlicePolicyMappingRecordsByPolicy().delete(record));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public @Nullable PolarisPolicyMappingRecord
lookupPolicyMappingRecordInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx,
+ long targetCatalogId,
+ long targetId,
+ int policyTypeCode,
+ long policyCatalogId,
+ long policyId) {
+ return this.store
+ .getSlicePolicyMappingRecords()
+ .read(
+ this.store.buildKeyComposite(
+ targetCatalogId, targetId, policyTypeCode, policyCatalogId,
policyId));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public @Nonnull List<PolarisPolicyMappingRecord>
loadPoliciesOnTargetByTypeInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx,
+ long targetCatalogId,
+ long targetId,
+ int policyTypeCode) {
+ return this.store
+ .getSlicePolicyMappingRecords()
+ .readRange(this.store.buildPrefixKeyComposite(targetCatalogId,
targetId, policyTypeCode));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public @Nonnull List<PolarisPolicyMappingRecord>
loadAllPoliciesOnTargetInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, long targetCatalogId, long
targetId) {
+ return this.store
+ .getSlicePolicyMappingRecords()
+ .readRange(this.store.buildPrefixKeyComposite(targetCatalogId,
targetId));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public @Nonnull List<PolarisPolicyMappingRecord>
loadAllTargetsOnPolicyInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, long policyCatalogId, long
policyId) {
+ return this.store
+ .getSlicePolicyMappingRecordsByPolicy()
+ .readRange(this.store.buildPrefixKeyComposite(policyCatalogId,
policyId));
+ }
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolarisPolicyMappingManager.java
b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolarisPolicyMappingManager.java
new file mode 100644
index 000000000..266d5477e
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolarisPolicyMappingManager.java
@@ -0,0 +1,103 @@
+/*
+ * 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 jakarta.annotation.Nonnull;
+import java.util.List;
+import java.util.Map;
+import org.apache.polaris.core.PolarisCallContext;
+import org.apache.polaris.core.entity.PolarisEntityCore;
+import org.apache.polaris.core.persistence.dao.entity.LoadPolicyMappingsResult;
+import org.apache.polaris.core.persistence.dao.entity.PolicyAttachmentResult;
+
+public interface PolarisPolicyMappingManager {
+
+ /**
+ * Attach a policy to a target entity, for example attach a policy to a
table.
+ *
+ * <p>For inheritable policy, only one policy of the same type can be
attached to the target. For
+ * non-inheritable policy, multiple policies of the same type can be
attached to the target.
+ *
+ * @param callCtx call context
+ * @param targetCatalogPath path to the target entity
+ * @param target target entity
+ * @param policyCatalogPath path to the policy entity
+ * @param policy policy entity
+ * @param parameters additional parameters for the attachment
+ * @return The policy mapping record we created for this attachment. Will
return ENTITY_NOT_FOUND
+ * if the specified target or policy does not exist. Will return
+ * POLICY_OF_SAME_TYPE_ALREADY_ATTACHED if the target already has a
policy of the same type
+ * attached and the policy is inheritable.
+ */
+ @Nonnull
+ PolicyAttachmentResult attachPolicyToEntity(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull List<PolarisEntityCore> targetCatalogPath,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull List<PolarisEntityCore> policyCatalogPath,
+ @Nonnull PolicyEntity policy,
+ Map<String, String> parameters);
+
+ /**
+ * Detach a policy from a target entity
+ *
+ * @param callCtx call context
+ * @param catalogPath path to the target entity
+ * @param target target entity
+ * @param policyCatalogPath path to the policy entity
+ * @param policy policy entity
+ * @return The policy mapping record we detached. Will return
ENTITY_NOT_FOUND if the specified
+ * target or policy does not exist. Will return POLICY_MAPPING_NOT_FOUND
if the mapping cannot
+ * be found
+ */
+ @Nonnull
+ PolicyAttachmentResult detachPolicyFromEntity(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull List<PolarisEntityCore> catalogPath,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull List<PolarisEntityCore> policyCatalogPath,
+ @Nonnull PolicyEntity policy);
+
+ /**
+ * Load all policies attached to a target entity
+ *
+ * @param callCtx call context
+ * @param target target entity
+ * @return the list of policy mapping records on the target entity. Will
return ENTITY_NOT_FOUND
+ * if the specified target does not exist.
+ */
+ @Nonnull
+ LoadPolicyMappingsResult loadPoliciesOnEntity(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisEntityCore target);
+
+ /**
+ * Load all policies of a specific type attached to a target entity
+ *
+ * @param callCtx call context
+ * @param target target entity
+ * @param policyType the type of policy
+ * @return the list of policy mapping records on the target entity. Will
return ENTITY_NOT_FOUND
+ * if the specified target does not exist.
+ */
+ @Nonnull
+ LoadPolicyMappingsResult loadPoliciesOnEntityByType(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull PolarisEntityCore target,
+ @Nonnull PolicyType policyType);
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolarisPolicyMappingRecord.java
b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolarisPolicyMappingRecord.java
new file mode 100644
index 000000000..d4bf118b6
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolarisPolicyMappingRecord.java
@@ -0,0 +1,215 @@
+/*
+ * 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.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class PolarisPolicyMappingRecord {
+ // to serialize/deserialize properties
+ public static final String EMPTY_MAP_STRING = "{}";
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ // id of the catalog where target entity resides
+ private long targetCatalogId;
+
+ // id of the target entity
+ private long targetId;
+
+ // id of the catalog where the policy entity resides
+ private long policyCatalogId;
+
+ // id of the policy
+ private long policyId;
+
+ // id associated to the policy type
+ private int policyTypeCode;
+
+ // additional parameters of the mapping
+ private String parameters;
+
+ public PolarisPolicyMappingRecord() {}
+
+ public long getTargetCatalogId() {
+ return targetCatalogId;
+ }
+
+ public void setTargetCatalogId(long targetCatalogId) {
+ this.targetCatalogId = targetCatalogId;
+ }
+
+ public long getTargetId() {
+ return targetId;
+ }
+
+ public void setTargetId(long targetId) {
+ this.targetId = targetId;
+ }
+
+ public long getPolicyId() {
+ return policyId;
+ }
+
+ public void setPolicyId(long policyId) {
+ this.policyId = policyId;
+ }
+
+ public int getPolicyTypeCode() {
+ return policyTypeCode;
+ }
+
+ public void setPolicyTypeCode(int policyTypeCode) {
+ this.policyTypeCode = policyTypeCode;
+ }
+
+ public long getPolicyCatalogId() {
+ return policyCatalogId;
+ }
+
+ public void setPolicyCatalogId(long policyCatalogId) {
+ this.policyCatalogId = policyCatalogId;
+ }
+
+ public String getParameters() {
+ return parameters;
+ }
+
+ public void setParameters(String parameters) {
+ this.parameters = parameters;
+ }
+
+ public Map<String, String> getParametersAsMap() {
+ if (parameters == null) {
+ return new HashMap<>();
+ }
+ try {
+ return MAPPER.readValue(parameters, new TypeReference<>() {});
+ } catch (JsonProcessingException ex) {
+ throw new IllegalStateException(
+ String.format("Failed to deserialize json. parameters %s",
parameters), ex);
+ }
+ }
+
+ public void setParametersAsMap(Map<String, String> parameters) {
+ try {
+ this.parameters =
+ parameters == null ? EMPTY_MAP_STRING :
MAPPER.writeValueAsString(parameters);
+ } catch (JsonProcessingException ex) {
+ throw new IllegalStateException(
+ String.format("Failed to serialize json. properties %s",
parameters), ex);
+ }
+ }
+
+ /**
+ * Constructor
+ *
+ * @param targetCatalogId id of the catalog where target entity resides
+ * @param targetId id of the target entity
+ * @param policyCatalogId id of the catalog where the policy entity resides
+ * @param policyId id of the policy
+ * @param policyTypeCode id associated to the policy type
+ * @param parameters additional parameters of the mapping
+ */
+ @JsonCreator
+ public PolarisPolicyMappingRecord(
+ @JsonProperty("targetCatalogId") long targetCatalogId,
+ @JsonProperty("targetId") long targetId,
+ @JsonProperty("policyCatalogId") long policyCatalogId,
+ @JsonProperty("policyId") long policyId,
+ @JsonProperty("policyTypeCode") int policyTypeCode,
+ @JsonProperty("parameters") String parameters) {
+ this.targetCatalogId = targetCatalogId;
+ this.targetId = targetId;
+ this.policyCatalogId = policyCatalogId;
+ this.policyId = policyId;
+ this.policyTypeCode = policyTypeCode;
+ this.parameters = parameters;
+ }
+
+ public PolarisPolicyMappingRecord(
+ long targetCatalogId,
+ long targetId,
+ long policyCatalogId,
+ long policyId,
+ int policyTypeCode,
+ Map<String, String> parameters) {
+ this.targetCatalogId = targetCatalogId;
+ this.targetId = targetId;
+ this.policyCatalogId = policyCatalogId;
+ this.policyId = policyId;
+ this.policyTypeCode = policyTypeCode;
+ this.setParametersAsMap(parameters);
+ }
+
+ /**
+ * Copy constructor
+ *
+ * @param policyMappingRecord policy mapping rec to copy
+ */
+ public PolarisPolicyMappingRecord(PolarisPolicyMappingRecord
policyMappingRecord) {
+ this.targetCatalogId = policyMappingRecord.getTargetCatalogId();
+ this.targetId = policyMappingRecord.getTargetId();
+ this.policyCatalogId = policyMappingRecord.getPolicyCatalogId();
+ this.policyId = policyMappingRecord.getPolicyId();
+ this.policyTypeCode = policyMappingRecord.getPolicyTypeCode();
+ this.parameters = policyMappingRecord.getParameters();
+ }
+
+ @Override
+ public String toString() {
+ return "PolarisPolicyMappingRec{"
+ + "targetCatalogId="
+ + targetCatalogId
+ + ", targetId="
+ + targetId
+ + ", policyCatalogId="
+ + policyCatalogId
+ + ", policyId="
+ + policyId
+ + ", policyType='"
+ + policyTypeCode
+ + ", parameters='"
+ + parameters
+ + "}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PolarisPolicyMappingRecord that = (PolarisPolicyMappingRecord) o;
+ return targetCatalogId == that.targetCatalogId
+ && targetId == that.targetId
+ && policyCatalogId == that.policyCatalogId
+ && policyId == that.policyId
+ && policyTypeCode == that.policyTypeCode
+ && Objects.equals(parameters, that.parameters);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(targetId, policyId, policyCatalogId,
policyTypeCode, parameters);
+ }
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyMappingPersistence.java
b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyMappingPersistence.java
new file mode 100644
index 000000000..33a754668
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/policy/PolicyMappingPersistence.java
@@ -0,0 +1,151 @@
+/*
+ * 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 jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import java.util.List;
+import org.apache.polaris.core.PolarisCallContext;
+import org.apache.polaris.core.entity.PolarisEntityCore;
+
+/**
+ * Interface for interacting with the Polaris persistence backend for Policy
Mapping operations.
+ * This interface provides methods to persist and retrieve policy mapping
records, which define the
+ * relationships between policies and target entities in Polaris.
+ *
+ * <p>Note that APIs to the actual persistence store are very basic, often
point read or write to
+ * the underlying data store. The goal is to make it really easy to back this
using databases like
+ * Postgres or simpler KV store. Each API in this interface need to be atomic.
+ */
+public interface PolicyMappingPersistence {
+
+ /**
+ * Write the specified policyMappingRecord to the policy_mapping_records
table. If there is a
+ * conflict (existing record with the same PK), all attributes of the new
record will replace the
+ * existing one.
+ *
+ * @param callCtx call context
+ * @param record policy mapping record to write, potentially replacing an
existing policy mapping
+ * with the same key
+ */
+ default void writeToPolicyMappingRecords(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /**
+ * Delete the specified policyMappingRecord to the policy_mapping_records
table.
+ *
+ * @param callCtx call context
+ * @param record policy mapping record to delete.
+ */
+ default void deleteFromPolicyMappingRecords(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /**
+ * Delete the all policy mapping records in the policy_mapping_records table
for the specified
+ * entity. This method will delete all policy mapping records on the entity
+ *
+ * @param callCtx call context
+ * @param entity entity whose policy mapping records should be deleted
+ * @param mappingOnTarget all mappings on that target entity. Empty list if
that entity is not a
+ * target
+ * @param mappingOnPolicy all mappings on that policy entity. Empty list if
that entity is not a
+ * policy
+ */
+ default void deleteAllEntityPolicyMappingRecords(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull PolarisEntityCore entity,
+ @Nonnull List<PolarisPolicyMappingRecord> mappingOnTarget,
+ @Nonnull List<PolarisPolicyMappingRecord> mappingOnPolicy) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /**
+ * Look up the specified policy mapping record from the
policy_mapping_records table. Return NULL
+ * if not found
+ *
+ * @param callCtx call context
+ * @param targetCatalogId catalog id of the target entity, NULL_ID if the
entity is top-level
+ * @param targetId id of the target entity
+ * @param policyTypeCode type code of the policy entity
+ * @param policyCatalogId catalog id of the policy entity
+ * @param policyId id of the policy entity
+ * @return the policy mapping record if found, NULL if not found
+ */
+ @Nullable
+ default PolarisPolicyMappingRecord lookupPolicyMappingRecord(
+ @Nonnull PolarisCallContext callCtx,
+ long targetCatalogId,
+ long targetId,
+ int policyTypeCode,
+ long policyCatalogId,
+ long policyId) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /**
+ * Get all policies on the specified target entity with specified policy
type.
+ *
+ * @param callCtx call context
+ * @param targetCatalogId catalog id of the target entity, NULL_ID if the
entity is top-level
+ * @param targetId id of the target entity
+ * @param policyTypeCode type code of the policy entity
+ * @return the list of policy mapping records for the specified target
entity with the specified
+ * policy type
+ */
+ @Nonnull
+ default List<PolarisPolicyMappingRecord> loadPoliciesOnTargetByType(
+ @Nonnull PolarisCallContext callCtx,
+ long targetCatalogId,
+ long targetId,
+ int policyTypeCode) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /**
+ * Get all policies on the specified target entity.
+ *
+ * @param callCtx call context
+ * @param targetCatalogId catalog id of the target entity, NULL_ID if the
entity is top-level
+ * @param targetId id of the target entity
+ * @return the list of policy mapping records for the specified target entity
+ */
+ @Nonnull
+ default List<PolarisPolicyMappingRecord> loadAllPoliciesOnTarget(
+ @Nonnull PolarisCallContext callCtx, long targetCatalogId, long
targetId) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /**
+ * Get all targets of the specified policy entity
+ *
+ * @param callCtx call context
+ * @param policyCatalogId catalog id of the policy entity, NULL_ID if the
entity is top-level
+ * @param policyId id of the policy entity
+ * @return the list of policy mapping records for the specified policy entity
+ */
+ @Nonnull
+ default List<PolarisPolicyMappingRecord> loadAllTargetsOnPolicy(
+ @Nonnull PolarisCallContext callCtx, long policyCatalogId, long
policyId) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/policy/TransactionalPolicyMappingPersistence.java
b/polaris-core/src/main/java/org/apache/polaris/core/policy/TransactionalPolicyMappingPersistence.java
new file mode 100644
index 000000000..6ad1ac80c
--- /dev/null
+++
b/polaris-core/src/main/java/org/apache/polaris/core/policy/TransactionalPolicyMappingPersistence.java
@@ -0,0 +1,98 @@
+/*
+ * 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 jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import java.util.List;
+import org.apache.polaris.core.PolarisCallContext;
+import org.apache.polaris.core.entity.PolarisEntityCore;
+
+public interface TransactionalPolicyMappingPersistence {
+ /** See {@link PolicyMappingPersistence#writeToPolicyMappingRecords} */
+ default void writeToPolicyMappingRecordsInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /**
+ * Helpers to check conditions for writing new PolicyMappingRecords in
current transaction.
+ *
+ * <p>It should throw a PolicyMappingAlreadyExistsException if the new
record conflicts with an
+ * exising record with same policy type but different policy.
+ *
+ * @param callCtx call context
+ * @param record policy mapping record to write.
+ */
+ default void checkConditionsForWriteToPolicyMappingRecordsInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /** See {@link PolicyMappingPersistence#deleteFromPolicyMappingRecords} */
+ default void deleteFromPolicyMappingRecordsInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, @Nonnull PolarisPolicyMappingRecord
record) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /** See {@link PolicyMappingPersistence#deleteAllEntityPolicyMappingRecords}
*/
+ default void deleteAllEntityPolicyMappingRecordsInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx,
+ @Nonnull PolarisEntityCore entity,
+ @Nonnull List<PolarisPolicyMappingRecord> mappingOnTarget,
+ @Nonnull List<PolarisPolicyMappingRecord> mappingOnPolicy) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /** See {@link PolicyMappingPersistence#lookupPolicyMappingRecord} */
+ @Nullable
+ default PolarisPolicyMappingRecord lookupPolicyMappingRecordInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx,
+ long targetCatalogId,
+ long targetId,
+ int policyTypeCode,
+ long policyCatalogId,
+ long policyId) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /** See {@link PolicyMappingPersistence#loadPoliciesOnTargetByType} */
+ @Nonnull
+ default List<PolarisPolicyMappingRecord>
loadPoliciesOnTargetByTypeInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx,
+ long targetCatalogId,
+ long targetId,
+ int policyTypeCode) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /** See {@link PolicyMappingPersistence#loadAllPoliciesOnTarget} */
+ @Nonnull
+ default List<PolarisPolicyMappingRecord> loadAllPoliciesOnTargetInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, long targetCatalogId, long
targetId) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ /** See {@link PolicyMappingPersistence#loadAllTargetsOnPolicy} */
+ @Nonnull
+ default List<PolarisPolicyMappingRecord> loadAllTargetsOnPolicyInCurrentTxn(
+ @Nonnull PolarisCallContext callCtx, long policyCatalogId, long
policyId) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+}
diff --git
a/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/BasePolarisMetaStoreManagerTest.java
b/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/BasePolarisMetaStoreManagerTest.java
index 81f233825..0f9824fcd 100644
---
a/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/BasePolarisMetaStoreManagerTest.java
+++
b/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/BasePolarisMetaStoreManagerTest.java
@@ -280,6 +280,12 @@ public abstract class BasePolarisMetaStoreManagerTest {
polarisTestMetaStoreManager.testEntityCache();
}
+ /** Test that attaching/detaching policies works well */
+ @Test
+ void testPolicyMapping() {
+ polarisTestMetaStoreManager.testPolicyMapping();
+ }
+
@Test
void testLoadTasks() {
for (int i = 0; i < 20; i++) {
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 591c657d1..3e7a0fb2a 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
@@ -48,7 +48,10 @@ import
org.apache.polaris.core.persistence.dao.entity.CreatePrincipalResult;
import org.apache.polaris.core.persistence.dao.entity.DropEntityResult;
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.LoadPolicyMappingsResult;
+import org.apache.polaris.core.persistence.dao.entity.PolicyAttachmentResult;
import org.apache.polaris.core.persistence.dao.entity.ResolvedEntityResult;
+import org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
import org.apache.polaris.core.policy.PolicyEntity;
import org.apache.polaris.core.policy.PolicyType;
import org.apache.polaris.core.policy.PredefinedPolicyTypes;
@@ -956,6 +959,195 @@ public class PolarisTestMetaStoreManager {
this.ensureGrantRecordRemoved(granted, grantee, priv);
}
+ /** attach a policy to a target */
+ void attachPolicyToTarget(
+ List<PolarisEntityCore> targetCatalogPath,
+ PolarisBaseEntity target,
+ List<PolarisEntityCore> policyCatalogPath,
+ PolicyEntity policy) {
+ polarisMetaStoreManager.attachPolicyToEntity(
+ polarisCallContext, targetCatalogPath, target, policyCatalogPath,
policy, null);
+ ensurePolicyMappingRecordExists(target, policy);
+ }
+
+ /** detach a policy from a target */
+ void detachPolicyFromTarget(
+ List<PolarisEntityCore> targetCatalogPath,
+ PolarisBaseEntity target,
+ List<PolarisEntityCore> policyCatalogPath,
+ PolicyEntity policy) {
+ polarisMetaStoreManager.detachPolicyFromEntity(
+ polarisCallContext, targetCatalogPath, target, policyCatalogPath,
policy);
+ ensurePolicyMappingRecordRemoved(target, policy);
+ }
+
+ /**
+ * Ensure that the specified policy mapping record exists
+ *
+ * @param target the target
+ * @param policy the policy
+ */
+ void ensurePolicyMappingRecordExists(PolarisBaseEntity target, PolicyEntity
policy) {
+ target =
+ polarisMetaStoreManager
+ .loadEntity(
+ this.polarisCallContext, target.getCatalogId(),
target.getId(), target.getType())
+ .getEntity();
+ Assertions.assertThat(target).isNotNull();
+
+ policy =
+ PolicyEntity.of(
+ polarisMetaStoreManager
+ .loadEntity(
+ this.polarisCallContext,
+ policy.getCatalogId(),
+ policy.getId(),
+ PolarisEntityType.POLICY)
+ .getEntity());
+ Assertions.assertThat(policy).isNotNull();
+
+ LoadPolicyMappingsResult loadPolicyMappingsResult =
+ polarisMetaStoreManager.loadPoliciesOnEntity(this.polarisCallContext,
target);
+
+ validateLoadedPolicyMappings(loadPolicyMappingsResult);
+
+ checkPolicyMappingRecordExists(
+ loadPolicyMappingsResult.getPolicyMappingRecords(), target, policy);
+
+ // also try load by specific type
+ LoadPolicyMappingsResult loadPolicyMappingsResultByType =
+ polarisMetaStoreManager.loadPoliciesOnEntityByType(
+ this.polarisCallContext, target, policy.getPolicyType());
+ validateLoadedPolicyMappings(loadPolicyMappingsResultByType);
+ checkPolicyMappingRecordExists(
+ loadPolicyMappingsResultByType.getPolicyMappingRecords(), target,
policy);
+ }
+
+ /**
+ * Ensure that the specified policy mapping record has been removed
+ *
+ * @param target the target
+ * @param policy the policy
+ */
+ void ensurePolicyMappingRecordRemoved(PolarisBaseEntity target, PolicyEntity
policy) {
+ target =
+ polarisMetaStoreManager
+ .loadEntity(
+ this.polarisCallContext, target.getCatalogId(),
target.getId(), target.getType())
+ .getEntity();
+ Assertions.assertThat(target).isNotNull();
+
+ policy =
+ PolicyEntity.of(
+ polarisMetaStoreManager
+ .loadEntity(
+ this.polarisCallContext,
+ policy.getCatalogId(),
+ policy.getId(),
+ PolarisEntityType.POLICY)
+ .getEntity());
+ Assertions.assertThat(policy).isNotNull();
+
+ LoadPolicyMappingsResult loadPolicyMappingsResult =
+ polarisMetaStoreManager.loadPoliciesOnEntity(this.polarisCallContext,
target);
+
+ validateLoadedPolicyMappings(loadPolicyMappingsResult);
+
+ checkPolicyMappingRecordRemoved(
+ loadPolicyMappingsResult.getPolicyMappingRecords(), target, policy);
+
+ // also try load by specific type
+ LoadPolicyMappingsResult loadPolicyMappingsResultByType =
+ polarisMetaStoreManager.loadPoliciesOnEntityByType(
+ this.polarisCallContext, target, policy.getPolicyType());
+ validateLoadedPolicyMappings(loadPolicyMappingsResultByType);
+ checkPolicyMappingRecordRemoved(
+ loadPolicyMappingsResultByType.getPolicyMappingRecords(), target,
policy);
+ }
+
+ /**
+ * Validate the return of loadPoliciesOnEntity() or
LoadPoliciesOnEntityByType()
+ *
+ * @param loadPolicyMappingRecords return from calling
+ * loadPoliciesOnEntity()/LoadPoliciesOnEntityByType()
+ */
+ void validateLoadedPolicyMappings(LoadPolicyMappingsResult
loadPolicyMappingRecords) {
+ Assertions.assertThat(loadPolicyMappingRecords).isNotNull();
+
+ Map<Long, PolarisBaseEntity> policyEntities =
loadPolicyMappingRecords.getEntitiesAsMap();
+ Assertions.assertThat(policyEntities).isNotNull();
+
+ for (PolarisPolicyMappingRecord policyMappingRecord :
+ loadPolicyMappingRecords.getPolicyMappingRecords()) {
+ PolicyEntity entity =
+ PolicyEntity.of(
+ polarisMetaStoreManager
+ .loadEntity(
+ this.polarisCallContext,
+ policyMappingRecord.getPolicyCatalogId(),
+ policyMappingRecord.getPolicyId(),
+ PolarisEntityType.POLICY)
+ .getEntity());
+
+ Assertions.assertThat(entity).isNotNull();
+
Assertions.assertThat(policyEntities.get(entity.getId())).isEqualTo(entity);
+ }
+ }
+
+ /**
+ * Check that the policy mapping record exists
+ *
+ * @param policyMappingRecords list of policy mapping records
+ * @param target the target
+ * @param policy the policy
+ */
+ void checkPolicyMappingRecordExists(
+ List<PolarisPolicyMappingRecord> policyMappingRecords,
+ PolarisBaseEntity target,
+ PolicyEntity policy) {
+ boolean exists = isPolicyMappingRecordExists(policyMappingRecords, target,
policy);
+ Assertions.assertThat(exists).isTrue();
+ }
+
+ /**
+ * Check that the policy mapping record has been removed
+ *
+ * @param policyMappingRecords list of policy mapping records
+ * @param target the target
+ * @param policy the policy
+ */
+ void checkPolicyMappingRecordRemoved(
+ List<PolarisPolicyMappingRecord> policyMappingRecords,
+ PolarisBaseEntity target,
+ PolicyEntity policy) {
+ boolean exists = isPolicyMappingRecordExists(policyMappingRecords, target,
policy);
+ Assertions.assertThat(exists).isFalse();
+ }
+
+ /**
+ * Check if the policy mapping record exists
+ *
+ * @param policyMappingRecords list of policy mapping records
+ * @param target the target
+ * @param policy the policy
+ */
+ boolean isPolicyMappingRecordExists(
+ List<PolarisPolicyMappingRecord> policyMappingRecords,
+ PolarisBaseEntity target,
+ PolicyEntity policy) {
+ long policyMappingCount =
+ policyMappingRecords.stream()
+ .filter(
+ record ->
+ record.getPolicyCatalogId() == policy.getCatalogId()
+ && record.getPolicyId() == policy.getId()
+ && record.getTargetCatalogId() == target.getCatalogId()
+ && record.getTargetId() == target.getId()
+ && record.getPolicyTypeCode() ==
policy.getPolicyTypeCode())
+ .count();
+ return policyMappingCount == 1;
+ }
+
/**
* 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,
POL for a policy):
@@ -2443,4 +2635,62 @@ public class PolarisTestMetaStoreManager {
this.refreshCacheEntry(
1, 1, PolarisEntityType.TABLE_LIKE, N1.getCatalogId() + 1000,
N1.getId(), false);
}
+
+ void testPolicyMapping() {
+ PolarisBaseEntity catalog = this.createTestCatalog("test");
+ Assertions.assertThat(catalog).isNotNull();
+
+ PolarisBaseEntity N1 =
+ this.ensureExistsByName(List.of(catalog), PolarisEntityType.NAMESPACE,
"N1");
+ PolarisBaseEntity N1_N2 =
+ this.ensureExistsByName(List.of(catalog, N1),
PolarisEntityType.NAMESPACE, "N2");
+ PolarisBaseEntity N5 =
+ this.ensureExistsByName(List.of(catalog), PolarisEntityType.NAMESPACE,
"N5");
+
+ PolarisBaseEntity N1_N2_T1 =
+ this.ensureExistsByName(
+ List.of(catalog, N1, N1_N2),
+ PolarisEntityType.TABLE_LIKE,
+ PolarisEntitySubType.ANY_SUBTYPE,
+ "T1");
+
+ PolicyEntity N1_P1 =
+ this.createPolicy(List.of(catalog, N1), "P1",
PredefinedPolicyTypes.DATA_COMPACTION);
+
+ PolicyEntity N1_P2 =
+ this.createPolicy(List.of(catalog, N1), "P2",
PredefinedPolicyTypes.DATA_COMPACTION);
+
+ PolicyEntity N5_P3 =
+ this.createPolicy(List.of(catalog, N5), "P3",
PredefinedPolicyTypes.METADATA_COMPACTION);
+ attachPolicyToTarget(List.of(catalog, N1, N1_N2), N1_N2_T1,
List.of(catalog, N1), N1_P1);
+ // attach a different policy of different inheritable type to the same
target, should succeed
+ attachPolicyToTarget(List.of(catalog, N1, N1_N2), N1_N2_T1,
List.of(catalog, N5), N5_P3);
+
+ // attach a different policy of same inheritable type to the same target,
should fail
+ PolicyAttachmentResult policyAttachmentResult =
+ polarisMetaStoreManager.attachPolicyToEntity(
+ polarisCallContext,
+ List.of(catalog, N1, N1_N2),
+ N1_N2_T1,
+ List.of(catalog, N1),
+ N1_P2,
+ null);
+
+ Assertions.assertThat(policyAttachmentResult.isSuccess()).isFalse();
+ Assertions.assertThat(policyAttachmentResult.getReturnStatus())
+
.isEqualTo(BaseResult.ReturnStatus.POLICY_MAPPING_OF_SAME_TYPE_ALREADY_EXISTS);
+
+ LoadPolicyMappingsResult loadPolicyMappingsResult =
+ polarisMetaStoreManager.loadPoliciesOnEntityByType(
+ polarisCallContext, N1_N2_T1,
PredefinedPolicyTypes.DATA_COMPACTION);
+ Assertions.assertThat(loadPolicyMappingsResult.isSuccess()).isTrue();
+ Assertions.assertThat(loadPolicyMappingsResult.getEntities()).hasSize(1);
+ PolicyEntity policyEntity =
PolicyEntity.of(loadPolicyMappingsResult.getEntities().get(0));
+ Assertions.assertThat(policyEntity.getId()).isEqualTo(N1_P1.getId());
+ Assertions.assertThat(policyEntity.getPolicyType())
+ .isEqualTo(PredefinedPolicyTypes.DATA_COMPACTION);
+
+ detachPolicyFromTarget(List.of(catalog, N1, N1_N2), N1_N2_T1,
List.of(catalog, N1), N1_P1);
+ detachPolicyFromTarget(List.of(catalog, N1, N1_N2), N1_N2_T1,
List.of(catalog, N5), N5_P3);
+ }
}