This is an automated email from the ASF dual-hosted git repository. dimas 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 e9267b67e Respond with 409 in case of concurrent Namespace update failures instead of 500 (#1989) e9267b67e is described below commit e9267b67e71a5ef492649f7615e55f9ab59bc7dd Author: fabio-rizzo-01 <fabio.rizzocas...@jpmorgan.com> AuthorDate: Thu Jul 24 20:13:21 2025 +0100 Respond with 409 in case of concurrent Namespace update failures instead of 500 (#1989) --- .../core/exceptions/CommitConflictException.java | 38 ++++++++++++++++++++++ .../service/catalog/iceberg/IcebergCatalog.java | 5 +-- .../service/exception/PolarisExceptionMapper.java | 31 ++++++++---------- .../service/exception/ExceptionMapperTest.java | 10 ++++++ 4 files changed, 65 insertions(+), 19 deletions(-) diff --git a/polaris-core/src/main/java/org/apache/polaris/core/exceptions/CommitConflictException.java b/polaris-core/src/main/java/org/apache/polaris/core/exceptions/CommitConflictException.java new file mode 100644 index 000000000..75a61204e --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/exceptions/CommitConflictException.java @@ -0,0 +1,38 @@ +/* + * 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.exceptions; + +import com.google.errorprone.annotations.FormatMethod; + +public class CommitConflictException extends PolarisException { + @FormatMethod + public CommitConflictException(String message, Object... args) { + super(String.format(message, args)); + } + + @FormatMethod + public CommitConflictException(Throwable cause, String message, Object... args) { + super(String.format(message, args), cause); + } + + public CommitConflictException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java index e2f4d96bd..7300d21c3 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java @@ -104,6 +104,7 @@ import org.apache.polaris.core.entity.PolarisEntitySubType; import org.apache.polaris.core.entity.PolarisEntityType; import org.apache.polaris.core.entity.PolarisTaskConstants; import org.apache.polaris.core.entity.table.IcebergTableLikeEntity; +import org.apache.polaris.core.exceptions.CommitConflictException; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper; import org.apache.polaris.core.persistence.ResolvedPolarisEntity; @@ -715,7 +716,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog .map(PolarisEntity::new) .orElse(null); if (returnedEntity == null) { - throw new RuntimeException("Concurrent modification of namespace: " + namespace); + throw new CommitConflictException("Concurrent modification of namespace: %s", namespace); } return true; } @@ -747,7 +748,7 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog .map(PolarisEntity::new) .orElse(null); if (returnedEntity == null) { - throw new RuntimeException("Concurrent modification of namespace: " + namespace); + throw new CommitConflictException("Concurrent modification of namespace: %s", namespace); } return true; } diff --git a/service/common/src/main/java/org/apache/polaris/service/exception/PolarisExceptionMapper.java b/service/common/src/main/java/org/apache/polaris/service/exception/PolarisExceptionMapper.java index dfbd4f6c5..8b68d3890 100644 --- a/service/common/src/main/java/org/apache/polaris/service/exception/PolarisExceptionMapper.java +++ b/service/common/src/main/java/org/apache/polaris/service/exception/PolarisExceptionMapper.java @@ -24,6 +24,7 @@ import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; import org.apache.iceberg.rest.responses.ErrorResponse; import org.apache.polaris.core.exceptions.AlreadyExistsException; +import org.apache.polaris.core.exceptions.CommitConflictException; import org.apache.polaris.core.exceptions.PolarisException; import org.apache.polaris.core.persistence.PolicyMappingAlreadyExistsException; import org.apache.polaris.core.policy.exceptions.NoSuchPolicyException; @@ -45,23 +46,19 @@ public class PolarisExceptionMapper implements ExceptionMapper<PolarisException> private static final Logger LOGGER = LoggerFactory.getLogger(PolarisExceptionMapper.class); private Response.Status getStatus(PolarisException exception) { - if (exception instanceof AlreadyExistsException) { - return Response.Status.CONFLICT; - } else if (exception instanceof InvalidPolicyException) { - return Response.Status.BAD_REQUEST; - } else if (exception instanceof PolicyAttachException) { - return Response.Status.BAD_REQUEST; - } else if (exception instanceof NoSuchPolicyException) { - return Response.Status.NOT_FOUND; - } else if (exception instanceof PolicyVersionMismatchException) { - return Response.Status.CONFLICT; - } else if (exception instanceof PolicyMappingAlreadyExistsException) { - return Response.Status.CONFLICT; - } else if (exception instanceof PolicyInUseException) { - return Response.Status.BAD_REQUEST; - } else { - return Response.Status.INTERNAL_SERVER_ERROR; - } + return switch (exception) { + case AlreadyExistsException alreadyExistsException -> Response.Status.CONFLICT; + case CommitConflictException commitConflictException -> Response.Status.CONFLICT; + case InvalidPolicyException invalidPolicyException -> Response.Status.BAD_REQUEST; + case PolicyAttachException policyAttachException -> Response.Status.BAD_REQUEST; + case NoSuchPolicyException noSuchPolicyException -> Response.Status.NOT_FOUND; + case PolicyVersionMismatchException policyVersionMismatchException -> + Response.Status.CONFLICT; + case PolicyMappingAlreadyExistsException policyMappingAlreadyExistsException -> + Response.Status.CONFLICT; + case PolicyInUseException policyInUseException -> Response.Status.BAD_REQUEST; + default -> Response.Status.INTERNAL_SERVER_ERROR; + }; } @Override diff --git a/service/common/src/test/java/org/apache/polaris/service/exception/ExceptionMapperTest.java b/service/common/src/test/java/org/apache/polaris/service/exception/ExceptionMapperTest.java index bc5f84c1d..6dc5f77d9 100644 --- a/service/common/src/test/java/org/apache/polaris/service/exception/ExceptionMapperTest.java +++ b/service/common/src/test/java/org/apache/polaris/service/exception/ExceptionMapperTest.java @@ -25,11 +25,14 @@ import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.core.read.ListAppender; import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonProcessingException; +import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.ExceptionMapper; import java.util.Optional; import java.util.stream.Stream; import org.apache.polaris.core.exceptions.AlreadyExistsException; +import org.apache.polaris.core.exceptions.CommitConflictException; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -74,6 +77,13 @@ public class ExceptionMapperTest { .isTrue(); } + @Test + public void testNamespaceException() { + PolarisExceptionMapper mapper = new PolarisExceptionMapper(); + Response response = mapper.toResponse(new CommitConflictException("test")); + Assertions.assertThat(response.getStatus()).isEqualTo(409); + } + static Stream<Arguments> testFullExceptionIsLogged() { // ConstraintViolationException isn't included because it doesn't propagate any info to its // inherited Exception