Skip to content

Commit 49825d8

Browse files
committed
Associate client exceptions with SQL state and ErrorDetails
We now provide error details for client-side exceptions for easier handling of failures. [resolves #538] Signed-off-by: Mark Paluch <[email protected]>
1 parent a32a679 commit 49825d8

File tree

5 files changed

+145
-15
lines changed

5 files changed

+145
-15
lines changed

src/main/java/io/r2dbc/postgresql/PostgresqlConnectionFactory.java

+15-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package io.r2dbc.postgresql;
1818

1919
import io.netty.buffer.ByteBufAllocator;
20+
import io.r2dbc.postgresql.api.ErrorDetails;
21+
import io.r2dbc.postgresql.api.PostgresqlException;
2022
import io.r2dbc.postgresql.client.Client;
2123
import io.r2dbc.postgresql.client.ConnectionSettings;
2224
import io.r2dbc.postgresql.client.ReactorNettyClient;
@@ -218,10 +220,20 @@ private Mono<IsolationLevel> getIsolationLevel(io.r2dbc.postgresql.api.Postgresq
218220
})).defaultIfEmpty(IsolationLevel.READ_COMMITTED).last();
219221
}
220222

221-
static class PostgresConnectionException extends R2dbcNonTransientResourceException {
223+
static class PostgresConnectionException extends R2dbcNonTransientResourceException implements PostgresqlException {
222224

223-
public PostgresConnectionException(String msg, @Nullable Throwable cause) {
224-
super(msg, cause);
225+
private static final String CONNECTION_DOES_NOT_EXIST = "08003";
226+
227+
private final ErrorDetails errorDetails;
228+
229+
public PostgresConnectionException(String reason, @Nullable Throwable cause) {
230+
super(reason, CONNECTION_DOES_NOT_EXIST, 0, null, cause);
231+
this.errorDetails = ErrorDetails.fromCodeAndMessage(CONNECTION_DOES_NOT_EXIST, reason);
232+
}
233+
234+
@Override
235+
public ErrorDetails getErrorDetails() {
236+
return this.errorDetails;
225237
}
226238

227239
}

src/main/java/io/r2dbc/postgresql/api/ErrorDetails.java

+17
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import java.util.Collections;
2525
import java.util.HashMap;
26+
import java.util.LinkedHashMap;
2627
import java.util.List;
2728
import java.util.Map;
2829
import java.util.Objects;
@@ -130,6 +131,22 @@ public static ErrorDetails fromMessage(String message) {
130131
return new ErrorDetails(Collections.singletonMap(MESSAGE, message));
131132
}
132133

134+
/**
135+
* Create a new {@link ErrorDetails}
136+
*
137+
* @param code the error code
138+
* @param message the error message
139+
* @return the {@link ErrorDetails} object
140+
*/
141+
public static ErrorDetails fromCodeAndMessage(String code, String message) {
142+
143+
Map<FieldType, String> details = new LinkedHashMap<>(2);
144+
details.put(CODE, code);
145+
details.put(MESSAGE, message);
146+
147+
return new ErrorDetails(details);
148+
}
149+
133150
@Override
134151
public boolean equals(Object o) {
135152
if (this == o) {

src/main/java/io/r2dbc/postgresql/client/AbstractPostgresSSLHandlerAdapter.java

+13-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import io.netty.handler.ssl.SslHandler;
2323
import io.netty.util.concurrent.Future;
2424
import io.netty.util.concurrent.GenericFutureListener;
25+
import io.r2dbc.postgresql.api.ErrorDetails;
26+
import io.r2dbc.postgresql.api.PostgresqlException;
2527
import io.r2dbc.spi.R2dbcPermissionDeniedException;
2628
import reactor.core.publisher.Mono;
2729

@@ -86,10 +88,18 @@ SslHandler getSslHandler() {
8688
/**
8789
* Postgres-specific {@link R2dbcPermissionDeniedException}.
8890
*/
89-
static final class PostgresqlSslException extends R2dbcPermissionDeniedException {
91+
static final class PostgresqlSslException extends R2dbcPermissionDeniedException implements PostgresqlException {
9092

91-
PostgresqlSslException(String msg) {
92-
super(msg);
93+
private final ErrorDetails errorDetails;
94+
95+
PostgresqlSslException(String reason) {
96+
super(reason, ReactorNettyClient.CONNECTION_FAILURE, 0, (String) null);
97+
this.errorDetails = ErrorDetails.fromCodeAndMessage(ReactorNettyClient.CONNECTION_FAILURE, reason);
98+
}
99+
100+
@Override
101+
public ErrorDetails getErrorDetails() {
102+
return this.errorDetails;
93103
}
94104

95105
}

src/main/java/io/r2dbc/postgresql/client/ReactorNettyClient.java

+45-9
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import io.netty.util.ReferenceCountUtil;
2929
import io.netty.util.internal.logging.InternalLogger;
3030
import io.netty.util.internal.logging.InternalLoggerFactory;
31+
import io.r2dbc.postgresql.api.ErrorDetails;
32+
import io.r2dbc.postgresql.api.PostgresqlException;
3133
import io.r2dbc.postgresql.message.backend.BackendKeyData;
3234
import io.r2dbc.postgresql.message.backend.BackendMessage;
3335
import io.r2dbc.postgresql.message.backend.BackendMessageDecoder;
@@ -89,6 +91,8 @@
8991
*/
9092
public final class ReactorNettyClient implements Client {
9193

94+
static final String CONNECTION_FAILURE = "08006";
95+
9296
private static final Logger logger = Loggers.getLogger(ReactorNettyClient.class);
9397

9498
private static final boolean DEBUG_ENABLED = logger.isDebugEnabled();
@@ -551,38 +555,70 @@ public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
551555

552556
}
553557

554-
static class PostgresConnectionClosedException extends R2dbcNonTransientResourceException {
558+
static class PostgresConnectionClosedException extends R2dbcNonTransientResourceException implements PostgresqlException {
559+
560+
private final ErrorDetails errorDetails;
555561

556562
public PostgresConnectionClosedException(String reason) {
557-
super(reason);
563+
super(reason, CONNECTION_FAILURE, 0, (String) null);
564+
this.errorDetails = ErrorDetails.fromCodeAndMessage(CONNECTION_FAILURE, reason);
558565
}
559566

560567
public PostgresConnectionClosedException(String reason, @Nullable Throwable cause) {
561-
super(reason, cause);
568+
super(reason, CONNECTION_FAILURE, 0, null, cause);
569+
this.errorDetails = ErrorDetails.fromCodeAndMessage(CONNECTION_FAILURE, reason);
570+
}
571+
572+
@Override
573+
public ErrorDetails getErrorDetails() {
574+
return this.errorDetails;
562575
}
563576

564577
}
565578

566-
static class PostgresConnectionException extends R2dbcNonTransientResourceException {
579+
static class PostgresConnectionException extends R2dbcNonTransientResourceException implements PostgresqlException {
580+
581+
private final static ErrorDetails ERROR_DETAILS = ErrorDetails.fromCodeAndMessage(CONNECTION_FAILURE, "An I/O error occurred while sending to the backend or receiving from the backend");
567582

568583
public PostgresConnectionException(Throwable cause) {
569-
super(cause);
584+
super(ERROR_DETAILS.getMessage(), ERROR_DETAILS.getCode(), 0, null, cause);
585+
}
586+
587+
@Override
588+
public ErrorDetails getErrorDetails() {
589+
return ERROR_DETAILS;
570590
}
571591

572592
}
573593

574-
static class RequestQueueException extends R2dbcTransientResourceException {
594+
static class RequestQueueException extends R2dbcTransientResourceException implements PostgresqlException {
595+
596+
private final ErrorDetails errorDetails;
575597

576598
public RequestQueueException(String message) {
577-
super(message);
599+
super(message, CONNECTION_FAILURE, 0, (String) null);
600+
this.errorDetails = ErrorDetails.fromCodeAndMessage(CONNECTION_FAILURE, message);
601+
}
602+
603+
@Override
604+
public ErrorDetails getErrorDetails() {
605+
return this.errorDetails;
578606
}
579607

580608
}
581609

582-
static class ResponseQueueException extends R2dbcNonTransientResourceException {
610+
static class ResponseQueueException extends R2dbcNonTransientResourceException implements PostgresqlException {
611+
612+
private final ErrorDetails errorDetails;
583613

584614
public ResponseQueueException(String message) {
585-
super(message);
615+
super(message, CONNECTION_FAILURE, 0, (String) null);
616+
this.errorDetails = ErrorDetails.fromCodeAndMessage(CONNECTION_FAILURE, message);
617+
}
618+
619+
@Override
620+
public ErrorDetails getErrorDetails() {
621+
return this.errorDetails;
586622
}
587623

588624
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.r2dbc.postgresql;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import java.time.Instant;
22+
import java.time.LocalTime;
23+
import java.time.OffsetDateTime;
24+
25+
/**
26+
* @author Mark Paluch
27+
*/
28+
public class Repro extends AbstractIntegrationTests{
29+
30+
@Test
31+
void name() {
32+
33+
34+
SERVER.getJdbcOperations().execute("DROP TABLE IF EXISTS repro;");
35+
SERVER.getJdbcOperations().execute("CREATE TABLE repro ( created_at timestamp not null,\n" +
36+
" mytime time not null,\n" +
37+
" updated_at timestamp not null,\n" +
38+
" transaction_amount bigint not null,\n" +
39+
" transaction_currency varchar(3) not null,\n" +
40+
" transaction_at timestamptz not null)");
41+
42+
for (int i = 0; i < 10; i++) {
43+
44+
connection.createStatement("INSERT INTO repro VALUES($1, $2, $3, $4, $5, $6)")
45+
.bind("$1", Instant.now())
46+
.bind("$2", LocalTime.now())
47+
.bind("$3", Instant.now())
48+
.bind("$4", 1234)
49+
.bind("$5", "FOO")
50+
.bind("$6", OffsetDateTime.now()).execute().flatMap(it -> it.getRowsUpdated()).blockLast();
51+
}
52+
53+
}
54+
55+
}

0 commit comments

Comments
 (0)