Skip to content
Closed
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 @@ -18,6 +18,8 @@
*/
package org.apache.polaris.service.catalog;

import static com.google.common.base.Preconditions.checkState;

import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import jakarta.ws.rs.core.SecurityContext;
Expand Down Expand Up @@ -184,9 +186,8 @@ private void initializeCatalog() {
this.baseCatalog =
catalogFactory.createCallContextCatalog(
callContext, authenticatedPrincipal, securityContext, resolutionManifest);
this.namespaceCatalog =
(baseCatalog instanceof SupportsNamespaces) ? (SupportsNamespaces) baseCatalog : null;
this.viewCatalog = (baseCatalog instanceof ViewCatalog) ? (ViewCatalog) baseCatalog : null;
this.namespaceCatalog = (baseCatalog instanceof SupportsNamespaces ns) ? ns : null;
this.viewCatalog = (baseCatalog instanceof ViewCatalog vc) ? vc : null;
}

private void authorizeBasicNamespaceOperationOrThrow(
Expand Down Expand Up @@ -673,9 +674,8 @@ private TableMetadata stageTableCreateHelper(Namespace namespace, CreateTableReq
if (request.location() != null) {
// Even if the request provides a location, run it through the catalog's TableBuilder
// to inherit any override behaviors if applicable.
if (baseCatalog instanceof BasePolarisCatalog) {
location =
((BasePolarisCatalog) baseCatalog).transformTableLikeLocation(request.location());
if (baseCatalog instanceof BasePolarisCatalog basePolarisCatalog) {
location = basePolarisCatalog.transformTableLikeLocation(request.location());
} else {
location = request.location();
}
Expand All @@ -691,14 +691,12 @@ private TableMetadata stageTableCreateHelper(Namespace namespace, CreateTableReq
.location();
}

TableMetadata metadata =
TableMetadata.newTableMetadata(
request.schema(),
request.spec() != null ? request.spec() : PartitionSpec.unpartitioned(),
request.writeOrder() != null ? request.writeOrder() : SortOrder.unsorted(),
location,
properties);
return metadata;
return TableMetadata.newTableMetadata(
request.schema(),
request.spec() != null ? request.spec() : PartitionSpec.unpartitioned(),
request.writeOrder() != null ? request.writeOrder() : SortOrder.unsorted(),
location,
properties);
}

public LoadTableResponse createTableStaged(Namespace namespace, CreateTableRequest request) {
Expand Down Expand Up @@ -896,12 +894,11 @@ private UpdateTableRequest applyUpdateFilters(UpdateTableRequest request) {
request.updates().stream()
.map(
update -> {
if (baseCatalog instanceof BasePolarisCatalog
&& update instanceof MetadataUpdate.SetLocation) {
String requestedLocation = ((MetadataUpdate.SetLocation) update).location();
if (baseCatalog instanceof BasePolarisCatalog basePolarisCatalog
&& update instanceof MetadataUpdate.SetLocation setLocation) {
String requestedLocation = setLocation.location();
String filteredLocation =
((BasePolarisCatalog) baseCatalog)
.transformTableLikeLocation(requestedLocation);
basePolarisCatalog.transformTableLikeLocation(requestedLocation);
return new MetadataUpdate.SetLocation(filteredLocation);
} else {
return update;
Expand Down Expand Up @@ -1019,88 +1016,86 @@ public void commitTransaction(CommitTransactionRequest commitTransactionRequest)
throw new BadRequestException("Cannot update table on external catalogs.");
}

if (!(baseCatalog instanceof BasePolarisCatalog)) {
if (baseCatalog instanceof BasePolarisCatalog basePolarisCatalog) {
Copy link
Contributor

@dimas-b dimas-b Mar 19, 2025

Choose a reason for hiding this comment

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

Is it totally forbidden to do if (!(baseCatalog instanceof BasePolarisCatalog))? it looks like an overkill to me 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

With the new errorprone version, yes.
Looks like the behavior to rely on errorprone defaults was there since forever 🤷

Copy link
Contributor

Choose a reason for hiding this comment

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

Blocking this particular coding option does not sound good to me. I believe it is quite valid to use negative instanceof checks in if conditions.

Copy link
Member Author

Choose a reason for hiding this comment

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

As an alternative we could use the config-file approach.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm, while looking into the config-file approach, I realized that exactly this pattern is explicitly set to ERROR here via #393

Copy link
Member Author

Choose a reason for hiding this comment

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

Created #1233.

I don't mind nuking this PR, setting it to draft for now, unless there's interest in this change.

// Swap in TransactionWorkspaceMetaStoreManager for all mutations made by this baseCatalog to
// only go into an in-memory collection that we can commit as a single atomic unit after all
// validations.
TransactionWorkspaceMetaStoreManager transactionMetaStoreManager =
new TransactionWorkspaceMetaStoreManager(metaStoreManager);
basePolarisCatalog.setMetaStoreManager(transactionMetaStoreManager);

commitTransactionRequest
.tableChanges()
.forEach(
change -> {
Table table = baseCatalog.loadTable(change.identifier());
checkState(
table instanceof BaseTable,
"Cannot wrap catalog that does not produce BaseTable");
if (isCreate(change)) {
throw new BadRequestException(
"Unsupported operation: commitTranaction with updateForStagedCreate: %s",
change);
}

TableOperations tableOps = ((BaseTable) table).operations();
TableMetadata currentMetadata = tableOps.current();

// Validate requirements; any CommitFailedExceptions will fail the overall request
change.requirements().forEach(requirement -> requirement.validate(currentMetadata));

// Apply changes
TableMetadata.Builder metadataBuilder = TableMetadata.buildFrom(currentMetadata);
change
.updates()
.forEach(
singleUpdate -> {
// Note: If location-overlap checking is refactored to be atomic, we could
// support validation within a single multi-table transaction as well, but
// will need to update the TransactionWorkspaceMetaStoreManager to better
// expose the concept of being able to read uncommitted updates.
if (singleUpdate instanceof MetadataUpdate.SetLocation setLocation) {
if (!currentMetadata.location().equals(setLocation.location())
&& !callContext
.getPolarisCallContext()
.getConfigurationStore()
.getConfiguration(
callContext.getPolarisCallContext(),
FeatureConfiguration.ALLOW_NAMESPACE_LOCATION_OVERLAP)) {
throw new BadRequestException(
"Unsupported operation: commitTransaction containing SetLocation"
+ " for table '%s' and new location '%s'",
change.identifier(), setLocation.location());
}
}

// Apply updates to builder
singleUpdate.applyTo(metadataBuilder);
});

// Commit into transaction workspace we swapped the baseCatalog to use
TableMetadata updatedMetadata = metadataBuilder.build();
if (!updatedMetadata.changes().isEmpty()) {
tableOps.commit(currentMetadata, updatedMetadata);
}
});

// Commit the collected updates in a single atomic operation
List<EntityWithPath> pendingUpdates = transactionMetaStoreManager.getPendingUpdates();
EntitiesResult result =
metaStoreManager.updateEntitiesPropertiesIfNotChanged(
callContext.getPolarisCallContext(), pendingUpdates);
if (!result.isSuccess()) {
// TODO: Retries and server-side cleanup on failure
throw new CommitFailedException(
"Transaction commit failed with status: %s, extraInfo: %s",
result.getReturnStatus(), result.getExtraInformation());
}
} else {
throw new BadRequestException(
"Unsupported operation: commitTransaction with baseCatalog type: %s",
baseCatalog.getClass().getName());
}

// Swap in TransactionWorkspaceMetaStoreManager for all mutations made by this baseCatalog to
// only go into an in-memory collection that we can commit as a single atomic unit after all
// validations.
TransactionWorkspaceMetaStoreManager transactionMetaStoreManager =
new TransactionWorkspaceMetaStoreManager(metaStoreManager);
((BasePolarisCatalog) baseCatalog).setMetaStoreManager(transactionMetaStoreManager);

commitTransactionRequest.tableChanges().stream()
.forEach(
change -> {
Table table = baseCatalog.loadTable(change.identifier());
if (!(table instanceof BaseTable)) {
throw new IllegalStateException(
"Cannot wrap catalog that does not produce BaseTable");
}
if (isCreate(change)) {
throw new BadRequestException(
"Unsupported operation: commitTranaction with updateForStagedCreate: %s",
change);
}

TableOperations tableOps = ((BaseTable) table).operations();
TableMetadata currentMetadata = tableOps.current();

// Validate requirements; any CommitFailedExceptions will fail the overall request
change.requirements().forEach(requirement -> requirement.validate(currentMetadata));

// Apply changes
TableMetadata.Builder metadataBuilder = TableMetadata.buildFrom(currentMetadata);
change.updates().stream()
.forEach(
singleUpdate -> {
// Note: If location-overlap checking is refactored to be atomic, we could
// support validation within a single multi-table transaction as well, but
// will need to update the TransactionWorkspaceMetaStoreManager to better
// expose the concept of being able to read uncommitted updates.
if (singleUpdate instanceof MetadataUpdate.SetLocation) {
if (!currentMetadata
.location()
.equals(((MetadataUpdate.SetLocation) singleUpdate).location())
&& !callContext
.getPolarisCallContext()
.getConfigurationStore()
.getConfiguration(
callContext.getPolarisCallContext(),
FeatureConfiguration.ALLOW_NAMESPACE_LOCATION_OVERLAP)) {
throw new BadRequestException(
"Unsupported operation: commitTransaction containing SetLocation"
+ " for table '%s' and new location '%s'",
change.identifier(),
((MetadataUpdate.SetLocation) singleUpdate).location());
}
}

// Apply updates to builder
singleUpdate.applyTo(metadataBuilder);
});

// Commit into transaction workspace we swapped the baseCatalog to use
TableMetadata updatedMetadata = metadataBuilder.build();
if (!updatedMetadata.changes().isEmpty()) {
tableOps.commit(currentMetadata, updatedMetadata);
}
});

// Commit the collected updates in a single atomic operation
List<EntityWithPath> pendingUpdates = transactionMetaStoreManager.getPendingUpdates();
EntitiesResult result =
metaStoreManager.updateEntitiesPropertiesIfNotChanged(
callContext.getPolarisCallContext(), pendingUpdates);
if (!result.isSuccess()) {
// TODO: Retries and server-side cleanup on failure
throw new CommitFailedException(
"Transaction commit failed with status: %s, extraInfo: %s",
result.getReturnStatus(), result.getExtraInformation());
}
}

public ListTablesResponse listViews(Namespace namespace) {
Expand Down