HHH-18772 introduce AuthException and simplify SQLStateConversionDelegate

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-10-26 12:51:06 +02:00
parent 55255e9d4a
commit b44833b7c9
4 changed files with 90 additions and 81 deletions

View File

@ -0,0 +1,37 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.exception;
import org.hibernate.JDBCException;
import java.sql.SQLException;
/**
* A {@link JDBCException} indicating an authentication or authorization failure.
*
* @since 7.0
*
* @author Gavin King
*/
public class AuthException extends JDBCException {
/**
* Constructor for AuthException.
*
* @param root The underlying exception.
*/
public AuthException(String message, SQLException root) {
super( message, root );
}
/**
* Constructor for AuthException.
*
* @param message Optional message.
* @param root The underlying exception.
*/
public AuthException(String message, SQLException root, String sql) {
super( message, root, sql );
}
}

View File

@ -9,7 +9,7 @@ import java.sql.SQLException;
import org.hibernate.JDBCException;
/**
* Extends {@link JDBCException} indicating that evaluation of the
* A {@link JDBCException} indicating that evaluation of the
* valid SQL statement against the given data resulted in some
* illegal operation, mismatched types or incorrect cardinality.
*
@ -17,7 +17,7 @@ import org.hibernate.JDBCException;
*/
public class DataException extends JDBCException {
/**
* Constructor for JDBCException.
* Constructor for DataException.
*
* @param root The underlying exception.
*/
@ -26,7 +26,7 @@ public class DataException extends JDBCException {
}
/**
* Constructor for JDBCException.
* Constructor for DataException.
*
* @param message Optional message.
* @param root The underlying exception.

View File

@ -5,11 +5,11 @@
package org.hibernate.exception.internal;
import java.sql.SQLException;
import java.util.Set;
import org.hibernate.JDBCException;
import org.hibernate.PessimisticLockException;
import org.hibernate.QueryTimeoutException;
import org.hibernate.exception.AuthException;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.DataException;
import org.hibernate.exception.JDBCConnectionException;
@ -17,108 +17,83 @@ import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.exception.SQLGrammarException;
import org.hibernate.exception.spi.AbstractSQLExceptionConversionDelegate;
import org.hibernate.exception.spi.ConversionContext;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.checkerframework.checker.nullness.qual.Nullable;
import static org.hibernate.internal.util.JdbcExceptionHelper.determineSqlStateClassCode;
import static org.hibernate.internal.util.JdbcExceptionHelper.extractErrorCode;
import static org.hibernate.internal.util.JdbcExceptionHelper.extractSqlState;
/**
* A {@link org.hibernate.exception.spi.SQLExceptionConverter} implementation which performs conversion based
* on the underlying SQLState. Interpretation of a SQL error based on SQLState is not nearly as accurate as
* using the ErrorCode (which is, however, vendor-specific).
* <p>
* SQLState codes are defined by both ANSI SQL specs and X/Open. Some "classes" are shared, others are
* specific to one or another, yet others are custom vendor classes. Unfortunately I have not been able to
* find a "blessed" list of X/Open codes. These codes are cobbled together between ANSI SQL spec and error
*
* @implNote
* SQLState codes are defined by both ANSI SQL specs and X/Open. Some "classes" are shared, others are
* specific to one or another, yet others are custom vendor classes. Unfortunately I have not been able to
* find a "blessed" list of X/Open codes. These codes are cobbled together between ANSI SQL spec and error
* code tables from few vendor's documentation.
*
* @author Steve Ebersole
*/
public class SQLStateConversionDelegate extends AbstractSQLExceptionConversionDelegate {
private static final Set<String> SQL_GRAMMAR_CATEGORIES = buildGrammarCategories();
private static Set<String> buildGrammarCategories() {
return Set.of(
"07", // "dynamic SQL error"
"20",
"2A", // "direct SQL syntax error or access rule violation"
"37", // "dynamic SQL syntax error or access rule violation"
"42", // "syntax error or access rule violation"
"65", // Oracle specific as far as I can tell
"S0" // MySQL specific as far as I can tell
);
}
private static final Set<String> DATA_CATEGORIES = buildDataCategories();
private static Set<String> buildDataCategories() {
return Set.of(
"21", // "cardinality violation"
"22" // "data exception"
);
}
private static final Set<String> INTEGRITY_VIOLATION_CATEGORIES = buildContraintCategories();
private static Set<String> buildContraintCategories() {
return Set.of(
"23", // "integrity constraint violation"
"27", // "triggered data change violation"
"44" // "with check option violation"
);
}
private static final Set<String> CONNECTION_CATEGORIES = buildConnectionCategories();
private static Set<String> buildConnectionCategories() {
return Set.of(
"08" // "connection exception"
);
}
public SQLStateConversionDelegate(ConversionContext conversionContext) {
super( conversionContext );
}
@Override
public @Nullable JDBCException convert(SQLException sqlException, String message, String sql) {
final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException );
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
final String sqlState = extractSqlState( sqlException );
if ( sqlState != null ) {
String sqlStateClassCode = JdbcExceptionHelper.determineSqlStateClassCode( sqlState );
if ( sqlStateClassCode != null ) {
if ( SQL_GRAMMAR_CATEGORIES.contains( sqlStateClassCode ) ) {
switch ( sqlState ) {
case "42501":
return new AuthException( message, sqlException, sql );
case "40001":
return new LockAcquisitionException( message, sqlException, sql );
case "40XL1", "40XL2":
// Derby "A lock could not be obtained within the time requested."
return new PessimisticLockException( message, sqlException, sql );
case "70100":
// MySQL Query execution was interrupted
return new QueryTimeoutException( message, sqlException, sql );
case "72000":
if ( extractErrorCode( sqlException ) == 1013 ) {
// Oracle user requested cancel of current operation
return new QueryTimeoutException( message, sqlException, sql );
}
}
switch ( determineSqlStateClassCode( sqlState ) ) {
case
"07", // "dynamic SQL error"
"20",
"2A", // "direct SQL syntax error or access rule violation"
"37", // "dynamic SQL syntax error or access rule violation"
"42", // "syntax error or access rule violation"
"65", // Oracle specific as far as I can tell
"S0": // MySQL specific as far as I can tell
return new SQLGrammarException( message, sqlException, sql );
}
else if ( INTEGRITY_VIOLATION_CATEGORIES.contains( sqlStateClassCode ) ) {
case
"23", // "integrity constraint violation"
"27", // "triggered data change violation"
"44": // "with check option violation"
final String constraintName = getConversionContext()
.getViolatedConstraintNameExtractor()
.extractConstraintName( sqlException );
return new ConstraintViolationException( message, sqlException, sql, constraintName );
}
else if ( CONNECTION_CATEGORIES.contains( sqlStateClassCode ) ) {
case
"08": // "connection exception"
return new JDBCConnectionException( message, sqlException, sql );
}
else if ( DATA_CATEGORIES.contains( sqlStateClassCode ) ) {
case
"21", // "cardinality violation"
"22": // "data exception"
return new DataException( message, sqlException, sql );
}
}
if ( "40001".equals( sqlState ) ) {
return new LockAcquisitionException( message, sqlException, sql );
}
if ( "40XL1".equals( sqlState ) || "40XL2".equals( sqlState )) {
// Derby "A lock could not be obtained within the time requested."
return new PessimisticLockException( message, sqlException, sql );
}
// MySQL Query execution was interrupted
if ( "70100".equals( sqlState ) ||
// Oracle user requested cancel of current operation
( "72000".equals( sqlState ) && errorCode == 1013 ) ) {
return new QueryTimeoutException( message, sqlException, sql );
case
"28": // "authentication failure"
return new AuthException( message, sqlException, sql );
}
}
return null;
}
}

View File

@ -56,9 +56,6 @@ public final class JdbcExceptionHelper {
}
public static String determineSqlStateClassCode(String sqlState) {
if ( sqlState == null || sqlState.length() < 2 ) {
return sqlState;
}
return sqlState.substring( 0, 2 );
return sqlState == null || sqlState.length() < 2 ? sqlState : sqlState.substring( 0, 2 );
}
}