Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.TableIdentifier;
Expand All @@ -51,6 +52,7 @@
import org.apache.polaris.core.admin.model.Catalog;
import org.apache.polaris.core.admin.model.CatalogGrant;
import org.apache.polaris.core.admin.model.CatalogPrivilege;
import org.apache.polaris.core.admin.model.CatalogRole;
import org.apache.polaris.core.admin.model.ConnectionConfigInfo;
import org.apache.polaris.core.admin.model.CreateCatalogRequest;
import org.apache.polaris.core.admin.model.ExternalCatalog;
Expand All @@ -60,6 +62,8 @@
import org.apache.polaris.core.admin.model.OAuthClientCredentialsParameters;
import org.apache.polaris.core.admin.model.PolicyGrant;
import org.apache.polaris.core.admin.model.PolicyPrivilege;
import org.apache.polaris.core.admin.model.Principal;
import org.apache.polaris.core.admin.model.PrincipalRole;
import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
import org.apache.polaris.core.admin.model.PrincipalWithCredentialsCredentials;
import org.apache.polaris.core.admin.model.TableGrant;
Expand All @@ -82,6 +86,7 @@
import org.apache.polaris.core.entity.NamespaceEntity;
import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.entity.PolarisEntity;
import org.apache.polaris.core.entity.PolarisEntityCore;
import org.apache.polaris.core.entity.PolarisEntitySubType;
import org.apache.polaris.core.entity.PolarisEntityType;
import org.apache.polaris.core.entity.PolarisGrantRecord;
Expand Down Expand Up @@ -200,6 +205,43 @@ private Optional<CatalogRoleEntity> findCatalogRoleByName(String catalogName, St
.map(path -> CatalogRoleEntity.of(path.getRawLeafEntity()));
}

private <T> Stream<T> loadEntities(
@Nonnull PolarisEntityType entityType,
@Nonnull PolarisEntitySubType entitySubType,
@Nullable PolarisEntity catalogEntity,
@Nonnull Function<PolarisBaseEntity, T> transformer) {
List<PolarisEntityCore> catalogPath;
long catalogId;
if (catalogEntity == null) {
catalogPath = null;
catalogId = 0;
} else {
catalogPath = PolarisEntity.toCoreList(List.of(catalogEntity));
catalogId = catalogEntity.getId();
}
// TODO: add loadEntities method to PolarisMetaStoreManager
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: We will need a discussion on how persistence layer is going to support it, as it affects all types of persistence. Loading everything in one call can provide a consistent view, which is nice, but there are some caveats that the uber call may be too large, so that it hits the limits(e.g., memory limit). With that, I think it's premature to consider this as a TODO item.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaict under the hood listEntities is already fetching the fully fledged PolarisBaseEntity instances from the database (for the jdbc case):

datasourceOperations.executeSelectOverStream(
query,
new ModelEntity(),
stream -> {
var data = stream.filter(entityFilter);
results.set(Page.mapped(pageToken, data, transformer, EntityIdToken::fromEntity));
});

it includes streaming/pagination.

it just happens that the given transformer turns PolarisBaseEntity into EntityNameLookupRecord... and then later the record is used to look up the full entity again.
so afaict, the memory footprint would not be very different if we had a loadEntities function (or changed listEntities to not only return a EntityNameLookupRecord).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so afaict, the memory footprint would not be very different if we had a loadEntities function

I agree. I also think the overall load is lower.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, I'm not against the idea, but we will need a discussion.

// loadEntity may return null due to multiple non-atomic API calls to the persistence layer.
// Specifically, this can happen when a PolarisEntity is returned by listEntities, but cannot be
// loaded afterward because it was purged by another process before it could be loaded.
return metaStoreManager
.listEntities(
getCurrentPolarisContext(),
catalogPath,
entityType,
entitySubType,
PageToken.readEverything())
.getEntities()
.stream()
.map(
nameAndId ->
metaStoreManager.loadEntity(
getCurrentPolarisContext(), catalogId, nameAndId.getId(), nameAndId.getType()))
.map(PolarisEntity::of)
.filter(Objects::nonNull)
.map(transformer)
.filter(Objects::nonNull);
}

