From fee857a08a5ee9d06d43c13a198ff861b55a9ac7 Mon Sep 17 00:00:00 2001 From: Amar Rana-Deshmukh Date: Wed, 26 Aug 2026 12:43:43 +0100 Subject: [PATCH] Added support for postgres cancelled SQLState, so that postgres related timeouts are correctly flagged. --- .../morf/jdbc/DatabaseExceptionHelper.java | 16 ++++++++++- .../jdbc/TestDatabaseExceptionHelper.java | 27 ++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/morf-core/src/main/java/org/alfasoftware/morf/jdbc/DatabaseExceptionHelper.java b/morf-core/src/main/java/org/alfasoftware/morf/jdbc/DatabaseExceptionHelper.java index 552507812..1c3393e62 100755 --- a/morf-core/src/main/java/org/alfasoftware/morf/jdbc/DatabaseExceptionHelper.java +++ b/morf-core/src/main/java/org/alfasoftware/morf/jdbc/DatabaseExceptionHelper.java @@ -15,6 +15,7 @@ package org.alfasoftware.morf.jdbc; +import java.sql.SQLException; import java.sql.SQLTimeoutException; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -37,10 +38,15 @@ public class DatabaseExceptionHelper { */ static final String MYSQL_TIMEOUT_EXCEPTION_NAME = "MySQLTimeoutException"; + /** + * The SQLState code used by postgres when a query has been cancelled. This code has some ambiguity, as queries can + * be cancelled due to a timeout, or a user request, but it's the best indication we have. + */ + private static final String POSTGRES_QUERY_CANCELLED_SQL_STATE = "57014"; /** *

Checks if the throwable was caused by timeout exception.

- * This method has been tested for Oracle and MySQL only and might not work + * This method has been tested for Oracle, MySQL & Postgres only and might not work * for other DB engines. * * @param throwable to check @@ -59,6 +65,14 @@ public boolean isCausedByTimeoutException(Throwable throwable) { if (MYSQL_TIMEOUT_EXCEPTION_NAME.equals(causeThrowable.getClass().getSimpleName())) { return true; } + // Postgres doesn't have a distinct timeout exception class. Instead it throws + // a plain PSQLException with a particular SQLState that needs to be checked + if (causeThrowable instanceof SQLException) { + String sqlState = ((SQLException) causeThrowable).getSQLState(); + if (POSTGRES_QUERY_CANCELLED_SQL_STATE.equals(sqlState)) { + return true; + } + } } return false; } diff --git a/morf-core/src/test/java/org/alfasoftware/morf/jdbc/TestDatabaseExceptionHelper.java b/morf-core/src/test/java/org/alfasoftware/morf/jdbc/TestDatabaseExceptionHelper.java index ba83994fe..0a0533c9b 100755 --- a/morf-core/src/test/java/org/alfasoftware/morf/jdbc/TestDatabaseExceptionHelper.java +++ b/morf-core/src/test/java/org/alfasoftware/morf/jdbc/TestDatabaseExceptionHelper.java @@ -15,7 +15,7 @@ package org.alfasoftware.morf.jdbc; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import java.sql.SQLException; import java.sql.SQLTimeoutException; @@ -40,7 +40,7 @@ public class TestDatabaseExceptionHelper { @Test public void testIsCausedByTimeoutForSQLTimeoutException() { // when then - assertEquals(true, databaseExceptionHelper.isCausedByTimeoutException(new ExtendsSQLTimeoutException())); + assertTrue(databaseExceptionHelper.isCausedByTimeoutException(new ExtendsSQLTimeoutException())); } @@ -51,9 +51,30 @@ public void testIsCausedByTimeoutForSQLTimeoutException() { @Test public void testIsCausedByTimeoutForMySQLTimeoutException() { // when then - assertEquals(true, databaseExceptionHelper.isCausedByTimeoutException(new MySQLTimeoutException())); + assertTrue(databaseExceptionHelper.isCausedByTimeoutException(new MySQLTimeoutException())); } + + /** + * Test if detection works for postgres SQLState based exceptions + */ + @Test + public void testIsCausedByTimeoutForPostgresTimeoutException() { + // when then + assertTrue(databaseExceptionHelper.isCausedByTimeoutException(new SQLException("Timeout", "57014"))); + } + + + /** + * Test if detection works for postgres SQLState based exceptions + */ + @Test + public void testIsNotCausedByTimeout() { + // when then + assertFalse(databaseExceptionHelper.isCausedByTimeoutException(new SQLException("Some other error", "0"))); + } + + /** * Test only generic exception which extends {@link SQLTimeoutException} *