Skip to content

Commit 6bedf84

Browse files
committed
feat: include PostgreSQL error code in exceptions
PostgreSQL error codes are now included in SpannerException when available. The PostgreSQL error code is only filled with a non-null value if the database is a Spanner PostgreSQL-dialect database, and the error is one that has a corresponding error code in PostgreSQL. See https://www.postgresql.org/docs/current/errcodes-appendix.html for a full list of all PostgreSQL error codes.
1 parent 820efc3 commit 6bedf84

File tree

11 files changed

+61
-40
lines changed

11 files changed

+61
-40
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncRunnerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private <R> R runTransaction(final AsyncWork<R> work) {
5959
try {
6060
return work.doWorkAsync(transaction).get();
6161
} catch (ExecutionException e) {
62-
throw SpannerExceptionFactory.newSpannerException(e.getCause());
62+
throw SpannerExceptionFactory.asSpannerException(e.getCause());
6363
} catch (InterruptedException e) {
6464
throw SpannerExceptionFactory.propagateInterrupt(e);
6565
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/AsyncTransactionManagerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ private ApiFuture<TransactionContext> internalBeginAsync(
123123
@Override
124124
public void onFailure(Throwable t) {
125125
onError(t);
126-
res.setException(SpannerExceptionFactory.newSpannerException(t));
126+
res.setException(SpannerExceptionFactory.asSpannerException(t));
127127
}
128128

129129
@Override

google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcResultSet.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.spanner;
1818

19+
import static com.google.cloud.spanner.SpannerExceptionFactory.asSpannerException;
1920
import static com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException;
2021
import static com.google.common.base.Preconditions.checkState;
2122

@@ -76,7 +77,7 @@ protected GrpcStruct currRow() {
7677
@Override
7778
public boolean next() throws SpannerException {
7879
if (error != null) {
79-
throw newSpannerException(error);
80+
throw asSpannerException(error);
8081
}
8182
try {
8283
if (currRow == null) {
@@ -108,7 +109,7 @@ public boolean next() throws SpannerException {
108109
return hasNext;
109110
} catch (Throwable t) {
110111
throw yieldError(
111-
SpannerExceptionFactory.asSpannerException(t),
112+
asSpannerException(t),
112113
iterator.isWithBeginTransaction() && currRow == null,
113114
iterator.isLastStatement());
114115
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/GrpcStreamIterator.java

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ protected final PartialResultSet computeNext() {
155155
call = null;
156156

157157
if (error != null) {
158-
throw SpannerExceptionFactory.newSpannerException(error);
158+
throw SpannerExceptionFactory.asSpannerException(error);
159159
}
160160

161161
endOfData();
@@ -192,25 +192,17 @@ public void onCompleted() {
192192
}
193193

194194
@Override
195-
public void onError(SpannerException e) {
195+
public void onError(SpannerException exception) {
196196
if (statement != null) {
197197
if (logger.isLoggable(Level.FINEST)) {
198198
// Include parameter values if logging level is set to FINEST or higher.
199-
e =
200-
SpannerExceptionFactory.newSpannerExceptionPreformatted(
201-
e.getErrorCode(),
202-
String.format("%s - Statement: '%s'", e.getMessage(), statement.toString()),
203-
e);
204-
logger.log(Level.FINEST, "Error executing statement", e);
199+
exception.setStatement(statement.toString());
200+
logger.log(Level.FINEST, "Error executing statement", exception);
205201
} else {
206-
e =
207-
SpannerExceptionFactory.newSpannerExceptionPreformatted(
208-
e.getErrorCode(),
209-
String.format("%s - Statement: '%s'", e.getMessage(), statement.getSql()),
210-
e);
202+
exception.setStatement(statement.getSql());
211203
}
212204
}
213-
error = e;
205+
error = exception;
214206
addToStream(END_OF_STREAM);
215207
}
216208

google-cloud-spanner/src/main/java/com/google/cloud/spanner/MultiplexedSessionDatabaseClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ private void verifyBeginTransactionWithRWOnMultiplexedSessionAsync(String sessio
498498
}
499499
readWriteBeginTransactionReferenceFuture.set(txn);
500500
} catch (Exception e) {
501-
SpannerException spannerException = SpannerExceptionFactory.newSpannerException(e);
501+
SpannerException spannerException = SpannerExceptionFactory.asSpannerException(e);
502502
// Mark multiplexed sessions for RW as unimplemented and fall back to regular sessions
503503
// if UNIMPLEMENTED is returned.
504504
maybeMarkUnimplementedForRW(spannerException);

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerApiFutures.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public static <T> T getOrNull(ApiFuture<T> future) throws SpannerException {
3333
if (e.getCause() instanceof SpannerException) {
3434
throw (SpannerException) e.getCause();
3535
}
36-
throw SpannerExceptionFactory.newSpannerException(e.getCause());
36+
throw SpannerExceptionFactory.asSpannerException(e.getCause());
3737
} catch (InterruptedException e) {
3838
throw SpannerExceptionFactory.propagateInterrupt(e, null /*TODO: requestId*/);
3939
} catch (CancellationException e) {

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerException.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,12 @@ public String getResourceName() {
5555
private static final long serialVersionUID = 20150916L;
5656
private static final Metadata.Key<RetryInfo> KEY_RETRY_INFO =
5757
ProtoUtils.keyForProto(RetryInfo.getDefaultInstance());
58+
private static final String PG_ERR_CODE_KEY = "pg_sqlerrcode";
5859

5960
private final ErrorCode code;
6061
private final ApiException apiException;
6162
private XGoogSpannerRequestId requestId;
63+
private String statement;
6264

6365
/** Private constructor. Use {@link SpannerExceptionFactory} to create instances. */
6466
SpannerException(
@@ -99,11 +101,28 @@ public String getResourceName() {
99101
this.requestId = requestId;
100102
}
101103

104+
@Override
105+
public String getMessage() {
106+
if (this.statement == null) {
107+
return super.getMessage();
108+
}
109+
return String.format("%s - Statement: '%s'", super.getMessage(), this.statement);
110+
}
111+
102112
/** Returns the error code associated with this exception. */
103113
public ErrorCode getErrorCode() {
104114
return code;
105115
}
106116

117+
/** Returns the PostgreSQL SQLState error code that is encoded in this exception, or null if this {@link SpannerException} does not include a PostgreSQL error code. */
118+
public String getPostgreSQLErrorCode() {
119+
ErrorDetails details = getErrorDetails();
120+
if (details == null || details.getErrorInfo() == null) {
121+
return null;
122+
}
123+
return details.getErrorInfo().getMetadataOrDefault(PG_ERR_CODE_KEY, null);
124+
}
125+
107126
public String getRequestId() {
108127
if (requestId == null) {
109128
return "";
@@ -199,6 +218,10 @@ public ErrorDetails getErrorDetails() {
199218
return null;
200219
}
201220

221+
void setStatement(String statement) {
222+
this.statement = statement;
223+
}
224+
202225
/** Sets the requestId. */
203226
@InternalApi
204227
public void setRequestId(XGoogSpannerRequestId reqId) {

google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ void ensureTxn() {
281281
try {
282282
ensureTxnAsync().get();
283283
} catch (ExecutionException e) {
284-
throw SpannerExceptionFactory.newSpannerException(e.getCause() == null ? e : e.getCause());
284+
throw SpannerExceptionFactory.asSpannerException(e.getCause() == null ? e : e.getCause());
285285
} catch (InterruptedException e) {
286286
throw SpannerExceptionFactory.propagateInterrupt(e);
287287
}
@@ -370,7 +370,7 @@ void commit() {
370370
throw SpannerExceptionFactory.propagateTimeout((TimeoutException) e);
371371
}
372372
} catch (ExecutionException e) {
373-
throw SpannerExceptionFactory.newSpannerException(e.getCause() == null ? e : e.getCause());
373+
throw SpannerExceptionFactory.asSpannerException(e.getCause() == null ? e : e.getCause());
374374
}
375375
}
376376

@@ -679,7 +679,7 @@ options, getPreviousTransactionId())))
679679
aborted = true;
680680
}
681681
}
682-
throw SpannerExceptionFactory.newSpannerException(e.getCause());
682+
throw SpannerExceptionFactory.asSpannerException(e.getCause());
683683
} catch (TimeoutException e) {
684684
// Throw an ABORTED exception to force a retry of the transaction if no transaction
685685
// has been returned by the first statement.

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/PartitionId.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ protected Class<?> resolveClass(ObjectStreamClass desc)
7474
throw SpannerExceptionFactory.newSpannerException(
7575
ErrorCode.INVALID_ARGUMENT, invalidClassException.getMessage(), invalidClassException);
7676
} catch (Exception exception) {
77-
throw SpannerExceptionFactory.newSpannerException(exception);
77+
throw SpannerExceptionFactory.asSpannerException(exception);
7878
}
7979
}
8080

@@ -90,7 +90,7 @@ public static String encodeToString(BatchTransactionId transactionId, Partition
9090
new ObjectOutputStream(new GZIPOutputStream(byteArrayOutputStream))) {
9191
objectOutputStream.writeObject(id);
9292
} catch (Exception exception) {
93-
throw SpannerExceptionFactory.newSpannerException(exception);
93+
throw SpannerExceptionFactory.asSpannerException(exception);
9494
}
9595
return Base64.getUrlEncoder().encodeToString(byteArrayOutputStream.toByteArray());
9696
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.spanner.spi.v1;
1818

19+
import static com.google.cloud.spanner.SpannerExceptionFactory.asSpannerException;
1920
import static com.google.cloud.spanner.SpannerExceptionFactory.newSpannerException;
2021
import static com.google.cloud.spanner.ThreadFactoryUtil.tryCreateVirtualThreadPerTaskExecutor;
2122

@@ -538,7 +539,7 @@ public <RequestT, ResponseT> UnaryCallable<RequestT, ResponseT> createUnaryCalla
538539
// is actually running.
539540
checkEmulatorConnection(options, channelProvider, credentialsProvider, emulatorHost);
540541
} catch (Exception e) {
541-
throw newSpannerException(e);
542+
throw asSpannerException(e);
542543
}
543544
} else {
544545
this.databaseAdminStub = null;
@@ -726,7 +727,7 @@ private <T> T runWithRetryOnAdministrativeRequestsExceeded(Callable<T> callable)
726727
new AdminRequestsLimitExceededRetryAlgorithm<>(),
727728
NanoClock.getDefaultClock());
728729
} catch (RetryHelperException e) {
729-
throw SpannerExceptionFactory.asSpannerException(e.getCause());
730+
throw asSpannerException(e.getCause());
730731
}
731732
}
732733

@@ -1317,7 +1318,7 @@ public OperationFuture<Empty, UpdateDatabaseDdlMetadata> updateDatabaseDdl(
13171318
throw newSpannerException(e);
13181319
} catch (ExecutionException e) {
13191320
Throwable t = e.getCause();
1320-
SpannerException se = SpannerExceptionFactory.asSpannerException(t);
1321+
SpannerException se = asSpannerException(t);
13211322
if (se instanceof AdminRequestsPerMinuteExceededException) {
13221323
// Propagate this to trigger a retry.
13231324
throw se;
@@ -1983,8 +1984,12 @@ private static <T> T get(final Future<T> future) throws SpannerException {
19831984
// We are the sole consumer of the future, so cancel it.
19841985
future.cancel(true);
19851986
throw SpannerExceptionFactory.propagateInterrupt(e);
1986-
} catch (Exception e) {
1987+
} catch (ExecutionException e) {
1988+
throw asSpannerException(e.getCause());
1989+
} catch (CancellationException e) {
19871990
throw newSpannerException(context, e, null);
1991+
} catch (Exception exception) {
1992+
throw asSpannerException(exception);
19881993
}
19891994
}
19901995

@@ -2222,7 +2227,7 @@ public void onError(Throwable t) {
22222227
if (this.consumer.cancelQueryWhenClientIsClosed()) {
22232228
unregisterResponseObserver(this);
22242229
}
2225-
consumer.onError(newSpannerException(t));
2230+
consumer.onError(asSpannerException(t));
22262231
}
22272232

22282233
@Override

0 commit comments

Comments
 (0)