Skip to content

Commit 0e61514

Browse files
authored
Add loadEntities batch call and rename listFullEntities (#2508)
* Add loadEntities batch call and rename listFullEntities * Changed batch call to implement loadResolvedEntities instead * Add loadResolvedEntities by id and entity cache support * Add additional test for loadResolvedEntities by id * Added additional test and updated comments in EntityCache interface * Add additional constructor to ResolvedEntitiesResult * Fixed unused method reference * Removed loadResolvedEntities method with lookup record param * Pulled out toResolvedPolarisEntity method per PR comment
1 parent 6546689 commit 0e61514

File tree

16 files changed

+1222
-161
lines changed

16 files changed

+1222
-161
lines changed

persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.LinkedHashMap;
3232
import java.util.List;
3333
import java.util.Map;
34+
import java.util.Objects;
3435
import java.util.Optional;
3536
import java.util.concurrent.atomic.AtomicReference;
3637
import java.util.function.Function;
@@ -463,7 +464,12 @@ public List<PolarisBaseEntity> lookupEntities(
463464
PreparedQuery query =
464465
QueryGenerator.generateSelectQueryWithEntityIds(realmId, schemaVersion, entityIds);
465466
try {
466-
return datasourceOperations.executeSelect(query, new ModelEntity(schemaVersion));
467+
Map<PolarisEntityId, PolarisBaseEntity> idMap =
468+
datasourceOperations.executeSelect(query, new ModelEntity(schemaVersion)).stream()
469+
.collect(
470+
Collectors.toMap(
471+
e -> new PolarisEntityId(e.getCatalogId(), e.getId()), Function.identity()));
472+
return entityIds.stream().map(idMap::get).collect(Collectors.toList());
467473
} catch (SQLException e) {
468474
throw new RuntimeException(
469475
String.format("Failed to retrieve polaris entities due to %s", e.getMessage()), e);
@@ -476,6 +482,7 @@ public List<PolarisChangeTrackingVersions> lookupEntityVersions(
476482
@Nonnull PolarisCallContext callCtx, List<PolarisEntityId> entityIds) {
477483
Map<PolarisEntityId, ModelEntity> idToEntityMap =
478484
lookupEntities(callCtx, entityIds).stream()
485+
.filter(Objects::nonNull)
479486
.collect(
480487
Collectors.toMap(
481488
entry -> new PolarisEntityId(entry.getCatalogId(), entry.getId()),
@@ -570,7 +577,7 @@ public Page<EntityNameLookupRecord> listEntities(
570577

571578
@Nonnull
572579
@Override
573-
public <T> Page<T> loadEntities(
580+
public <T> Page<T> listFullEntities(
574581
@Nonnull PolarisCallContext callCtx,
575582
long catalogId,
576583
long parentId,

polaris-core/src/main/java/org/apache/polaris/core/persistence/AtomicOperationMetaStoreManager.java

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.concurrent.atomic.AtomicInteger;
3333
import java.util.function.Function;
3434
import java.util.stream.Collectors;
35+
import java.util.stream.IntStream;
3536
import org.apache.polaris.core.PolarisCallContext;
3637
import org.apache.polaris.core.PolarisDiagnostics;
3738
import org.apache.polaris.core.config.FeatureConfiguration;
@@ -67,6 +68,7 @@
6768
import org.apache.polaris.core.persistence.dao.entity.PolicyAttachmentResult;
6869
import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult;
6970
import org.apache.polaris.core.persistence.dao.entity.PrivilegeResult;
71+
import org.apache.polaris.core.persistence.dao.entity.ResolvedEntitiesResult;
7072
import org.apache.polaris.core.persistence.dao.entity.ResolvedEntityResult;
7173
import org.apache.polaris.core.persistence.dao.entity.ScopedCredentialsResult;
7274
import org.apache.polaris.core.persistence.pagination.Page;
@@ -705,7 +707,7 @@ private void revokeGrantRecord(
705707

706708
/** {@inheritDoc} */
707709
@Override
708-
public @Nonnull Page<PolarisBaseEntity> loadEntities(
710+
public @Nonnull Page<PolarisBaseEntity> listFullEntities(
709711
@Nonnull PolarisCallContext callCtx,
710712
@Nullable List<PolarisEntityCore> catalogPath,
711713
@Nonnull PolarisEntityType entityType,
@@ -728,7 +730,7 @@ private void revokeGrantRecord(
728730
// with sensitive data; but the window of inconsistency is only the duration of a single
729731
// in-flight request (the cache-based resolution follows a different path entirely).
730732

731-
return ms.loadEntities(
733+
return ms.listFullEntities(
732734
callCtx,
733735
catalogId,
734736
parentId,
@@ -1200,7 +1202,7 @@ private void revokeGrantRecord(
12001202

12011203
// get the list of catalog roles, at most 2
12021204
List<PolarisBaseEntity> catalogRoles =
1203-
ms.loadEntities(
1205+
ms.listFullEntities(
12041206
callCtx,
12051207
catalogId,
12061208
catalogId,
@@ -1520,7 +1522,7 @@ private void revokeGrantRecord(
15201522

15211523
// find all available tasks
15221524
Page<PolarisBaseEntity> availableTasks =
1523-
ms.loadEntities(
1525+
ms.listFullEntities(
15241526
callCtx,
15251527
PolarisEntityConstants.getRootEntityId(),
15261528
PolarisEntityConstants.getRootEntityId(),
@@ -1760,6 +1762,56 @@ private void revokeGrantRecord(
17601762
return result;
17611763
}
17621764

1765+
@Nonnull
1766+
@Override
1767+
public ResolvedEntitiesResult loadResolvedEntities(
1768+
@Nonnull PolarisCallContext callCtx,
1769+
@Nonnull PolarisEntityType entityType,
1770+
@Nonnull List<PolarisEntityId> entityIds) {
1771+
BasePersistence ms = callCtx.getMetaStore();
1772+
return getResolvedEntitiesResult(callCtx, ms, entityIds, i -> entityType);
1773+
}
1774+
1775+
private static ResolvedEntitiesResult getResolvedEntitiesResult(
1776+
PolarisCallContext callCtx,
1777+
BasePersistence ms,
1778+
List<PolarisEntityId> entityIds,
1779+
Function<Integer, PolarisEntityType> entityTypeForIndex) {
1780+
List<PolarisBaseEntity> entities = ms.lookupEntities(callCtx, entityIds);
1781+
// mimic the behavior of loadEntity above, return null if not found or type mismatch
1782+
List<ResolvedPolarisEntity> ret =
1783+
IntStream.range(0, entityIds.size())
1784+
.mapToObj(
1785+
i -> {
1786+
if (entities.get(i) != null
1787+
&& !entities.get(i).getType().equals(entityTypeForIndex.apply(i))) {
1788+
return null;
1789+
} else {
1790+
return entities.get(i);
1791+
}
1792+
})
1793+
.map(e -> toResolvedPolarisEntity(callCtx, e, ms))
1794+
.collect(Collectors.toList());
1795+
return new ResolvedEntitiesResult(ret);
1796+
}
1797+
1798+
private static ResolvedPolarisEntity toResolvedPolarisEntity(
1799+
PolarisCallContext callCtx, PolarisBaseEntity e, BasePersistence ms) {
1800+
if (e == null) {
1801+
return null;
1802+
} else {
1803+
// load the grant records
1804+
final List<PolarisGrantRecord> grantRecordsAsSecurable =
1805+
ms.loadAllGrantRecordsOnSecurable(callCtx, e.getCatalogId(), e.getId());
1806+
final List<PolarisGrantRecord> grantRecordsAsGrantee =
1807+
e.getType().isGrantee()
1808+
? ms.loadAllGrantRecordsOnGrantee(callCtx, e.getCatalogId(), e.getId())
1809+
: List.of();
1810+
return new ResolvedPolarisEntity(
1811+
PolarisEntity.of(e), grantRecordsAsGrantee, grantRecordsAsSecurable);
1812+
}
1813+
}
1814+
17631815
/** {@inheritDoc} */
17641816
@Override
17651817
public @Nonnull ResolvedEntityResult refreshResolvedEntity(

polaris-core/src/main/java/org/apache/polaris/core/persistence/BasePersistence.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ List<PolarisChangeTrackingVersions> lookupEntityVersions(
279279

280280
/**
281281
* List lightweight information of entities matching the given criteria with pagination. If all
282-
* properties of the entity are required,use {@link #loadEntities} instead.
282+
* properties of the entity are required,use {@link #listFullEntities} instead.
283283
*
284284
* @param callCtx call context
285285
* @param catalogId catalog id for that entity, NULL_ID if the entity is top-level
@@ -314,7 +314,7 @@ Page<EntityNameLookupRecord> listEntities(
314314
* @return the paged list of matching entities after transformation
315315
*/
316316
@Nonnull
317-
<T> Page<T> loadEntities(
317+
<T> Page<T> listFullEntities(
318318
@Nonnull PolarisCallContext callCtx,
319319
long catalogId,
320320
long parentId,

polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManager.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.apache.polaris.core.persistence.dao.entity.EntityWithPath;
4848
import org.apache.polaris.core.persistence.dao.entity.GenerateEntityIdResult;
4949
import org.apache.polaris.core.persistence.dao.entity.ListEntitiesResult;
50+
import org.apache.polaris.core.persistence.dao.entity.ResolvedEntitiesResult;
5051
import org.apache.polaris.core.persistence.dao.entity.ResolvedEntityResult;
5152
import org.apache.polaris.core.persistence.pagination.Page;
5253
import org.apache.polaris.core.persistence.pagination.PageToken;
@@ -114,7 +115,7 @@ EntityResult readEntityByName(
114115

115116
/**
116117
* List lightweight information about entities matching the given criteria. If all properties of
117-
* the entity are required,use {@link #loadEntities} instead.
118+
* the entity are required,use {@link #listFullEntities} instead.
118119
*
119120
* @param callCtx call context
120121
* @param catalogPath path inside a catalog. If null or empty, the entities to list are top-level,
@@ -135,7 +136,7 @@ ListEntitiesResult listEntities(
135136
/**
136137
* Load full entities matching the given criteria with pagination. If only the entity name/id/type
137138
* is required, use {@link #listEntities} instead. If no pagination is required, use {@link
138-
* #loadEntitiesAll} instead.
139+
* #listFullEntitiesAll} instead.
139140
*
140141
* @param callCtx call context
141142
* @param catalogPath path inside a catalog. If null or empty, the entities to list are top-level,
@@ -145,7 +146,7 @@ ListEntitiesResult listEntities(
145146
* @return paged list of matching entities
146147
*/
147148
@Nonnull
148-
Page<PolarisBaseEntity> loadEntities(
149+
Page<PolarisBaseEntity> listFullEntities(
149150
@Nonnull PolarisCallContext callCtx,
150151
@Nullable List<PolarisEntityCore> catalogPath,
151152
@Nonnull PolarisEntityType entityType,
@@ -154,7 +155,7 @@ Page<PolarisBaseEntity> loadEntities(
154155

155156
/**
156157
* Load full entities matching the given criteria into an unpaged list. If pagination is required
157-
* use {@link #loadEntities} instead. If only the entity name/id/type is required, use {@link
158+
* use {@link #listFullEntities} instead. If only the entity name/id/type is required, use {@link
158159
* #listEntities} instead.
159160
*
160161
* @param callCtx call context
@@ -164,12 +165,13 @@ Page<PolarisBaseEntity> loadEntities(
164165
* @param entitySubType subType of entities to list (or ANY_SUBTYPE)
165166
* @return list of all matching entities
166167
*/
167-
default @Nonnull List<PolarisBaseEntity> loadEntitiesAll(
168+
default @Nonnull List<PolarisBaseEntity> listFullEntitiesAll(
168169
@Nonnull PolarisCallContext callCtx,
169170
@Nullable List<PolarisEntityCore> catalogPath,
170171
@Nonnull PolarisEntityType entityType,
171172
@Nonnull PolarisEntitySubType entitySubType) {
172-
return loadEntities(callCtx, catalogPath, entityType, entitySubType, PageToken.readEverything())
173+
return listFullEntities(
174+
callCtx, catalogPath, entityType, entitySubType, PageToken.readEverything())
173175
.items();
174176
}
175177

@@ -416,6 +418,23 @@ ResolvedEntityResult loadResolvedEntityByName(
416418
@Nonnull PolarisEntityType entityType,
417419
@Nonnull String entityName);
418420

421+
/**
422+
* Load a batch of resolved entities of a specified entity type given their {@link
423+
* PolarisEntityId}. Will return an empty list if the input list is empty. Order in that returned
424+
* list is the same as the input list. Some elements might be NULL if the entity has been dropped.
425+
*
426+
* @param callCtx call context
427+
* @param entityType the type of entities to load
428+
* @param entityIds the list of entity ids to load
429+
* @return a non-null list of entities corresponding to the lookup keys. Some elements might be
430+
* NULL if the entity has been dropped.
431+
*/
432+
@Nonnull
433+
ResolvedEntitiesResult loadResolvedEntities(
434+
@Nonnull PolarisCallContext callCtx,
435+
@Nonnull PolarisEntityType entityType,
436+
@Nonnull List<PolarisEntityId> entityIds);
437+
419438
/**
420439
* Refresh a resolved entity from the backend store. Will return NULL if the entity does not
421440
* exist, i.e. has been purged or dropped. Else, will determine what has changed based on the

polaris-core/src/main/java/org/apache/polaris/core/persistence/TransactionWorkspaceMetaStoreManager.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.apache.polaris.core.persistence.dao.entity.PolicyAttachmentResult;
5454
import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult;
5555
import org.apache.polaris.core.persistence.dao.entity.PrivilegeResult;
56+
import org.apache.polaris.core.persistence.dao.entity.ResolvedEntitiesResult;
5657
import org.apache.polaris.core.persistence.dao.entity.ResolvedEntityResult;
5758
import org.apache.polaris.core.persistence.dao.entity.ScopedCredentialsResult;
5859
import org.apache.polaris.core.persistence.pagination.Page;
@@ -133,7 +134,7 @@ public EntityResult readEntityByName(
133134
}
134135

135136
@Override
136-
public @Nonnull Page<PolarisBaseEntity> loadEntities(
137+
public @Nonnull Page<PolarisBaseEntity> listFullEntities(
137138
@Nonnull PolarisCallContext callCtx,
138139
@Nullable List<PolarisEntityCore> catalogPath,
139140
@Nonnull PolarisEntityType entityType,
@@ -379,6 +380,16 @@ public ResolvedEntityResult loadResolvedEntityByName(
379380
return null;
380381
}
381382

383+
@Nonnull
384+
@Override
385+
public ResolvedEntitiesResult loadResolvedEntities(
386+
@Nonnull PolarisCallContext callCtx,
387+
@Nonnull PolarisEntityType entityType,
388+
@Nonnull List<PolarisEntityId> entityIds) {
389+
diagnostics.fail("illegal_method_in_transaction_workspace", "loadResolvedEntities");
390+
return null;
391+
}
392+
382393
@Override
383394
public ResolvedEntityResult refreshResolvedEntity(
384395
@Nonnull PolarisCallContext callCtx,

polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCache.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020

2121
import jakarta.annotation.Nonnull;
2222
import jakarta.annotation.Nullable;
23+
import java.util.List;
2324
import org.apache.polaris.core.PolarisCallContext;
2425
import org.apache.polaris.core.entity.PolarisBaseEntity;
26+
import org.apache.polaris.core.entity.PolarisEntityId;
2527
import org.apache.polaris.core.entity.PolarisEntityType;
2628
import org.apache.polaris.core.persistence.ResolvedPolarisEntity;
2729

@@ -80,4 +82,32 @@ EntityCacheLookupResult getOrLoadEntityById(
8082
@Nullable
8183
EntityCacheLookupResult getOrLoadEntityByName(
8284
@Nonnull PolarisCallContext callContext, @Nonnull EntityCacheByNameKey entityNameKey);
85+
86+
/**
87+
* Load multiple entities by id, returning those found in the cache and loading those not found.
88+
*
89+
* <p>Cached entity versions and grant versions must be verified against the versions returned by
90+
* the {@link
91+
* org.apache.polaris.core.persistence.PolarisMetaStoreManager#loadEntitiesChangeTracking(PolarisCallContext,
92+
* List)} API to ensure the returned entities are consistent with the current state of the
93+
* metastore. Cache implementations must never return a mix of stale entities and fresh entities,
94+
* as authorization or table conflict decisions could be made based on inconsistent data. For
95+
* example, a Principal may have a grant to a Principal Role in a cached entry, but that grant may
96+
* be revoked prior to the Principal Role being granted a privilege on a Catalog. If the Principal
97+
* record is stale, but the Principal Role is refreshed, the Principal may be incorrectly
98+
* authorized to access the Catalog.
99+
*
100+
* @param callCtx the Polaris call context
101+
* @param entityType the entity type
102+
* @param entityIds the list of entity ids to load
103+
* @return the list of resolved entities, in the same order as the requested entity ids. As in
104+
* {@link
105+
* org.apache.polaris.core.persistence.PolarisMetaStoreManager#loadResolvedEntities(PolarisCallContext,
106+
* PolarisEntityType, List)}, elements in the returned list may be null if the corresponding
107+
* entity id does not exist.
108+
*/
109+
List<EntityCacheLookupResult> getOrLoadResolvedEntities(
110+
@Nonnull PolarisCallContext callCtx,
111+
@Nonnull PolarisEntityType entityType,
112+
@Nonnull List<PolarisEntityId> entityIds);
83113
}

0 commit comments

Comments
 (0)