Skip to content

Commit

Permalink
fix some constraint name extractors, and improve matching of constrai…
Browse files Browse the repository at this point in the history
…nt name in 'on conflict on constraint'

add a test
  • Loading branch information
gavinking committed Mar 4, 2024
1 parent 7aba13e commit a84ba5c
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -709,13 +709,16 @@ public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
// 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,12 @@ public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
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;
}
Expand All @@ -728,18 +733,12 @@ public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.function.BiConsumer;
import java.util.function.Function;

Expand Down Expand Up @@ -109,10 +108,11 @@ public int execute(
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;
}
}
Expand All @@ -124,4 +124,11 @@ public int execute(
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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
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;
Expand All @@ -16,17 +18,28 @@

@SessionFactory
@DomainModel(annotatedClasses = InsertConflictOnConstraintTest.Constrained.class)
@RequiresDialect(PostgreSQLDialect.class)
public class InsertConflictOnConstraintTest {
@Test void test(SessionFactoryScope scope) {

@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 constrained_count_name_key do update set count = 96").executeUpdate());
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
Expand Down

1 comment on commit a84ba5c

@gavinking
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.