Skip to content

Commit

Permalink
Hide entity resolution from service implementations
Browse files Browse the repository at this point in the history
Currently all service implementations, both the Polaris admin service and the (Iceberg REST) catalog service need knowledge about the implementation details about _how exactly_ entities and their instances are managed. This is rather a persistence implementation concern, not a service implementation concern. Services should tell, if necessary, tell a properly abstracted builder which entities it will need.

This change abstracts the entity resolution via interfaces and concrete implementation classes, eliminating the hard dependency of services to concrete persistence specific implementations.
  • Loading branch information
snazy committed Dec 10, 2024
1 parent 426f5eb commit f92cee9
Show file tree
Hide file tree
Showing 19 changed files with 805 additions and 509 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ public enum PolarisEntitySubType {
// ANY_SUBTYPE is not stored but is used to indicate that any subtype entities should be
// returned, for example when doing a list operation or checking if a table like object of
// name X exists
ANY_SUBTYPE(-1, null),
ANY_SUBTYPE(-1, null, "Table or view"),
// the NULL value is used when an entity has no subtype, i.e. NOT_APPLICABLE really
NULL_SUBTYPE(0, null),
TABLE(2, PolarisEntityType.TABLE_LIKE),
VIEW(3, PolarisEntityType.TABLE_LIKE);
NULL_SUBTYPE(0, null, "(null)"),
TABLE(2, PolarisEntityType.TABLE_LIKE, "Table"),
VIEW(3, PolarisEntityType.TABLE_LIKE, "View");

// to efficiently map the code of a subtype to its corresponding subtype enum, use a reverse
// array which is initialized below
Expand Down Expand Up @@ -63,10 +63,13 @@ public enum PolarisEntitySubType {
// parent type for this entity
private final PolarisEntityType parentType;

PolarisEntitySubType(int code, PolarisEntityType parentType) {
private final String readableName;

PolarisEntitySubType(int code, PolarisEntityType parentType, String readableName) {
// remember the id of this entity
this.code = code;
this.parentType = parentType;
this.readableName = readableName;
}

/**
Expand Down Expand Up @@ -111,4 +114,8 @@ public PolarisEntityType getParentType() {

return null;
}

public String readableName() {
return readableName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@

/** Types of entities with their id */
public enum PolarisEntityType {
NULL_TYPE(0, null, false, false),
ROOT(1, null, false, false),
PRINCIPAL(2, ROOT, true, false),
PRINCIPAL_ROLE(3, ROOT, true, false),
CATALOG(4, ROOT, false, false),
CATALOG_ROLE(5, CATALOG, true, false),
NAMESPACE(6, CATALOG, false, true),
NULL_TYPE(0, null, false, false, "(null)"),
ROOT(1, null, false, false, "Root"),
PRINCIPAL(2, ROOT, true, false, "Principal"),
PRINCIPAL_ROLE(3, ROOT, true, false, "Principal role"),
CATALOG(4, ROOT, false, false, "Catalog"),
CATALOG_ROLE(5, CATALOG, true, false, "Catalog role"),
NAMESPACE(6, CATALOG, false, true, "Namespace"),
// generic table is either a view or a real table
TABLE_LIKE(7, NAMESPACE, false, false),
TASK(8, ROOT, false, false),
FILE(9, TABLE_LIKE, false, false);
TABLE_LIKE(7, NAMESPACE, false, false, "Table/view"),
TASK(8, ROOT, false, false, "Task"),
FILE(9, TABLE_LIKE, false, false, "File");

// to efficiently map a code to its corresponding entity type, use a reverse array which
// is initialized below
Expand Down Expand Up @@ -70,13 +70,20 @@ public enum PolarisEntityType {

// parent entity type, null for an ACCOUNT
private final PolarisEntityType parentType;

PolarisEntityType(int id, PolarisEntityType parentType, boolean isGrantee, boolean sefRef) {
private final String readableName;

PolarisEntityType(
int id,
PolarisEntityType parentType,
boolean isGrantee,
boolean sefRef,
String readableName) {
// remember the id of this entity
this.code = id;
this.isGrantee = isGrantee;
this.parentType = parentType;
this.parentSelfReference = sefRef;
this.readableName = readableName;
}

/**
Expand Down Expand Up @@ -132,4 +139,8 @@ public boolean isTopLevel() {
public PolarisEntityType getParentType() {
return this.parentType;
}

public String readableName() {
return readableName;
}
}
Original file line number Diff line number Diff line change
@@ -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;

import java.util.Optional;
import org.apache.iceberg.exceptions.NoSuchNamespaceException;
import org.apache.iceberg.exceptions.NoSuchTableException;
import org.apache.iceberg.exceptions.NoSuchViewException;
import org.apache.iceberg.exceptions.NotFoundException;
import org.apache.polaris.core.entity.PolarisEntitySubType;
import org.apache.polaris.core.entity.PolarisEntityType;

public class EntityNotFoundException extends RuntimeException {
private final PolarisEntityType entityType;
private final Optional<PolarisEntitySubType> subType;
private final String name;

public EntityNotFoundException(
PolarisEntityType entityType, Optional<PolarisEntitySubType> subType, String name) {
super(
subType.map(PolarisEntitySubType::readableName).orElseGet(entityType::readableName)
+ " "
+ name);
this.entityType = entityType;
this.subType = subType;
this.name = name;
}

public EntityNotFoundException(PolarisEntityType entityType, String name) {
this(entityType, Optional.empty(), name);
}

public RuntimeException asGenericIcebergNotFoundException() {
throw new NotFoundException(
"%s does not exist: %s",
subType.map(PolarisEntitySubType::readableName).orElseGet(entityType::readableName), name);
}

public RuntimeException asSpecializedIcebergNotFoundException() {
switch (entityType) {
case NAMESPACE:
return new NoSuchNamespaceException("Namespace does not exist: %s", name);
case TABLE_LIKE:
return subType
.map(
sub -> {
switch (sub) {
case TABLE:
return new NoSuchTableException("Table does not exist: %s", name);
case VIEW:
return new NoSuchViewException("View does not exist: %s", name);
default:
return asGenericIcebergNotFoundException();
}
})
.orElseGet(this::asGenericIcebergNotFoundException);
default:
return asGenericIcebergNotFoundException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import org.apache.polaris.core.entity.PolarisGrantRecord;
import org.apache.polaris.core.entity.PolarisPrivilege;
import org.apache.polaris.core.persistence.cache.EntityCache;
import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
import org.apache.polaris.core.persistence.resolver.Resolver;
import org.apache.polaris.core.storage.cache.StorageCredentialCache;

Expand Down Expand Up @@ -78,16 +77,17 @@ public Resolver prepareResolver(
referenceCatalogName);
}

public PolarisResolutionManifest prepareResolutionManifest(
public ResolutionManifestBuilder prepareResolutionManifest(
@Nonnull CallContext callContext,
@Nonnull AuthenticatedPolarisPrincipal authenticatedPrincipal,
@Nullable String referenceCatalogName) {
PolarisResolutionManifest manifest =
new PolarisResolutionManifest(
callContext, this, authenticatedPrincipal, referenceCatalogName);
manifest.setSimulatedResolvedRootContainerEntity(
getSimulatedResolvedRootContainerEntity(callContext));
return manifest;
return this.metaStoreManager
.newResolutionManifestBuilder(
callContext,
authenticatedPrincipal,
() -> prepareResolver(callContext, authenticatedPrincipal, referenceCatalogName),
referenceCatalogName)
.withRootContainerEntity(getSimulatedResolvedRootContainerEntity(callContext));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
import jakarta.annotation.Nullable;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
import org.apache.polaris.core.auth.PolarisGrantManager;
import org.apache.polaris.core.auth.PolarisSecretsManager;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.entity.PolarisEntity;
import org.apache.polaris.core.entity.PolarisEntityActiveRecord;
Expand All @@ -36,6 +39,7 @@
import org.apache.polaris.core.entity.PolarisEntityType;
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
import org.apache.polaris.core.persistence.cache.PolarisRemoteCache;
import org.apache.polaris.core.persistence.resolver.Resolver;
import org.apache.polaris.core.storage.PolarisCredentialVendor;

/**
Expand Down Expand Up @@ -72,6 +76,13 @@ public interface PolarisMetaStoreManager
@Nonnull
BaseResult purge(@Nonnull PolarisCallContext callCtx);

@Nonnull
ResolutionManifestBuilder newResolutionManifestBuilder(
@Nonnull CallContext callContext,
@Nonnull AuthenticatedPolarisPrincipal authenticatedPrincipal,
@Nonnull Supplier<Resolver> resolverSupplier,
@Nullable String referenceCatalogName);

/** the return for an entity lookup call */
class EntityResult extends BaseResult {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,17 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.exceptions.NotFoundException;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.entity.AsyncTaskType;
import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.entity.PolarisChangeTrackingVersions;
Expand All @@ -51,6 +58,8 @@
import org.apache.polaris.core.entity.PolarisPrincipalSecrets;
import org.apache.polaris.core.entity.PolarisPrivilege;
import org.apache.polaris.core.entity.PolarisTaskConstants;
import org.apache.polaris.core.persistence.resolver.Resolver;
import org.apache.polaris.core.persistence.resolver.ResolverPath;
import org.apache.polaris.core.storage.PolarisCredentialProperty;
import org.apache.polaris.core.storage.PolarisStorageActions;
import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
Expand Down Expand Up @@ -1006,6 +1015,114 @@ public Map<String, String> deserializeProperties(PolarisCallContext callCtx, Str
: new PrincipalSecretsResult(secrets);
}

@Override
@Nonnull
public ResolutionManifestBuilder newResolutionManifestBuilder(
@Nonnull CallContext callContext,
@Nonnull AuthenticatedPolarisPrincipal authenticatedPrincipal,
@Nonnull Supplier<Resolver> resolverSupplier,
@Nullable String referenceCatalogName) {
return new ResolutionManifestBuilder() {
private Function<EntityNotFoundException, RuntimeException> notFoundExceptionMapper =
nf -> nf;

private final PolarisResolutionManifest manifest =
new PolarisResolutionManifest(
authenticatedPrincipal,
referenceCatalogName,
resolverSupplier,
callContext.getPolarisCallContext().getDiagServices());

@Override
public ResolutionManifestBuilder withRootContainerEntity(
ResolvedPolarisEntity simulatedResolvedRootContainerEntity) {
manifest.setSimulatedResolvedRootContainerEntity(simulatedResolvedRootContainerEntity);
return this;
}

@Override
public ResolutionManifestBuilder addTopLevelName(
String topLevelEntityName, PolarisEntityType entityType, boolean optional) {
manifest.addTopLevelName(topLevelEntityName, entityType, optional);
return this;
}

@Override
public ResolutionManifestBuilder addPath(ResolverPath resolverPath, String catalogRoleName) {
manifest.addPath(resolverPath, catalogRoleName);
return this;
}

@Override
public ResolutionManifestBuilder addPath(ResolverPath resolverPath, Namespace namespace) {
manifest.addPath(resolverPath, namespace);
return this;
}

@Override
public ResolutionManifestBuilder addPath(
ResolverPath resolverPath, TableIdentifier tableIdentifier) {
manifest.addPath(resolverPath, tableIdentifier);
return this;
}

@Override
public ResolutionManifestBuilder addPassthroughPath(
ResolverPath resolverPath, TableIdentifier tableIdentifier) {
manifest.addPassthroughPath(resolverPath, tableIdentifier);
return this;
}

@Override
public ResolutionManifestBuilder addPassthroughPath(
ResolverPath resolverPath, Namespace namespace) {
manifest.addPassthroughPath(resolverPath, namespace);
return this;
}

@Override
public ResolutionManifestBuilder notFoundExceptionMapper(
Function<EntityNotFoundException, RuntimeException> notFoundExceptionMapper) {
this.notFoundExceptionMapper = notFoundExceptionMapper;
return this;
}

@Override
public ResolutionManifest buildResolved() {
return buildResolved(null);
}

@Override
public ResolutionManifest buildResolved(PolarisEntitySubType subType) {
var status = manifest.resolveAll();

switch (status.getStatus()) {
case SUCCESS:
return manifest;
case CALLER_PRINCIPAL_DOES_NOT_EXIST:
throw new NotFoundException("Caller principal does not exist");
case ENTITY_COULD_NOT_BE_RESOLVED:
throw notFoundExceptionMapper.apply(
new EntityNotFoundException(
status.getFailedToResolvedEntityType(),
Optional.ofNullable(subType),
status.getFailedToResolvedEntityName()));
case PATH_COULD_NOT_BE_FULLY_RESOLVED:
var path = status.getFailedToResolvePath();
var names = path.getEntityNames();
throw notFoundExceptionMapper.apply(
new EntityNotFoundException(
path.getLastEntityType(),
Optional.ofNullable(subType),
String.join(".", names)));

default:
throw new IllegalStateException("Unexpected status " + status);
}
}
};
}

/** See {@link #} */
private @Nullable PolarisPrincipalSecrets rotatePrincipalSecrets(
@Nonnull PolarisCallContext callCtx,
Expand Down
Loading

0 comments on commit f92cee9

Please sign in to comment.