From 6275b2f4fca904ebae74dbf4af34b2938cbe97b2 Mon Sep 17 00:00:00 2001 From: gauravpurohit Date: Tue, 28 Jun 2022 08:04:55 +0000 Subject: [PATCH 1/6] 1) While wrapping API exception under SpannerException, using actual exception not it's cause. 2) Added public methods under SpannerException to provide more info related to errors. --- .../cloud/spanner/SpannerException.java | 31 +++++++++++++++++++ .../spanner/SpannerExceptionFactory.java | 8 ++--- 2 files changed, 33 insertions(+), 6 deletions(-) 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..75aa9c37244 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. */ @@ -95,4 +98,32 @@ static long extractRetryDelay(Throwable cause) { } return -1L; } + + public String getReason() { + if (this.getCause() instanceof ApiException) { + return ((ApiException) this.getCause()).getReason(); + } + return null; + } + + public String getDomain() { + if (this.getCause() instanceof ApiException) { + return ((ApiException) this.getCause()).getDomain(); + } + return null; + } + + public Map getMetadata() { + if (this.getCause() instanceof ApiException) { + return ((ApiException) this.getCause()).getMetadata(); + } + return null; + } + + public ErrorDetails getErrorDetails() { + if (this.getCause() instanceof ApiException) { + return ((ApiException) this.getCause()).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..b50dee1c58e 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 @@ -295,12 +295,8 @@ 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.newSpannerException( + errorCode, exception.getMessage(), exception); } private static boolean isRetryable(ErrorCode code, @Nullable Throwable cause) { From 0fad97fb1525af3684e23bb2f41dc49864f38fda Mon Sep 17 00:00:00 2001 From: gauravpurohit Date: Thu, 30 Jun 2022 09:07:38 +0000 Subject: [PATCH 2/6] docs: Adding JavaDoc for newly added public methods. --- .../cloud/spanner/SpannerException.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) 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 75aa9c37244..08683c3cd36 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 @@ -99,6 +99,14 @@ 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.getCause() instanceof ApiException) { return ((ApiException) this.getCause()).getReason(); @@ -106,6 +114,14 @@ public String 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.getCause() instanceof ApiException) { return ((ApiException) this.getCause()).getDomain(); @@ -113,6 +129,14 @@ public String 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.getCause() instanceof ApiException) { return ((ApiException) this.getCause()).getMetadata(); @@ -120,6 +144,17 @@ public Map 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.getCause() instanceof ApiException) { return ((ApiException) this.getCause()).getErrorDetails(); From 33bf30b9fca81e323adbf4b8349e9b1477548ee4 Mon Sep 17 00:00:00 2001 From: gauravpurohit Date: Thu, 30 Jun 2022 11:31:59 +0000 Subject: [PATCH 3/6] reactor: To add apiException as a prtavie field under SpannerException. --- .../cloud/spanner/SpannerException.java | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) 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 08683c3cd36..59c816a2944 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 @@ -54,6 +54,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( @@ -62,10 +63,23 @@ public String getResourceName() { boolean retryable, @Nullable String message, @Nullable Throwable cause) { - super(message, cause, code.getCode(), retryable); + super( + message, + // If the cause is instance of APIException then using its cause to avoid the breaking + // change, because earlier we were passing APIException's cause to constructor. + cause instanceof ApiException ? cause.getCause() : cause, + code.getCode(), + retryable); + if (token != DoNotConstructDirectly.ALLOWED) { throw new AssertionError("Do not construct directly: use SpannerExceptionFactory"); } + + if (cause instanceof ApiException) { + this.apiException = (ApiException) cause; + } else { + this.apiException = null; + } this.code = Preconditions.checkNotNull(code); } @@ -108,8 +122,8 @@ static long extractRetryDelay(Throwable cause) { * @return the reason of an error. */ public String getReason() { - if (this.getCause() instanceof ApiException) { - return ((ApiException) this.getCause()).getReason(); + if (this.apiException != null) { + return this.apiException.getReason(); } return null; } @@ -123,8 +137,8 @@ public String getReason() { * @return the logical grouping to which the "reason" belongs. */ public String getDomain() { - if (this.getCause() instanceof ApiException) { - return ((ApiException) this.getCause()).getDomain(); + if (this.apiException != null) { + return this.apiException.getDomain(); } return null; } @@ -138,8 +152,8 @@ public String getDomain() { * @return the map of additional structured details about an error. */ public Map getMetadata() { - if (this.getCause() instanceof ApiException) { - return ((ApiException) this.getCause()).getMetadata(); + if (this.apiException != null) { + return this.apiException.getMetadata(); } return null; } @@ -156,8 +170,8 @@ public Map getMetadata() { * @return An object containing getters for structured objects from error_details.proto. */ public ErrorDetails getErrorDetails() { - if (this.getCause() instanceof ApiException) { - return ((ApiException) this.getCause()).getErrorDetails(); + if (this.apiException != null) { + return this.apiException.getErrorDetails(); } return null; } From 5fe21d0349954429580681cffa45fb5d260e10b6 Mon Sep 17 00:00:00 2001 From: gauravpurohit Date: Fri, 1 Jul 2022 05:26:39 +0000 Subject: [PATCH 4/6] reactor: Modify SpannerException and derived classes constructor to add support for APIException, and overloading method in SpannerExceptionFactory to add support for new parameter. --- .../cloud/spanner/AbortedException.java | 12 ++++++- ...minRequestsPerMinuteExceededException.java | 12 ++++++- .../spanner/DatabaseNotFoundException.java | 13 ++++++- .../spanner/InstanceNotFoundException.java | 12 ++++++- .../spanner/SessionNotFoundException.java | 13 ++++++- .../cloud/spanner/SpannerException.java | 30 ++++++++-------- .../spanner/SpannerExceptionFactory.java | 36 ++++++++++++++----- 7 files changed, 99 insertions(+), 29 deletions(-) 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 59c816a2944..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 @@ -39,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; } @@ -63,24 +64,23 @@ public String getResourceName() { boolean retryable, @Nullable String message, @Nullable Throwable cause) { - super( - message, - // If the cause is instance of APIException then using its cause to avoid the breaking - // change, because earlier we were passing APIException's cause to constructor. - cause instanceof ApiException ? cause.getCause() : cause, - code.getCode(), - retryable); + 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"); } - - if (cause instanceof ApiException) { - this.apiException = (ApiException) cause; - } else { - this.apiException = null; - } this.code = Preconditions.checkNotNull(code); + this.apiException = apiException; } /** Returns the error code associated with this exception. */ 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 b50dee1c58e..8eedd65bed3 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,8 +307,14 @@ private static SpannerException fromApiException(ApiException exception) { code = Status.Code.UNKNOWN; } ErrorCode errorCode = ErrorCode.fromGrpcStatus(Status.fromCode(code)); - return SpannerExceptionFactory.newSpannerException( - errorCode, exception.getMessage(), exception); + + Throwable exceptionCause = null; + + if (exception.getCause() != null) { + exceptionCause = exception.getCause(); + } + return SpannerExceptionFactory.newSpannerExceptionPreformatted( + errorCode, formatMessage(errorCode, exception.getMessage()), exceptionCause, exception); } private static boolean isRetryable(ErrorCode code, @Nullable Throwable cause) { From 36014befeaa902196e8060faecc337d10dc7e312 Mon Sep 17 00:00:00 2001 From: gauravpurohit Date: Mon, 4 Jul 2022 10:11:50 +0000 Subject: [PATCH 5/6] refactor: Passing exception cause directly to method. --- .../google/cloud/spanner/SpannerExceptionFactory.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 8eedd65bed3..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 @@ -308,13 +308,11 @@ private static SpannerException fromApiException(ApiException exception) { } ErrorCode errorCode = ErrorCode.fromGrpcStatus(Status.fromCode(code)); - Throwable exceptionCause = null; - - if (exception.getCause() != null) { - exceptionCause = exception.getCause(); - } return SpannerExceptionFactory.newSpannerExceptionPreformatted( - errorCode, formatMessage(errorCode, exception.getMessage()), exceptionCause, exception); + errorCode, + formatMessage(errorCode, exception.getMessage()), + exception.getCause(), + exception); } private static boolean isRetryable(ErrorCode code, @Nullable Throwable cause) { From e860a27266c5c2773c76a639f2ec0d114db427dd Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Mon, 4 Jul 2022 17:20:29 +0000 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20po?= =?UTF-8?q?st-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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