[jOOQ/jOOQ#14675] Add a DataException for SQLState 22 and a IntegrityConstraintViolationException for SQLState 23

This commit is contained in:
Lukas Eder 2023-02-22 15:31:39 +01:00
parent 8c2cebaee9
commit e35dfdbe04
7 changed files with 220 additions and 10 deletions

View File

@ -37,8 +37,13 @@
*/
package org.jooq.exception;
import static org.jooq.tools.StringUtils.defaultIfNull;
import java.sql.SQLException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import io.r2dbc.spi.R2dbcException;
/**
@ -78,14 +83,15 @@ public class DataAccessException extends RuntimeException {
* <code>DataAccessException</code> was caused by a {@link SQLException} or
* {@link R2dbcException}.
*/
@NotNull
public String sqlState() {
SQLException s = getCause(SQLException.class);
if (s != null)
return s.getSQLState();
return defaultIfNull(s.getSQLState(), "00000");
R2dbcException r = getCause(R2dbcException.class);
if (r != null)
return r.getSqlState();
return defaultIfNull(r.getSqlState(), "00000");
return "00000";
}
@ -96,28 +102,51 @@ public class DataAccessException extends RuntimeException {
* {@link SQLStateClass}, if this <code>DataAccessException</code> was
* caused by a {@link SQLException} or {@link R2dbcException}.
*/
@NotNull
public SQLStateClass sqlStateClass() {
SQLException s = getCause(SQLException.class);
if (s != null)
if (s.getSQLState() != null)
return SQLStateClass.fromCode(s.getSQLState());
else if (s.getSQLState() == null && "org.sqlite.SQLiteException".equals(s.getClass().getName()))
return SQLStateClass.fromSQLiteVendorCode(s.getErrorCode());
return sqlStateClass(s);
R2dbcException r = getCause(R2dbcException.class);
if (r != null)
if (r.getSqlState() != null)
return SQLStateClass.fromCode(r.getSqlState());
return sqlStateClass(r);
return SQLStateClass.NONE;
}
/**
* Decode the {@link SQLException#getSQLState()} into {@link SQLStateClass}.
*/
@NotNull
public static SQLStateClass sqlStateClass(SQLException e) {
if (e.getSQLState() != null)
return SQLStateClass.fromCode(e.getSQLState());
else if (e.getSQLState() == null && "org.sqlite.SQLiteException".equals(e.getClass().getName()))
return SQLStateClass.fromSQLiteVendorCode(e.getErrorCode());
else
return SQLStateClass.NONE;
}
/**
* Decode the {@link R2dbcException#getSqlState()} into
* {@link SQLStateClass}.
*/
@NotNull
public static SQLStateClass sqlStateClass(R2dbcException e) {
if (e.getSqlState() != null)
return SQLStateClass.fromCode(e.getSqlState());
else
return SQLStateClass.NONE;
}
/**
* Decode the {@link SQLException#getSQLState()} or
* {@link R2dbcException#getSqlState()} from {@link #getCause()} into
* {@link SQLStateSubclass}, if this <code>DataAccessException</code> was
* caused by a {@link SQLException} or {@link R2dbcException}.
*/
@NotNull
public SQLStateSubclass sqlStateSubclass() {
return SQLStateSubclass.fromCode(sqlState());
}
@ -131,6 +160,7 @@ public class DataAccessException extends RuntimeException {
* Find a root cause of a given type, or <code>null</code> if no root cause
* of that type was found.
*/
@Nullable
public <T extends Throwable> T getCause(Class<? extends T> type) {
return ExceptionTools.getCause(this, type);
}

View File

@ -0,0 +1,76 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: https://www.jooq.org/legal/licensing
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.exception;
import java.sql.SQLDataException;
import java.sql.SQLException;
import io.r2dbc.spi.R2dbcException;
/**
* The <code>DataException</code> is jOOQ's equivalent of JDBC's
* {@link SQLDataException}.
* <p>
* It is thrown by jOOQ whenever jOOQ detects
* {@link SQLStateClass#C22_DATA_EXCEPTION} from the JDBC driver. Whether this
* SQL state is available is JDBC driver implementation specific.
*
* @author Lukas Eder
*/
public class DataException extends DataAccessException {
/**
* Constructor for DataException.
*
* @param message the detail message
*/
public DataException(String message) {
super(message);
}
/**
* Constructor for DataException.
*
* @param message the detail message
* @param cause the root cause (usually from using a underlying data access
* API such as JDBC)
*/
public DataException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -37,6 +37,8 @@
*/
package org.jooq.exception;
import org.jetbrains.annotations.Nullable;
/**
* @author Lukas Eder
*/
@ -52,6 +54,7 @@ public final class ExceptionTools {
* of that type was found.
*/
@SuppressWarnings("unchecked")
@Nullable
public static <T extends Throwable> T getCause(Throwable t, Class<? extends T> type) {
Throwable next = t.getCause();
Throwable prev;

View File

@ -0,0 +1,74 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: https://www.jooq.org/legal/licensing
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.exception;
import java.sql.SQLIntegrityConstraintViolationException;
/**
* The <code>IntegrityConstraintViolationException</code> is jOOQ's equivalent
* of JDBC's {@link SQLIntegrityConstraintViolationException}.
* <p>
* It is thrown by jOOQ whenever jOOQ detects
* {@link SQLStateClass#C23_INTEGRITY_CONSTRAINT_VIOLATION} from the JDBC
* driver. Whether this SQL state is available is JDBC driver implementation
* specific.
*
* @author Lukas Eder
*/
public class IntegrityConstraintViolationException extends DataAccessException {
/**
* Constructor for IntegrityConstraintViolationException.
*
* @param message the detail message
*/
public IntegrityConstraintViolationException(String message) {
super(message);
}
/**
* Constructor for IntegrityConstraintViolationException.
*
* @param message the detail message
* @param cause the root cause (usually from using a underlying data access
* API such as JDBC)
*/
public IntegrityConstraintViolationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -40,6 +40,8 @@ package org.jooq.exception;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
/**
* The class of the SQL state as specified by the SQL:2011 standard, or by individual
* vendors.
@ -165,10 +167,12 @@ public enum SQLStateClass {
this.className = className;
}
@NotNull
public String className() {
return className;
}
@NotNull
public static SQLStateClass fromCode(String code) {
if (code == null || code.length() < 2)
return SQLStateClass.OTHER;
@ -177,6 +181,7 @@ public enum SQLStateClass {
return result != null ? result : SQLStateClass.OTHER;
}
@NotNull
static SQLStateClass fromSQLiteVendorCode(int errorCode) {
// See https://sqlite.org/c3ref/c_abort.html

View File

@ -88,6 +88,8 @@ import static org.jooq.exception.SQLStateClass.CHZ_REMOTE_DATABASE_ACCESS;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
/**
* The subclass of the SQL state class as specified by the SQL standard, or by individual
* vendors.
@ -460,18 +462,22 @@ public enum SQLStateSubclass {
this.subclass = subclass;
}
@NotNull
public String sqlStateSubclassName() {
return subclass;
}
@NotNull
public SQLStateClass sqlStateClass() {
return clazz;
}
@NotNull
public String sqlStateClassName() {
return sqlStateClass().className();
}
@NotNull
public static SQLStateSubclass fromCode(String code) {
if (code == null || code.length() != 5)
return SQLStateSubclass.OTHER;
@ -488,6 +494,7 @@ public enum SQLStateSubclass {
return SQLStateSubclass.OTHER;
}
@NotNull
static SQLStateSubclass fromSQLiteVendorCode(int errorCode) {
// See https://sqlite.org/c3ref/c_abort.html

View File

@ -88,6 +88,7 @@ import static org.jooq.conf.SettingsTools.getBackslashEscaping;
import static org.jooq.conf.SettingsTools.updatablePrimaryKeys;
import static org.jooq.conf.ThrowExceptions.THROW_FIRST;
import static org.jooq.conf.ThrowExceptions.THROW_NONE;
import static org.jooq.exception.DataAccessException.sqlStateClass;
import static org.jooq.impl.CacheType.REFLECTION_CACHE_GET_ANNOTATED_GETTER;
import static org.jooq.impl.CacheType.REFLECTION_CACHE_GET_ANNOTATED_MEMBERS;
import static org.jooq.impl.CacheType.REFLECTION_CACHE_GET_ANNOTATED_SETTERS;
@ -339,11 +340,14 @@ import org.jooq.conf.Settings;
import org.jooq.conf.SettingsTools;
import org.jooq.conf.ThrowExceptions;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.DataException;
import org.jooq.exception.DataTypeException;
import org.jooq.exception.DetachedException;
import org.jooq.exception.ExceptionTools;
import org.jooq.exception.IntegrityConstraintViolationException;
import org.jooq.exception.MappingException;
import org.jooq.exception.NoDataFoundException;
import org.jooq.exception.SQLStateClass;
import org.jooq.exception.TemplatingException;
import org.jooq.exception.TooManyRowsException;
import org.jooq.impl.QOM.Quantifier;
@ -3412,7 +3416,7 @@ final class Tools {
*/
static final DataAccessException translate(String sql, R2dbcException e) {
if (e != null)
return new DataAccessException("SQL [" + sql + "]; " + e.getMessage(), e);
return translate(sql, e, sqlStateClass(e));
else
return new DataAccessException("SQL [" + sql + "]; Unspecified R2dbcException");
}
@ -3422,11 +3426,22 @@ final class Tools {
*/
static final DataAccessException translate(String sql, SQLException e) {
if (e != null)
return new DataAccessException("SQL [" + sql + "]; " + e.getMessage(), e);
return translate(sql, e, sqlStateClass(e));
else
return new DataAccessException("SQL [" + sql + "]; Unspecified SQLException");
}
private static final DataAccessException translate(String sql, Exception e, SQLStateClass sqlState) {
switch (sqlState) {
case C22_DATA_EXCEPTION:
return new DataException("SQL [" + sql + "]; " + e.getMessage(), e);
case C23_INTEGRITY_CONSTRAINT_VIOLATION:
return new IntegrityConstraintViolationException("SQL [" + sql + "]; " + e.getMessage(), e);
default:
return new DataAccessException("SQL [" + sql + "]; " + e.getMessage(), e);
}
}
/**
* Translate a {@link RuntimeException} to a {@link DataAccessException}
*/