diff --git a/README.md b/README.md
index a5067eccd35..4a7495bf860 100644
--- a/README.md
+++ b/README.md
@@ -56,13 +56,13 @@ implementation 'com.google.cloud:google-cloud-spanner'
If you are using Gradle without BOM, add this to your dependencies
```Groovy
-implementation 'com.google.cloud:google-cloud-spanner:6.25.6'
+implementation 'com.google.cloud:google-cloud-spanner:6.25.7'
```
If you are using SBT, add this to your dependencies
```Scala
-libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.25.6"
+libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.25.7"
```
## Authentication
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbortedException.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbortedException.java
index b3fb0b05328..21b0bb2224a 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbortedException.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbortedException.java
@@ -16,6 +16,7 @@
package com.google.cloud.spanner;
+import com.google.api.gax.rpc.ApiException;
import javax.annotation.Nullable;
/**
@@ -34,6 +35,15 @@ public class AbortedException extends SpannerException {
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
AbortedException(
DoNotConstructDirectly token, @Nullable String message, @Nullable Throwable cause) {
- super(token, ErrorCode.ABORTED, IS_RETRYABLE, message, cause);
+ this(token, message, cause, null);
+ }
+
+ /** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
+ AbortedException(
+ DoNotConstructDirectly token,
+ @Nullable String message,
+ @Nullable Throwable cause,
+ @Nullable ApiException apiException) {
+ super(token, ErrorCode.ABORTED, IS_RETRYABLE, message, cause, apiException);
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AdminRequestsPerMinuteExceededException.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AdminRequestsPerMinuteExceededException.java
index 11870c94d00..72d8b0ab15d 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AdminRequestsPerMinuteExceededException.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AdminRequestsPerMinuteExceededException.java
@@ -16,6 +16,7 @@
package com.google.cloud.spanner;
+import com.google.api.gax.rpc.ApiException;
import javax.annotation.Nullable;
/**
@@ -31,6 +32,15 @@ public class AdminRequestsPerMinuteExceededException extends SpannerException {
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
AdminRequestsPerMinuteExceededException(
DoNotConstructDirectly token, @Nullable String message, @Nullable Throwable cause) {
- super(token, ErrorCode.RESOURCE_EXHAUSTED, true, message, cause);
+ this(token, message, cause, null);
+ }
+
+ /** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
+ AdminRequestsPerMinuteExceededException(
+ DoNotConstructDirectly token,
+ @Nullable String message,
+ @Nullable Throwable cause,
+ @Nullable ApiException apiException) {
+ super(token, ErrorCode.RESOURCE_EXHAUSTED, true, message, cause, apiException);
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseNotFoundException.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseNotFoundException.java
index aafd799103e..cc4a2e32f0b 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseNotFoundException.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseNotFoundException.java
@@ -16,6 +16,7 @@
package com.google.cloud.spanner;
+import com.google.api.gax.rpc.ApiException;
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
import com.google.rpc.ResourceInfo;
import javax.annotation.Nullable;
@@ -34,6 +35,16 @@ public class DatabaseNotFoundException extends ResourceNotFoundException {
@Nullable String message,
ResourceInfo resourceInfo,
@Nullable Throwable cause) {
- super(token, message, resourceInfo, cause);
+ this(token, message, resourceInfo, cause, null);
+ }
+
+ /** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
+ DatabaseNotFoundException(
+ DoNotConstructDirectly token,
+ @Nullable String message,
+ ResourceInfo resourceInfo,
+ @Nullable Throwable cause,
+ @Nullable ApiException apiException) {
+ super(token, message, resourceInfo, cause, apiException);
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceNotFoundException.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceNotFoundException.java
index 6c179ca9b60..82c451f9475 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceNotFoundException.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceNotFoundException.java
@@ -16,6 +16,7 @@
package com.google.cloud.spanner;
+import com.google.api.gax.rpc.ApiException;
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
import com.google.rpc.ResourceInfo;
import javax.annotation.Nullable;
@@ -34,6 +35,15 @@ public class InstanceNotFoundException extends ResourceNotFoundException {
@Nullable String message,
ResourceInfo resourceInfo,
@Nullable Throwable cause) {
- super(token, message, resourceInfo, cause);
+ this(token, message, resourceInfo, cause, null);
+ }
+ /** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
+ InstanceNotFoundException(
+ DoNotConstructDirectly token,
+ @Nullable String message,
+ ResourceInfo resourceInfo,
+ @Nullable Throwable cause,
+ @Nullable ApiException apiException) {
+ super(token, message, resourceInfo, cause, apiException);
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionNotFoundException.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionNotFoundException.java
index 4e3e08c5c21..f4a62b1954a 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionNotFoundException.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SessionNotFoundException.java
@@ -16,6 +16,7 @@
package com.google.cloud.spanner;
+import com.google.api.gax.rpc.ApiException;
import com.google.cloud.spanner.SpannerException.ResourceNotFoundException;
import com.google.rpc.ResourceInfo;
import javax.annotation.Nullable;
@@ -34,6 +35,16 @@ public class SessionNotFoundException extends ResourceNotFoundException {
@Nullable String message,
ResourceInfo resourceInfo,
@Nullable Throwable cause) {
- super(token, message, resourceInfo, cause);
+ this(token, message, resourceInfo, cause, null);
+ }
+
+ /** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
+ SessionNotFoundException(
+ DoNotConstructDirectly token,
+ @Nullable String message,
+ ResourceInfo resourceInfo,
+ @Nullable Throwable cause,
+ @Nullable ApiException apiException) {
+ super(token, message, resourceInfo, cause, apiException);
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerException.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerException.java
index 8c3af71547e..58076570c20 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerException.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerException.java
@@ -16,6 +16,8 @@
package com.google.cloud.spanner;
+import com.google.api.gax.rpc.ApiException;
+import com.google.api.gax.rpc.ErrorDetails;
import com.google.cloud.grpc.BaseGrpcServiceException;
import com.google.common.base.Preconditions;
import com.google.protobuf.util.Durations;
@@ -24,6 +26,7 @@
import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.protobuf.ProtoUtils;
+import java.util.Map;
import javax.annotation.Nullable;
/** Base exception type for all exceptions produced by the Cloud Spanner service. */
@@ -36,8 +39,9 @@ public abstract static class ResourceNotFoundException extends SpannerException
DoNotConstructDirectly token,
@Nullable String message,
ResourceInfo resourceInfo,
- @Nullable Throwable cause) {
- super(token, ErrorCode.NOT_FOUND, /* retryable */ false, message, cause);
+ @Nullable Throwable cause,
+ @Nullable ApiException apiException) {
+ super(token, ErrorCode.NOT_FOUND, /* retryable */ false, message, cause, apiException);
this.resourceInfo = resourceInfo;
}
@@ -51,6 +55,7 @@ public String getResourceName() {
ProtoUtils.keyForProto(RetryInfo.getDefaultInstance());
private final ErrorCode code;
+ private final ApiException apiException;
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
SpannerException(
@@ -59,11 +64,23 @@ public String getResourceName() {
boolean retryable,
@Nullable String message,
@Nullable Throwable cause) {
+ this(token, code, retryable, message, cause, null);
+ }
+
+ /** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
+ SpannerException(
+ DoNotConstructDirectly token,
+ ErrorCode code,
+ boolean retryable,
+ @Nullable String message,
+ @Nullable Throwable cause,
+ @Nullable ApiException apiException) {
super(message, cause, code.getCode(), retryable);
if (token != DoNotConstructDirectly.ALLOWED) {
throw new AssertionError("Do not construct directly: use SpannerExceptionFactory");
}
this.code = Preconditions.checkNotNull(code);
+ this.apiException = apiException;
}
/** Returns the error code associated with this exception. */
@@ -95,4 +112,67 @@ static long extractRetryDelay(Throwable cause) {
}
return -1L;
}
+
+ /**
+ * Checks the underlying reason of the exception and if it's {@link ApiException} then return the
+ * reason otherwise null.
+ *
+ * @see Reason
+ * @return the reason of an error.
+ */
+ public String getReason() {
+ if (this.apiException != null) {
+ return this.apiException.getReason();
+ }
+ return null;
+ }
+
+ /**
+ * Checks the underlying reason of the exception and if it's {@link ApiException} then return the
+ * specific domain otherwise null.
+ *
+ * @see Domain
+ * @return the logical grouping to which the "reason" belongs.
+ */
+ public String getDomain() {
+ if (this.apiException != null) {
+ return this.apiException.getDomain();
+ }
+ return null;
+ }
+
+ /**
+ * Checks the underlying reason of the exception and if it's {@link ApiException} then return a
+ * map of key-value pairs otherwise null.
+ *
+ * @see Metadata
+ * @return the map of additional structured details about an error.
+ */
+ public Map getMetadata() {
+ if (this.apiException != null) {
+ return this.apiException.getMetadata();
+ }
+ return null;
+ }
+
+ /**
+ * Checks the underlying reason of the exception and if it's {@link ApiException} then return the
+ * ErrorDetails otherwise null.
+ *
+ * @see Status
+ * @see Error
+ * Details
+ * @return An object containing getters for structured objects from error_details.proto.
+ */
+ public ErrorDetails getErrorDetails() {
+ if (this.apiException != null) {
+ return this.apiException.getErrorDetails();
+ }
+ return null;
+ }
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java
index da17680d6db..2c52192d214 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerExceptionFactory.java
@@ -251,12 +251,15 @@ static StatusRuntimeException createAbortedExceptionWithRetryDelay(
}
static SpannerException newSpannerExceptionPreformatted(
- ErrorCode code, @Nullable String message, @Nullable Throwable cause) {
+ ErrorCode code,
+ @Nullable String message,
+ @Nullable Throwable cause,
+ @Nullable ApiException apiException) {
// This is the one place in the codebase that is allowed to call constructors directly.
DoNotConstructDirectly token = DoNotConstructDirectly.ALLOWED;
switch (code) {
case ABORTED:
- return new AbortedException(token, message, cause);
+ return new AbortedException(token, message, cause, apiException);
case RESOURCE_EXHAUSTED:
ErrorInfo info = extractErrorInfo(cause);
if (info != null
@@ -265,26 +268,35 @@ static SpannerException newSpannerExceptionPreformatted(
&& AdminRequestsPerMinuteExceededException.ADMIN_REQUESTS_LIMIT_VALUE.equals(
info.getMetadataMap()
.get(AdminRequestsPerMinuteExceededException.ADMIN_REQUESTS_LIMIT_KEY))) {
- return new AdminRequestsPerMinuteExceededException(token, message, cause);
+ return new AdminRequestsPerMinuteExceededException(token, message, cause, apiException);
}
case NOT_FOUND:
ResourceInfo resourceInfo = extractResourceInfo(cause);
if (resourceInfo != null) {
switch (resourceInfo.getResourceType()) {
case SESSION_RESOURCE_TYPE:
- return new SessionNotFoundException(token, message, resourceInfo, cause);
+ return new SessionNotFoundException(
+ token, message, resourceInfo, cause, apiException);
case DATABASE_RESOURCE_TYPE:
- return new DatabaseNotFoundException(token, message, resourceInfo, cause);
+ return new DatabaseNotFoundException(
+ token, message, resourceInfo, cause, apiException);
case INSTANCE_RESOURCE_TYPE:
- return new InstanceNotFoundException(token, message, resourceInfo, cause);
+ return new InstanceNotFoundException(
+ token, message, resourceInfo, cause, apiException);
}
}
// Fall through to the default.
default:
- return new SpannerException(token, code, isRetryable(code, cause), message, cause);
+ return new SpannerException(
+ token, code, isRetryable(code, cause), message, cause, apiException);
}
}
+ static SpannerException newSpannerExceptionPreformatted(
+ ErrorCode code, @Nullable String message, @Nullable Throwable cause) {
+ return newSpannerExceptionPreformatted(code, message, cause, null);
+ }
+
private static SpannerException fromApiException(ApiException exception) {
Status.Code code;
if (exception.getStatusCode() instanceof GrpcStatusCode) {
@@ -295,12 +307,12 @@ private static SpannerException fromApiException(ApiException exception) {
code = Status.Code.UNKNOWN;
}
ErrorCode errorCode = ErrorCode.fromGrpcStatus(Status.fromCode(code));
- if (exception.getCause() != null) {
- return SpannerExceptionFactory.newSpannerException(
- errorCode, exception.getMessage(), exception.getCause());
- } else {
- return SpannerExceptionFactory.newSpannerException(errorCode, exception.getMessage());
- }
+
+ return SpannerExceptionFactory.newSpannerExceptionPreformatted(
+ errorCode,
+ formatMessage(errorCode, exception.getMessage()),
+ exception.getCause(),
+ exception);
}
private static boolean isRetryable(ErrorCode code, @Nullable Throwable cause) {