diff --git a/jdbc/src/main/java/tech/ydb/jdbc/context/TableTxExecutor.java b/jdbc/src/main/java/tech/ydb/jdbc/context/TableTxExecutor.java index e5c6683c..47189107 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/context/TableTxExecutor.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/context/TableTxExecutor.java @@ -17,6 +17,7 @@ import tech.ydb.jdbc.YdbTracer; import tech.ydb.jdbc.exception.ExceptionFactory; import tech.ydb.jdbc.exception.YdbConditionallyRetryableException; +import tech.ydb.jdbc.exception.YdbUnavailbaleException; import tech.ydb.jdbc.query.YdbQuery; import tech.ydb.query.QueryStream; import tech.ydb.query.QueryTransaction; @@ -128,7 +129,7 @@ protected void commitImpl(YdbContext ctx, YdbValidator validator, QueryTransacti tracer.query(commitQuery); return query.execute(); }); - } catch (YdbConditionallyRetryableException ex) { + } catch (YdbConditionallyRetryableException | YdbUnavailbaleException ex) { Result res = validateRetryCtx.supplyResult( session -> session.executeDataQuery(validateQuery, TxControl.snapshotRo(), params) ).join(); diff --git a/jdbc/src/main/java/tech/ydb/jdbc/exception/ExceptionFactory.java b/jdbc/src/main/java/tech/ydb/jdbc/exception/ExceptionFactory.java index ced7fa77..8022381a 100644 --- a/jdbc/src/main/java/tech/ydb/jdbc/exception/ExceptionFactory.java +++ b/jdbc/src/main/java/tech/ydb/jdbc/exception/ExceptionFactory.java @@ -32,19 +32,17 @@ public static SQLException createException(String message, UnexpectedResultExcep } // transport problems are translated to SQLTransientConnectionException - if (code == StatusCode.TRANSPORT_UNAVAILABLE || code == StatusCode.UNAVAILABLE) { + if (code == StatusCode.TRANSPORT_UNAVAILABLE) { return new YdbUnavailbaleException(message, sqlState, vendorCode, cause); } // timeouts are translated to SQLTimeoutException - if (code == StatusCode.TIMEOUT || - code == StatusCode.CLIENT_DEADLINE_EXPIRED || - code == StatusCode.CLIENT_DEADLINE_EXCEEDED) { + if (code == StatusCode.CLIENT_DEADLINE_EXPIRED || code == StatusCode.CLIENT_DEADLINE_EXCEEDED) { return new YdbTimeoutException(message, sqlState, vendorCode, cause); } // all others transient problems are translated to base SQLTransientException - if (code.isRetryable(true)) { + if (code.isRetryable(true) || code == StatusCode.TIMEOUT) { return new YdbConditionallyRetryableException(message, sqlState, vendorCode, cause); } diff --git a/jdbc/src/test/java/tech/ydb/jdbc/context/YdbDriverTxValidateTest.java b/jdbc/src/test/java/tech/ydb/jdbc/context/YdbDriverTxValidateTest.java index 82cbcb5b..52423e32 100644 --- a/jdbc/src/test/java/tech/ydb/jdbc/context/YdbDriverTxValidateTest.java +++ b/jdbc/src/test/java/tech/ydb/jdbc/context/YdbDriverTxValidateTest.java @@ -20,6 +20,7 @@ import tech.ydb.jdbc.YdbConnection; import tech.ydb.jdbc.exception.YdbConditionallyRetryableException; import tech.ydb.jdbc.exception.YdbRetryableException; +import tech.ydb.jdbc.exception.YdbTimeoutException; import tech.ydb.jdbc.impl.YdbTracerImpl; import tech.ydb.jdbc.impl.helper.ExceptionAssert; import tech.ydb.jdbc.impl.helper.JdbcConnectionExtention; @@ -155,6 +156,34 @@ public void commitedTxTest() throws SQLException { } } + @Test + public void unavailableTxTest() throws SQLException { + String url = jdbcURL.withArg("withTxValidationTable", "tx1_store").build(); + try (Connection conn = DriverManager.getConnection(url)) { + ErrorTxTracer tracer = YdbTracerImpl.use(new ErrorTxTracer()); + // table was created automatically + assertTxCount("tx1_store", 0); + + conn.setAutoCommit(false); + + conn.createStatement().execute("DELETE FROM tx1_store"); + // throw condintionally retryable exception AFTER commit + tracer.throwErrorOn("<-- Status", Status.of(StatusCode.TRANSPORT_UNAVAILABLE)); + conn.commit(); // no error, tx is validated successfully + + assertTxCount("tx1_store", 1); + + conn.createStatement().execute("DELETE FROM tx1_store"); + // throw condintionally retryable exception BEFORE commit + tracer.throwErrorOn("--> commit-and-store-tx", Status.of(StatusCode.TRANSPORT_UNAVAILABLE)); + ExceptionAssert.sqlRecoverable("Transaction wasn't committed", conn::commit); + + Assert.assertNull(tracer.error); + } finally { + jdbc.connection().createStatement().execute("DROP TABLE tx1_store"); + } + } + @Test public void executeDataQueryTest() throws SQLException { String url = jdbcURL.withArg("withTxValidationTable", "tx1_store").build(); @@ -176,6 +205,11 @@ public void executeDataQueryTest() throws SQLException { YdbRetryableException e2 = Assertions.assertThrows(YdbRetryableException.class, () -> st.execute("DELETE FROM tx1_store")); Assertions.assertEquals(Status.of(StatusCode.ABORTED), e2.getStatus()); + + tracer.throwErrorOn("<-- Status", Status.of(StatusCode.CLIENT_DEADLINE_EXCEEDED)); + YdbTimeoutException e3 = Assertions.assertThrows(YdbTimeoutException.class, + () -> st.execute("DELETE FROM tx1_store")); + Assertions.assertEquals(Status.of(StatusCode.CLIENT_DEADLINE_EXCEEDED), e3.getStatus()); }