fix some constraint name extractors, and improve matching of constraint name in 'on conflict on constraint'

add a test
This commit is contained in:
Gavin King 2024-03-04 16:57:41 +01:00
parent 7aba13ee47
commit a84ba5c8c9
4 changed files with 45 additions and 23 deletions

View File

@ -709,13 +709,16 @@ public class H2Dialect extends Dialect {
// 23001: Unique index or primary key violation: {0}
if ( sqle.getSQLState().startsWith( "23" ) ) {
final String message = sqle.getMessage();
final int idx = message.indexOf( "violation: " );
if ( idx > 0 ) {
String constraintName = message.substring( idx + "violation: ".length() );
final int i = message.indexOf( "violation: " );
if ( i > 0 ) {
String constraintDescription =
message.substring( i + "violation: ".length() )
.replace( "\"", "" );
if ( sqle.getSQLState().equals( "23506" ) ) {
constraintName = constraintName.substring( 1, constraintName.indexOf( ':' ) );
constraintDescription = constraintDescription.substring( 1, constraintDescription.indexOf( ':' ) );
}
return constraintName;
final int j = constraintDescription.indexOf(" ON ");
return j>0 ? constraintDescription.substring(0, j) : constraintDescription;
}
}
return null;

View File

@ -707,7 +707,12 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
switch ( JdbcExceptionHelper.extractErrorCode( sqle ) ) {
case 2627:
case 2601:
return extractUsingTemplate( "'", "'", sqle.getMessage() );
String message = sqle.getMessage();
int i = message.indexOf("unique index ");
if (i>0) {
message = message.substring(i);
}
return extractUsingTemplate( "'", "'", message);
default:
return null;
}
@ -728,18 +733,12 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
case 1222:
return new LockTimeoutException( message, sqlException, sql );
case 2627:
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
case 2601:
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
default:

View File

@ -8,7 +8,6 @@ package org.hibernate.sql.exec.internal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.function.BiConsumer;
import java.util.function.Function;
@ -109,10 +108,11 @@ public class StandardJdbcMutationExecutor implements JdbcMutationExecutor {
if ( exception instanceof ConstraintViolationException && jdbcMutation instanceof JdbcOperationQueryInsert ) {
final ConstraintViolationException constraintViolationException = (ConstraintViolationException) exception;
if ( constraintViolationException.getKind() == ConstraintViolationException.ConstraintKind.UNIQUE ) {
final String uniqueConstraintNameThatMayFail = ( (JdbcOperationQueryInsert) jdbcMutation ).getUniqueConstraintNameThatMayFail();
final JdbcOperationQueryInsert jdbcInsert = (JdbcOperationQueryInsert) jdbcMutation;
final String uniqueConstraintNameThatMayFail = jdbcInsert.getUniqueConstraintNameThatMayFail();
if ( uniqueConstraintNameThatMayFail != null ) {
if ( uniqueConstraintNameThatMayFail.isEmpty()
|| uniqueConstraintNameThatMayFail.equalsIgnoreCase( constraintViolationException.getConstraintName() ) ) {
final String violatedConstraintName = constraintViolationException.getConstraintName();
if ( constraintNameMatches( uniqueConstraintNameThatMayFail, violatedConstraintName ) ) {
return 0;
}
}
@ -124,4 +124,11 @@ public class StandardJdbcMutationExecutor implements JdbcMutationExecutor {
executionContext.afterStatement( logicalConnection );
}
}
private static boolean constraintNameMatches(String uniqueConstraintNameThatMayFail, String violatedConstraintName) {
return uniqueConstraintNameThatMayFail.isEmpty()
|| uniqueConstraintNameThatMayFail.equalsIgnoreCase(violatedConstraintName)
|| violatedConstraintName != null && violatedConstraintName.indexOf('.') > 0
&& uniqueConstraintNameThatMayFail.equalsIgnoreCase(violatedConstraintName.substring(violatedConstraintName.lastIndexOf('.') + 1));
}
}

View File

@ -5,7 +5,9 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.hibernate.testing.orm.junit.SessionFactory;
@ -16,17 +18,28 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
@SessionFactory
@DomainModel(annotatedClasses = InsertConflictOnConstraintTest.Constrained.class)
@RequiresDialect(PostgreSQLDialect.class)
public class InsertConflictOnConstraintTest {
@Test void test(SessionFactoryScope scope) {
scope.inTransaction( s -> s.persist(new Constrained()));
scope.inTransaction( s -> s.createMutationQuery("insert into Constrained(id, name, count) values (4,'Gavin',69) on conflict on constraint constrained_count_name_key do update set count = 96").executeUpdate());
scope.inSession( s -> assertEquals(96, s.createSelectionQuery("select count from Constrained", int.class).getSingleResult()));
@RequiresDialect(PostgreSQLDialect.class)
@Test void testDoUpdate(SessionFactoryScope scope) {
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
scope.inTransaction( s -> s.persist(new Constrained()));
scope.inTransaction( s -> s.createMutationQuery("insert into Constrained(id, name, count) values (4,'Gavin',69) on conflict on constraint count_name_key do update set count = 96").executeUpdate());
scope.inSession( s -> assertEquals(96, s.createSelectionQuery("select count from Constrained", int.class).getSingleResult()));
}
@RequiresDialect( PostgreSQLDialect.class )
@RequiresDialect( OracleDialect.class )
@RequiresDialect( SQLServerDialect.class )
@Test void testDoNothing(SessionFactoryScope scope) {
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
scope.inTransaction( s -> s.persist(new Constrained()));
scope.inTransaction( s -> s.createMutationQuery("insert into Constrained(id, name, count) values (4,'Gavin',69) on conflict on constraint count_name_key do nothing").executeUpdate());
scope.inSession( s -> assertEquals(69, s.createSelectionQuery("select count from Constrained", int.class).getSingleResult()));
}
@Entity(name = "Constrained")
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"count","name"}))
@Table(uniqueConstraints = @UniqueConstraint(name = "count_name_key", columnNames = {"count","name"}))
static class Constrained {
@Id
@GeneratedValue