private void authorizeBasicRootOperationOrThrow(PolarisAuthorizableOperation op) {
resolutionManifest =
resolutionManifestFactory.createResolutionManifest(
Expand Down Expand Up @@ -618,8 +660,7 @@ private boolean catalogOverlapsWithExistingCatalog(CatalogEntity catalogEntity)
}

Set<String> newCatalogLocations = getCatalogLocations(catalogEntity);
return listCatalogsUnsafe().stream()
.map(CatalogEntity::new)
return listCatalogsUnsafe()
.anyMatch(
existingCatalog -> {
if (existingCatalog.getName().equals(catalogEntity.getName())) {
Expand Down Expand Up @@ -944,40 +985,16 @@ private void validateUpdateCatalogDiffOrThrow(
return returnedEntity;
}

/**
* List all catalogs after checking for permission. Nulls due to non-atomic list-then-get are
* filtered out.
*/
public List<PolarisEntity> listCatalogs() {
/** List all catalogs after checking for permission. */
public List<Catalog> listCatalogs() {
authorizeBasicRootOperationOrThrow(PolarisAuthorizableOperation.LIST_CATALOGS);
return listCatalogsUnsafe();
return listCatalogsUnsafe().map(CatalogEntity::asCatalog).toList();
}

/**
* List all catalogs without checking for permission. Nulls due to non-atomic list-then-get are
* filtered out.
*/
private List<PolarisEntity> listCatalogsUnsafe() {
// loadEntity may return null due to multiple non-atomic
// API calls to the persistence layer. Specifically, this can happen when a PolarisEntity is
// returned by listCatalogs, but cannot be loaded afterward because it was purged by another
// process before it could be loaded.
return metaStoreManager
.listEntities(
getCurrentPolarisContext(),
null,
PolarisEntityType.CATALOG,
PolarisEntitySubType.ANY_SUBTYPE,
PageToken.readEverything())
.getEntities()
.stream()
.map(
nameAndId ->
PolarisEntity.of(
metaStoreManager.loadEntity(
getCurrentPolarisContext(), 0, nameAndId.getId(), nameAndId.getType())))
.filter(Objects::nonNull)
.toList();
/** List all catalogs without checking for permission. */
private Stream<CatalogEntity> listCatalogsUnsafe() {
return loadEntities(
PolarisEntityType.CATALOG, PolarisEntitySubType.ANY_SUBTYPE, null, CatalogEntity::of);
}

public PrincipalWithCredentials createPrincipal(PolarisEntity entity) {
Expand Down Expand Up @@ -1141,24 +1158,16 @@ public void deletePrincipal(String name) {
return rotateOrResetCredentialsHelper(principalName, true);
}

public List<PolarisEntity> listPrincipals() {
public List<Principal> listPrincipals() {
PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LIST_PRINCIPALS;
authorizeBasicRootOperationOrThrow(op);

return metaStoreManager
.listEntities(
getCurrentPolarisContext(),
null,
return loadEntities(
PolarisEntityType.PRINCIPAL,
PolarisEntitySubType.NULL_SUBTYPE,
PageToken.readEverything())
.getEntities()
.stream()
.map(
nameAndId ->
PolarisEntity.of(
metaStoreManager.loadEntity(
getCurrentPolarisContext(), 0, nameAndId.getId(), nameAndId.getType())))
null,
PrincipalEntity::of)
.map(PrincipalEntity::asPrincipal)
.toList();
}

Expand Down Expand Up @@ -1254,24 +1263,16 @@ public void deletePrincipalRole(String name) {
return returnedEntity;
}

public List<PolarisEntity> listPrincipalRoles() {
public List<PrincipalRole> listPrincipalRoles() {
PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LIST_PRINCIPAL_ROLES;
authorizeBasicRootOperationOrThrow(op);

return metaStoreManager
.listEntities(
getCurrentPolarisContext(),
null,
return loadEntities(
PolarisEntityType.PRINCIPAL_ROLE,
PolarisEntitySubType.NULL_SUBTYPE,
PageToken.readEverything())
.getEntities()
.stream()
.map(
nameAndId ->
PolarisEntity.of(
metaStoreManager.loadEntity(
getCurrentPolarisContext(), 0, nameAndId.getId(), nameAndId.getType())))
null,
PrincipalRoleEntity::of)
.map(PrincipalRoleEntity::asPrincipalRole)
.toList();
}

Expand Down Expand Up @@ -1383,30 +1384,19 @@ public void deleteCatalogRole(String catalogName, String name) {
return returnedEntity;
}

public List<PolarisEntity> listCatalogRoles(String catalogName) {
public List<CatalogRole> listCatalogRoles(String catalogName) {
PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LIST_CATALOG_ROLES;
authorizeBasicTopLevelEntityOperationOrThrow(op, catalogName, PolarisEntityType.CATALOG);

PolarisEntity catalogEntity =
findCatalogByName(catalogName)
.orElseThrow(() -> new NotFoundException("Parent catalog %s not found", catalogName));
return metaStoreManager
.listEntities(
getCurrentPolarisContext(),
PolarisEntity.toCoreList(List.of(catalogEntity)),
return loadEntities(
PolarisEntityType.CATALOG_ROLE,
PolarisEntitySubType.NULL_SUBTYPE,
PageToken.readEverything())
.getEntities()
.stream()
.map(
nameAndId ->
PolarisEntity.of(
metaStoreManager.loadEntity(
getCurrentPolarisContext(),
catalogEntity.getId(),
nameAndId.getId(),
nameAndId.getType())))
catalogEntity,
CatalogRoleEntity::of)
.map(CatalogRoleEntity::asCatalogRole)
.toList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,7 @@ public Response updateCatalog(
@Override
public Response listCatalogs(RealmContext realmContext, SecurityContext securityContext) {
PolarisAdminService adminService = newAdminService(realmContext, securityContext);
List<Catalog> catalogList =
adminService.listCatalogs().stream()
.map(CatalogEntity::new)
.map(CatalogEntity::asCatalog)
.toList();
List<Catalog> catalogList = adminService.listCatalogs();
Catalogs catalogs = new Catalogs(catalogList);
LOGGER.debug("listCatalogs returning: {}", catalogs);
return Response.ok(catalogs).build();
Expand Down Expand Up @@ -311,11 +307,7 @@ public Response rotateCredentials(
@Override
public Response listPrincipals(RealmContext realmContext, SecurityContext securityContext) {
PolarisAdminService adminService = newAdminService(realmContext, securityContext);
List<Principal> principalList =
adminService.listPrincipals().stream()
.map(PrincipalEntity::new)
.map(PrincipalEntity::asPrincipal)
.toList();
List<Principal> principalList = adminService.listPrincipals();
Principals principals = new Principals(principalList);
LOGGER.debug("listPrincipals returning: {}", principals);
return Response.ok(principals).build();
Expand Down Expand Up @@ -375,11 +367,7 @@ public Response updatePrincipalRole(
@Override
public Response listPrincipalRoles(RealmContext realmContext, SecurityContext securityContext) {
PolarisAdminService adminService = newAdminService(realmContext, securityContext);
List<PrincipalRole> principalRoleList =
adminService.listPrincipalRoles().stream()
.map(PrincipalRoleEntity::new)
.map(PrincipalRoleEntity::asPrincipalRole)
.toList();
List<PrincipalRole> principalRoleList = adminService.listPrincipalRoles();
PrincipalRoles principalRoles = new PrincipalRoles(principalRoleList);
LOGGER.debug("listPrincipalRoles returning: {}", principalRoles);
return Response.ok(principalRoles).build();
Expand Down Expand Up @@ -451,11 +439,7 @@ public Response updateCatalogRole(
public Response listCatalogRoles(
String catalogName, RealmContext realmContext, SecurityContext securityContext) {
PolarisAdminService adminService = newAdminService(realmContext, securityContext);
List<CatalogRole> catalogRoleList =
adminService.listCatalogRoles(catalogName).stream()
.map(CatalogRoleEntity::new)
.map(CatalogRoleEntity::asCatalogRole)
.toList();
List<CatalogRole> catalogRoleList = adminService.listCatalogRoles(catalogName);
CatalogRoles catalogRoles = new CatalogRoles(catalogRoleList);
LOGGER.debug("listCatalogRoles returning: {}", catalogRoles);
return Response.ok(catalogRoles).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
import org.apache.polaris.core.auth.PolarisAuthorizerImpl;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.entity.PolarisEntity;
import org.apache.polaris.core.entity.PolarisEntityConstants;
import org.apache.polaris.core.entity.PolarisEntitySubType;
import org.apache.polaris.core.entity.PolarisEntityType;
Expand Down Expand Up @@ -310,8 +309,8 @@ public void testCatalogNotReturnedWhenDeletedAfterListBeforeGet() {
.when(metaStoreManager)
.loadEntity(Mockito.any(), Mockito.anyLong(), Mockito.anyLong(), Mockito.any());

List<PolarisEntity> catalogs = polarisAdminService.listCatalogs();
List<Catalog> catalogs = polarisAdminService.listCatalogs();
assertThat(catalogs.size()).isEqualTo(1);
assertThat(catalogs.getFirst().getId()).isEqualTo(catalog2.getCatalog().getId());
assertThat(catalogs.getFirst().getName()).isEqualTo(catalog2.getCatalog().getName());
}
}