From 11ae0f72c833dc5be990190e86eb5d88e570d010 Mon Sep 17 00:00:00 2001 From: Brett Meyer Date: Fri, 26 Jun 2015 15:19:20 -0400 Subject: [PATCH] HHH-9166 handle nested exceptions with TemplatedViolatedConstraintNameExtracter --- .../org/hibernate/dialect/Cache71Dialect.java | 12 ++--- .../java/org/hibernate/dialect/H2Dialect.java | 10 ++-- .../org/hibernate/dialect/HSQLDialect.java | 15 +++--- .../hibernate/dialect/InformixDialect.java | 10 ++-- .../org/hibernate/dialect/MySQL5Dialect.java | 24 ++++------ .../hibernate/dialect/Oracle8iDialect.java | 17 +++---- .../org/hibernate/dialect/Oracle9Dialect.java | 17 ++++--- .../dialect/PostgreSQL81Dialect.java | 46 +++++++++---------- .../hibernate/dialect/Teradata14Dialect.java | 2 +- ...platedViolatedConstraintNameExtracter.java | 27 +++++++++++ .../dialect/PostgreSQL81DialectTestCase.java | 23 +++++++--- 11 files changed, 116 insertions(+), 87 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java index b314d8ddcc..94256d808e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Cache71Dialect.java @@ -6,11 +6,6 @@ */ package org.hibernate.dialect; -import java.sql.CallableStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; - import org.hibernate.LockMode; import org.hibernate.MappingException; import org.hibernate.cfg.Environment; @@ -47,6 +42,11 @@ import org.hibernate.sql.CacheJoinFragment; import org.hibernate.sql.JoinFragment; import org.hibernate.type.StandardBasicTypes; +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + /** * Caché 2007.1 dialect. * @@ -669,7 +669,7 @@ public class Cache71Dialect extends Dialect { */ public static final ViolatedConstraintNameExtracter EXTRACTER = new TemplatedViolatedConstraintNameExtracter() { @Override - public String extractConstraintName(SQLException sqle) { + protected String doExtractConstraintName(SQLException sqle) throws NumberFormatException { return extractUsingTemplate( "constraint (", ") violated", sqle.getMessage() ); } }; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index 42597e9823..17214160ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -6,9 +6,6 @@ */ package org.hibernate.dialect; -import java.sql.SQLException; -import java.sql.Types; - import org.hibernate.JDBCException; import org.hibernate.PessimisticLockException; import org.hibernate.boot.TempTableDdlTransactionHandling; @@ -37,9 +34,11 @@ import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorH2 import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.type.StandardBasicTypes; - import org.jboss.logging.Logger; +import java.sql.SQLException; +import java.sql.Types; + /** * A dialect compatible with the H2 database. * @@ -335,7 +334,8 @@ public class H2Dialect extends Dialect { * @param sqle The exception that was the result of the constraint violation. * @return The extracted constraint name. */ - public String extractConstraintName(SQLException sqle) { + @Override + protected String doExtractConstraintName(SQLException sqle) throws NumberFormatException { String constraintName = null; // 23000: Check constraint violation: {0} // 23001: Unique index or primary key violation: {0} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java index 7f0265d514..99e60e3a2a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -6,11 +6,6 @@ */ package org.hibernate.dialect; -import java.io.Serializable; -import java.sql.SQLException; -import java.sql.Types; -import java.util.Locale; - import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.MappingException; @@ -46,9 +41,13 @@ import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.persister.entity.Lockable; import org.hibernate.type.StandardBasicTypes; - import org.jboss.logging.Logger; +import java.io.Serializable; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Locale; + /** * An SQL dialect compatible with HSQLDB (HyperSQL). *

@@ -393,7 +392,7 @@ public class HSQLDialect extends Dialect { private static final ViolatedConstraintNameExtracter EXTRACTER_18 = new TemplatedViolatedConstraintNameExtracter() { @Override - public String extractConstraintName(SQLException sqle) { + protected String doExtractConstraintName(SQLException sqle) throws NumberFormatException { String constraintName = null; final int errorCode = JdbcExceptionHelper.extractErrorCode( sqle ); @@ -430,7 +429,7 @@ public class HSQLDialect extends Dialect { */ private static final ViolatedConstraintNameExtracter EXTRACTER_20 = new TemplatedViolatedConstraintNameExtracter() { @Override - public String extractConstraintName(SQLException sqle) { + protected String doExtractConstraintName(SQLException sqle) throws NumberFormatException { String constraintName = null; final int errorCode = JdbcExceptionHelper.extractErrorCode( sqle ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java index 373cba17bc..61af2f2c70 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java @@ -6,10 +6,6 @@ */ package org.hibernate.dialect; -import java.sql.SQLException; -import java.sql.Types; -import java.util.Locale; - import org.hibernate.MappingException; import org.hibernate.dialect.function.VarArgsSQLFunction; import org.hibernate.dialect.pagination.FirstLimitHandler; @@ -26,6 +22,10 @@ import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.type.StandardBasicTypes; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Locale; + /** * Informix dialect.
*
@@ -223,7 +223,7 @@ public class InformixDialect extends Dialect { private static final ViolatedConstraintNameExtracter EXTRACTER = new TemplatedViolatedConstraintNameExtracter() { @Override - public String extractConstraintName(SQLException sqle) { + protected String doExtractConstraintName(SQLException sqle) throws NumberFormatException { String constraintName = null; final int errorCode = JdbcExceptionHelper.extractErrorCode( sqle ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQL5Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQL5Dialect.java index 640157a419..9a69635694 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQL5Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQL5Dialect.java @@ -6,13 +6,13 @@ */ package org.hibernate.dialect; -import java.sql.SQLException; -import java.sql.Types; - import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtracter; import org.hibernate.exception.spi.ViolatedConstraintNameExtracter; import org.hibernate.internal.util.JdbcExceptionHelper; +import java.sql.SQLException; +import java.sql.Types; + /** * An SQL dialect for MySQL 5.x specific features. * @@ -38,17 +38,13 @@ public class MySQL5Dialect extends MySQLDialect { private static final ViolatedConstraintNameExtracter EXTRACTER = new TemplatedViolatedConstraintNameExtracter() { - public String extractConstraintName(SQLException sqle) { - try { - final int sqlState = Integer.valueOf( JdbcExceptionHelper.extractSqlState( sqle ) ).intValue(); - switch ( sqlState ) { - case 23000: - return extractUsingTemplate( " for key '", "'", sqle.getMessage() ); - default: - return null; - } - } - catch ( NumberFormatException nfe ) { + @Override + protected String doExtractConstraintName(SQLException sqle) throws NumberFormatException { + final int sqlState = Integer.valueOf( JdbcExceptionHelper.extractSqlState( sqle ) ).intValue(); + switch ( sqlState ) { + case 23000: + return extractUsingTemplate( " for key '", "'", sqle.getMessage() ); + default: return null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java index 2dfc03ef25..a6d7b8e0c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java @@ -6,13 +6,6 @@ */ package org.hibernate.dialect; -import java.sql.CallableStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; -import java.util.List; -import java.util.Locale; - import org.hibernate.JDBCException; import org.hibernate.QueryTimeoutException; import org.hibernate.annotations.common.util.StringHelper; @@ -47,6 +40,13 @@ import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.sql.BitTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; +import java.util.Locale; + /** * A dialect for Oracle 8i. * @@ -469,7 +469,8 @@ public class Oracle8iDialect extends Dialect { * @param sqle The exception that was the result of the constraint violation. * @return The extracted constraint name. */ - public String extractConstraintName(SQLException sqle) { + @Override + protected String doExtractConstraintName(SQLException sqle) throws NumberFormatException { final int errorCode = JdbcExceptionHelper.extractErrorCode( sqle ); if ( errorCode == 1 || errorCode == 2291 || errorCode == 2292 ) { return extractUsingTemplate( "(", ")", sqle.getMessage() ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle9Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle9Dialect.java index db227687f4..42a626423a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle9Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle9Dialect.java @@ -6,12 +6,6 @@ */ package org.hibernate.dialect; -import java.sql.CallableStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; -import java.util.Locale; - import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.NoArgSQLFunction; import org.hibernate.dialect.function.NvlFunction; @@ -21,15 +15,20 @@ import org.hibernate.dialect.function.VarArgsSQLFunction; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtracter; import org.hibernate.exception.spi.ViolatedConstraintNameExtracter; import org.hibernate.hql.spi.id.IdTableSupportStandardImpl; -import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy; +import org.hibernate.hql.spi.id.global.GlobalTemporaryTableBulkIdStrategy; import org.hibernate.hql.spi.id.local.AfterUseAction; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.type.StandardBasicTypes; - import org.jboss.logging.Logger; +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Locale; + /** * An SQL dialect for Oracle 9 (uses ANSI-style syntax where possible). * @@ -299,7 +298,7 @@ public class Oracle9Dialect extends Dialect { private static final ViolatedConstraintNameExtracter EXTRACTER = new TemplatedViolatedConstraintNameExtracter() { @Override - public String extractConstraintName(SQLException sqle) { + protected String doExtractConstraintName(SQLException sqle) throws NumberFormatException { final int errorCode = JdbcExceptionHelper.extractErrorCode( sqle ); if ( errorCode == 1 || errorCode == 2291 || errorCode == 2292 ) { return extractUsingTemplate( "constraint (", ") violated", sqle.getMessage() ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java index fc4dbaff1a..35e7c3ee2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java @@ -6,11 +6,6 @@ */ package org.hibernate.dialect; -import java.sql.CallableStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; - import org.hibernate.JDBCException; import org.hibernate.LockOptions; import org.hibernate.PessimisticLockException; @@ -41,6 +36,11 @@ import org.hibernate.type.descriptor.sql.BlobTypeDescriptor; import org.hibernate.type.descriptor.sql.ClobTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + /** * An SQL dialect for Postgres *

@@ -406,26 +406,22 @@ public class PostgreSQL81Dialect extends Dialect { * Orginally contributed by Denny Bartelt. */ private static final ViolatedConstraintNameExtracter EXTRACTER = new TemplatedViolatedConstraintNameExtracter() { - public String extractConstraintName(SQLException sqle) { - try { - final int sqlState = Integer.valueOf( JdbcExceptionHelper.extractSqlState( sqle ) ); - switch (sqlState) { - // CHECK VIOLATION - case 23514: return extractUsingTemplate( "violates check constraint \"","\"", sqle.getMessage() ); - // UNIQUE VIOLATION - case 23505: return extractUsingTemplate( "violates unique constraint \"","\"", sqle.getMessage() ); - // FOREIGN KEY VIOLATION - case 23503: return extractUsingTemplate( "violates foreign key constraint \"","\"", sqle.getMessage() ); - // NOT NULL VIOLATION - case 23502: return extractUsingTemplate( "null value in column \"","\" violates not-null constraint", sqle.getMessage() ); - // TODO: RESTRICT VIOLATION - case 23001: return null; - // ALL OTHER - default: return null; - } - } - catch (NumberFormatException nfe) { - return null; + @Override + protected String doExtractConstraintName(SQLException sqle) throws NumberFormatException { + final int sqlState = Integer.valueOf( JdbcExceptionHelper.extractSqlState( sqle ) ); + switch (sqlState) { + // CHECK VIOLATION + case 23514: return extractUsingTemplate( "violates check constraint \"","\"", sqle.getMessage() ); + // UNIQUE VIOLATION + case 23505: return extractUsingTemplate( "violates unique constraint \"","\"", sqle.getMessage() ); + // FOREIGN KEY VIOLATION + case 23503: return extractUsingTemplate( "violates foreign key constraint \"","\"", sqle.getMessage() ); + // NOT NULL VIOLATION + case 23502: return extractUsingTemplate( "null value in column \"","\" violates not-null constraint", sqle.getMessage() ); + // TODO: RESTRICT VIOLATION + case 23001: return null; + // ALL OTHER + default: return null; } } }; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Teradata14Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Teradata14Dialect.java index 4fd6323058..a7369bb373 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Teradata14Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Teradata14Dialect.java @@ -144,7 +144,7 @@ public class Teradata14Dialect extends TeradataDialect { * @return The extracted constraint name. */ @Override - public String extractConstraintName(SQLException sqle) { + protected String doExtractConstraintName(SQLException sqle) throws NumberFormatException { String constraintName = null; int errorCode = sqle.getErrorCode(); diff --git a/hibernate-core/src/main/java/org/hibernate/exception/spi/TemplatedViolatedConstraintNameExtracter.java b/hibernate-core/src/main/java/org/hibernate/exception/spi/TemplatedViolatedConstraintNameExtracter.java index 7cc5d6401a..067ab24589 100644 --- a/hibernate-core/src/main/java/org/hibernate/exception/spi/TemplatedViolatedConstraintNameExtracter.java +++ b/hibernate-core/src/main/java/org/hibernate/exception/spi/TemplatedViolatedConstraintNameExtracter.java @@ -6,14 +6,41 @@ */ package org.hibernate.exception.spi; +import java.sql.SQLException; + /** * Knows how to extract a violated constraint name from an error message based on the * fact that the constraint name is templated within the message. * * @author Steve Ebersole + * @author Brett Meyer */ public abstract class TemplatedViolatedConstraintNameExtracter implements ViolatedConstraintNameExtracter { + @Override + public String extractConstraintName(SQLException sqle) { + try { + String constraintName = null; + + // handle nested exceptions + do { + constraintName = doExtractConstraintName(sqle); + if (sqle.getNextException() == null + || sqle.getNextException() == sqle) { + break; + } else { + sqle = sqle.getNextException(); + } + } while (constraintName == null); + + return constraintName; + } catch (NumberFormatException nfe) { + return null; + } + } + + protected abstract String doExtractConstraintName(SQLException sqle) throws NumberFormatException; + /** * Extracts the constraint name based on a template (i.e., templateStartconstraintNametemplateEnd). * diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/PostgreSQL81DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/PostgreSQL81DialectTestCase.java index 0cc2e726e7..0524d8a572 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/PostgreSQL81DialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/PostgreSQL81DialectTestCase.java @@ -6,11 +6,6 @@ */ package org.hibernate.dialect; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.sql.SQLException; - import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.LockOptions; @@ -21,13 +16,19 @@ import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; +import java.sql.BatchUpdateException; +import java.sql.SQLException; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; /** * Testing of patched support for PostgreSQL Lock error detection. HHH-7251 * * @author Bryan Varner */ -@TestForIssue( jiraKey = "HHH-7251" ) public class PostgreSQL81DialectTestCase extends BaseUnitTestCase { @Test @@ -67,4 +68,14 @@ public class PostgreSQL81DialectTestCase extends BaseUnitTestCase { forUpdateClause = dialect.getForUpdateString("tableAlias1,tableAlias2", lockOptions); assertTrue("for update of tableAlias1,tableAlias2".equals(forUpdateClause)); } + + @Test + public void testExtractConstraintName() { + PostgreSQL81Dialect dialect = new PostgreSQL81Dialect(); + SQLException psqlException = new java.sql.SQLException("ERROR: duplicate key value violates unique constraint \"uk_4bm1x2ultdmq63y3h5r3eg0ej\" Detail: Key (username, server_config)=(user, 1) already exists.", "23505"); + BatchUpdateException batchUpdateException = new BatchUpdateException("Concurrent Error", "23505", null); + batchUpdateException.setNextException(psqlException); + String constraintName = dialect.getViolatedConstraintNameExtracter().extractConstraintName(batchUpdateException); + assertThat(constraintName, is("uk_4bm1x2ultdmq63y3h5r3eg0ej")); + } }