Skip to content

Commit

Permalink
Check well-known database error codes in case of generic SQL state 23000
Browse files Browse the repository at this point in the history
  • Loading branch information
jhoeller committed Dec 23, 2022
1 parent 697292b commit a644245
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 4 deletions.
Expand Up @@ -89,7 +89,7 @@ else if (ex instanceof SQLNonTransientException) {
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
}
if (ex instanceof SQLIntegrityConstraintViolationException) {
if ("23505".equals(ex.getSQLState())) {
if (SQLStateSQLExceptionTranslator.indicatesDuplicateKey(ex.getSQLState(), ex.getErrorCode())) {
return new DuplicateKeyException(buildMessage(task, sql, ex), ex);
}
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
Expand Down
Expand Up @@ -99,7 +99,7 @@ protected DataAccessException doTranslate(String task, @Nullable String sql, SQL
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
}
else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) {
if ("23505".equals(sqlState)) {
if (indicatesDuplicateKey(sqlState, ex.getErrorCode())) {
return new DuplicateKeyException(buildMessage(task, sql, ex), ex);
}
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
Expand Down Expand Up @@ -148,4 +148,19 @@ private String getSqlState(SQLException ex) {
return sqlState;
}

/**
* Check whether the given SQL state (and the associated error code in case
* of a generic SQL state value) indicate a duplicate key exception:
* either SQL state 23505 as a specific indication, or the generic SQL state
* 23000 with well-known vendor codes (1 for Oracle, 1062 for MySQL/MariaDB,
* 2627 for MS SQL Server).
* @param sqlState the SQL state value
* @param errorCode the error code value
*/
static boolean indicatesDuplicateKey(@Nullable String sqlState, int errorCode) {
return ("23505".equals(sqlState) ||
("23000".equals(sqlState) &&
(errorCode == 1 || errorCode == 1062 || errorCode == 2627)));
}

}
Expand Up @@ -57,6 +57,9 @@ public void exceptionClassTranslation() {
doTest(new SQLFeatureNotSupportedException("", "", 0), InvalidDataAccessApiUsageException.class);
doTest(new SQLIntegrityConstraintViolationException("", "", 0), DataIntegrityViolationException.class);
doTest(new SQLIntegrityConstraintViolationException("", "23505", 0), DuplicateKeyException.class);
doTest(new SQLIntegrityConstraintViolationException("", "23000", 1), DuplicateKeyException.class);
doTest(new SQLIntegrityConstraintViolationException("", "23000", 1062), DuplicateKeyException.class);
doTest(new SQLIntegrityConstraintViolationException("", "23505", 2627), DuplicateKeyException.class);
doTest(new SQLInvalidAuthorizationSpecException("", "", 0), PermissionDeniedDataAccessException.class);
doTest(new SQLNonTransientConnectionException("", "", 0), DataAccessResourceFailureException.class);
doTest(new SQLRecoverableException("", "", 0), RecoverableDataAccessException.class);
Expand Down
Expand Up @@ -61,6 +61,21 @@ public void translateDuplicateKey() {
doTest("23505", DuplicateKeyException.class);
}

@Test
public void translateDuplicateKeyOracle() {
doTest("23000", 1, DuplicateKeyException.class);
}

@Test
public void translateDuplicateKeyMySQL() {
doTest("23000", 1062, DuplicateKeyException.class);
}

@Test
public void translateDuplicateKeyMSSQL() {
doTest("23000", 2627, DuplicateKeyException.class);
}

@Test
public void translateDataAccessResourceFailure() {
doTest("53", DataAccessResourceFailureException.class);
Expand Down Expand Up @@ -105,8 +120,12 @@ public void malformedSqlStateCodes() {


private void doTest(@Nullable String sqlState, @Nullable Class<?> dataAccessExceptionType) {
doTest(sqlState, 0, dataAccessExceptionType);
}

private void doTest(@Nullable String sqlState, int errorCode, @Nullable Class<?> dataAccessExceptionType) {
SQLExceptionTranslator translator = new SQLStateSQLExceptionTranslator();
SQLException ex = new SQLException("reason", sqlState);
SQLException ex = new SQLException("reason", sqlState, errorCode);
DataAccessException dax = translator.translate("task", "SQL", ex);

if (dataAccessExceptionType == null) {
Expand Down
Expand Up @@ -231,7 +231,7 @@ else if (ex instanceof R2dbcNonTransientException) {
return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex);
}
if (ex instanceof R2dbcDataIntegrityViolationException) {
if ("23505".equals(ex.getSqlState())) {
if (indicatesDuplicateKey(ex.getSqlState(), ex.getErrorCode())) {
return new DuplicateKeyException(buildMessage(task, sql, ex), ex);
}
return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
Expand All @@ -246,6 +246,19 @@ else if (ex instanceof R2dbcNonTransientException) {
return new UncategorizedR2dbcException(buildMessage(task, sql, ex), sql, ex);
}

/**
* Check whether the given SQL state (and the associated error code in case
* of a generic SQL state value) indicate a duplicate key exception. See
* {@code org.springframework.jdbc.support.SQLStateSQLExceptionTranslator#indicatesDuplicateKey}.
* @param sqlState the SQL state value
* @param errorCode the error code value
*/
static boolean indicatesDuplicateKey(@Nullable String sqlState, int errorCode) {
return ("23505".equals(sqlState) ||
("23000".equals(sqlState) &&
(errorCode == 1 || errorCode == 1062 || errorCode == 2627)));
}

/**
* Build a message {@code String} for the given {@link R2dbcException}.
* <p>To be called by translator subclasses when creating an instance of a generic
Expand Down
Expand Up @@ -95,6 +95,18 @@ public void shouldTranslateIntegrityViolationException() {
exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23505"));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);

exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23000", 1));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);

exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23000", 1062));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);

exception = ConnectionFactoryUtils.convertR2dbcException("", "",
new R2dbcDataIntegrityViolationException("reason", "23000", 2627));
assertThat(exception).isExactlyInstanceOf(DuplicateKeyException.class);
}

@Test
Expand Down

0 comments on commit a644245

Please sign in to comment.