|
16 | 16 |
|
17 | 17 | package io.r2dbc.postgresql;
|
18 | 18 |
|
| 19 | +import io.r2dbc.postgresql.api.ErrorDetails; |
19 | 20 | import io.r2dbc.postgresql.api.Notification;
|
20 | 21 | import io.r2dbc.postgresql.api.PostgresqlResult;
|
21 | 22 | import io.r2dbc.postgresql.api.PostgresqlStatement;
|
|
25 | 26 | import io.r2dbc.postgresql.client.SimpleQueryMessageFlow;
|
26 | 27 | import io.r2dbc.postgresql.client.TransactionStatus;
|
27 | 28 | import io.r2dbc.postgresql.codec.Codecs;
|
| 29 | +import io.r2dbc.postgresql.message.backend.BackendMessage; |
| 30 | +import io.r2dbc.postgresql.message.backend.CommandComplete; |
| 31 | +import io.r2dbc.postgresql.message.backend.ErrorResponse; |
28 | 32 | import io.r2dbc.postgresql.message.backend.NotificationResponse;
|
29 | 33 | import io.r2dbc.postgresql.util.Assert;
|
30 | 34 | import io.r2dbc.postgresql.util.Operators;
|
31 | 35 | import io.r2dbc.spi.Connection;
|
32 | 36 | import io.r2dbc.spi.IsolationLevel;
|
| 37 | +import io.r2dbc.spi.R2dbcException; |
33 | 38 | import io.r2dbc.spi.ValidationDepth;
|
34 | 39 | import org.reactivestreams.Publisher;
|
35 | 40 | import org.reactivestreams.Subscriber;
|
@@ -112,9 +117,33 @@ public Mono<Void> close() {
|
112 | 117 |
|
113 | 118 | @Override
|
114 | 119 | public Mono<Void> commitTransaction() {
|
| 120 | + |
| 121 | + AtomicReference<R2dbcException> ref = new AtomicReference<>(); |
115 | 122 | return useTransactionStatus(transactionStatus -> {
|
116 | 123 | if (IDLE != transactionStatus) {
|
117 |
| - return exchange("COMMIT"); |
| 124 | + return Flux.from(exchange("COMMIT")) |
| 125 | + .filter(CommandComplete.class::isInstance) |
| 126 | + .cast(CommandComplete.class) |
| 127 | + .<BackendMessage>handle((message, sink) -> { |
| 128 | + |
| 129 | + // Certain backend versions (e.g. 12.2, 11.7, 10.12, 9.6.17, 9.5.21, etc) |
| 130 | + // silently rollback the transaction in the response to COMMIT statement |
| 131 | + // in case the transaction has failed. |
| 132 | + // See discussion in pgsql-hackers: https://www.postgresql.org/message-id/b9fb50dc-0f6e-15fb-6555-8ddb86f4aa71%40postgresfriends.org |
| 133 | + |
| 134 | + if ("ROLLBACK".equalsIgnoreCase(message.getCommand())) { |
| 135 | + ErrorDetails details = ErrorDetails.fromMessage("The database returned ROLLBACK, so the transaction cannot be committed. Transaction " + |
| 136 | + "failure is not known (check server logs?)"); |
| 137 | + ref.set(new ExceptionFactory.PostgresqlRollbackException(details)); |
| 138 | + return; |
| 139 | + } |
| 140 | + |
| 141 | + sink.next(message); |
| 142 | + }).doOnComplete(() -> { |
| 143 | + if (ref.get() != null) { |
| 144 | + throw ref.get(); |
| 145 | + } |
| 146 | + }); |
118 | 147 | } else {
|
119 | 148 | this.logger.debug(this.connectionContext.getMessage("Skipping commit transaction because status is {}"), transactionStatus);
|
120 | 149 | return Mono.empty();
|
@@ -358,9 +387,21 @@ private <T> Mono<T> withTransactionStatus(Function<TransactionStatus, T> f) {
|
358 | 387 |
|
359 | 388 | @SuppressWarnings("unchecked")
|
360 | 389 | private <T> Publisher<T> exchange(String sql) {
|
361 |
| - ExceptionFactory exceptionFactory = ExceptionFactory.withSql(sql); |
| 390 | + AtomicReference<R2dbcException> ref = new AtomicReference<>(); |
362 | 391 | return (Publisher<T>) SimpleQueryMessageFlow.exchange(this.client, sql)
|
363 |
| - .handle(exceptionFactory::handleErrorResponse); |
| 392 | + .handle((backendMessage, synchronousSink) -> { |
| 393 | + |
| 394 | + if (backendMessage instanceof ErrorResponse) { |
| 395 | + ref.set(ExceptionFactory.createException((ErrorResponse) backendMessage, sql)); |
| 396 | + } else { |
| 397 | + synchronousSink.next(backendMessage); |
| 398 | + } |
| 399 | + }) |
| 400 | + .doOnComplete(() -> { |
| 401 | + if (ref.get() != null) { |
| 402 | + throw ref.get(); |
| 403 | + } |
| 404 | + }); |
364 | 405 | }
|
365 | 406 |
|
366 | 407 | /**
|
|
0 commit comments