Skip to content

Commit a70beb3

Browse files
committed
HHH-18900 adding support and test correction for mariadb 11.6.2 snapshot isolation
1 parent c337381 commit a70beb3

File tree

8 files changed

+84
-11
lines changed

8 files changed

+84
-11
lines changed

hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.sql.DatabaseMetaData;
88
import java.sql.SQLException;
99

10+
import org.hibernate.PessimisticLockException;
1011
import org.hibernate.boot.model.FunctionContributions;
1112
import org.hibernate.boot.model.TypeContributions;
1213
import org.hibernate.dialect.aggregate.AggregateSupport;
@@ -21,6 +22,10 @@
2122
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
2223
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
2324
import org.hibernate.engine.spi.SessionFactoryImplementor;
25+
import org.hibernate.exception.ConstraintViolationException;
26+
import org.hibernate.exception.LockAcquisitionException;
27+
import org.hibernate.exception.LockTimeoutException;
28+
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
2429
import org.hibernate.query.sqm.CastType;
2530
import org.hibernate.service.ServiceRegistry;
2631
import org.hibernate.sql.ast.SqlAstTranslator;
@@ -37,6 +42,7 @@
3742
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
3843
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
3944

45+
import static org.hibernate.internal.util.JdbcExceptionHelper.extractSqlState;
4046
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC;
4147
import static org.hibernate.type.SqlTypes.GEOMETRY;
4248
import static org.hibernate.type.SqlTypes.OTHER;
@@ -313,4 +319,44 @@ public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, D
313319
public String getDual() {
314320
return "dual";
315321
}
322+
323+
@Override
324+
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
325+
return (sqlException, message, sql) -> {
326+
switch ( sqlException.getErrorCode() ) {
327+
// If @@innodb_snapshot_isolation is set (default since 11.6.2),
328+
// if an attempt to acquire a lock on a record that does not exist in the current read view is made,
329+
// an error DB_RECORD_CHANGED will be raised.
330+
case 1020:
331+
return new LockAcquisitionException( message, sqlException, sql );
332+
case 1205:
333+
case 3572:
334+
return new PessimisticLockException( message, sqlException, sql );
335+
case 1207:
336+
case 1206:
337+
return new LockAcquisitionException( message, sqlException, sql );
338+
case 1062:
339+
// Unique constraint violation
340+
return new ConstraintViolationException(
341+
message,
342+
sqlException,
343+
sql,
344+
ConstraintViolationException.ConstraintKind.UNIQUE,
345+
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
346+
);
347+
}
348+
349+
final String sqlState = extractSqlState( sqlException );
350+
if ( sqlState != null ) {
351+
switch ( sqlState ) {
352+
case "41000":
353+
return new LockTimeoutException( message, sqlException, sql );
354+
case "40001":
355+
return new LockAcquisitionException( message, sqlException, sql );
356+
}
357+
}
358+
359+
return null;
360+
};
361+
}
316362
}

hibernate-core/src/test/java/org/hibernate/orm/test/batch/BatchOptimisticLockingTest.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.hibernate.cfg.AvailableSettings;
1515
import org.hibernate.dialect.CockroachDialect;
1616

17+
import org.hibernate.dialect.MariaDBDialect;
1718
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
1819
import org.junit.Test;
1920

@@ -107,10 +108,19 @@ public void testBatchAndOptimisticLocking() {
107108
}
108109
else {
109110
assertEquals( OptimisticLockException.class, expected.getClass() );
110-
assertTrue(
111-
expected.getMessage()
112-
.startsWith("Batch update returned unexpected row count from update 1 (expected row count 1 but was 0) [update Person set name=?,version=? where id=? and version=?]")
113-
);
111+
112+
if ( getDialect() instanceof MariaDBDialect && getDialect().getVersion().isAfter( 11, 6, 2 )) {
113+
assertTrue(
114+
expected.getMessage()
115+
.contains( "Record has changed since last read in table 'Person'" )
116+
);
117+
} else {
118+
assertTrue(
119+
expected.getMessage()
120+
.startsWith(
121+
"Batch update returned unexpected row count from update 1 (expected row count 1 but was 0) [update Person set name=?,version=? where id=? and version=?]" )
122+
);
123+
}
114124
}
115125
}
116126
}

hibernate-core/src/test/java/org/hibernate/orm/test/batch/BatchUpdateAndVersionTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import org.hibernate.StaleObjectStateException;
1414
import org.hibernate.cfg.AvailableSettings;
1515

16+
import org.hibernate.dialect.MariaDBDialect;
17+
import org.hibernate.exception.LockAcquisitionException;
1618
import org.hibernate.exception.TransactionSerializationException;
1719
import org.hibernate.testing.orm.junit.JiraKey;
1820
import org.hibernate.testing.orm.junit.DomainModel;
@@ -32,6 +34,7 @@
3234
import jakarta.persistence.Version;
3335

3436
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
37+
import static org.hibernate.testing.orm.junit.DialectContext.getDialect;
3538
import static org.junit.jupiter.api.Assertions.assertTrue;
3639
import static org.junit.jupiter.api.Assertions.fail;
3740

@@ -136,7 +139,12 @@ public void testFailedUpdate(SessionFactoryScope scope) {
136139
fail();
137140
}
138141
catch (OptimisticLockException ole) {
139-
assertTrue( ole.getCause() instanceof StaleObjectStateException );
142+
if (getDialect() instanceof MariaDBDialect && getDialect().getVersion().isAfter( 11, 6, 2 )) {
143+
// if @@innodb_snapshot_isolation is set, database throw an exception if record is not available anymore
144+
assertTrue( ole.getCause() instanceof LockAcquisitionException );
145+
} else {
146+
assertTrue( ole.getCause() instanceof StaleObjectStateException );
147+
}
140148
}
141149
//CockroachDB errors with a Serialization Exception
142150
catch (RollbackException rbe) {

hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/RepeatableReadTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
1313
import org.hibernate.cfg.AvailableSettings;
1414
import org.hibernate.dialect.CockroachDialect;
15+
import org.hibernate.dialect.MariaDBDialect;
1516
import org.hibernate.dialect.SQLServerDialect;
1617
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
1718
import org.hibernate.exception.SQLGrammarException;
@@ -105,6 +106,7 @@ public void testStaleVersionedInstanceFoundInQueryResult() {
105106

106107
@Test
107108
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach uses SERIALIZABLE by default and fails to acquire a write lock after a TX in between committed changes to a row")
109+
@SkipForDialect(dialectClass = MariaDBDialect.class, majorVersion = 11, minorVersion = 6, microVersion = 2, reason = "MariaDB will throw an error DB_RECORD_CHANGED when acquiring a lock on a record that have changed")
108110
public void testStaleVersionedInstanceFoundOnLock() {
109111
if ( !readCommittedIsolationMaintained( "repeatable read tests" ) ) {
110112
return;
@@ -228,6 +230,7 @@ public void testStaleNonVersionedInstanceFoundInQueryResult() {
228230

229231
@Test
230232
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach uses SERIALIZABLE by default and fails to acquire a write lock after a TX in between committed changes to a row")
233+
@SkipForDialect(dialectClass = MariaDBDialect.class, majorVersion = 11, minorVersion = 6, microVersion = 2, reason = "MariaDB will throw an error DB_RECORD_CHANGED when acquiring a lock on a record that have changed")
231234
public void testStaleNonVersionedInstanceFoundOnLock() {
232235
if ( !readCommittedIsolationMaintained( "repeatable read tests" ) ) {
233236
return;

hibernate-core/src/test/java/org/hibernate/orm/test/locking/OptimisticAndPessimisticLockTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import org.hibernate.LockMode;
1212
import org.hibernate.dialect.CockroachDialect;
13-
13+
import org.hibernate.dialect.MariaDBDialect;
1414
import org.hibernate.testing.orm.junit.DomainModel;
1515
import org.hibernate.testing.orm.junit.JiraKey;
1616
import org.hibernate.testing.orm.junit.SessionFactory;
@@ -30,6 +30,7 @@
3030
@SessionFactory
3131
@JiraKey("HHH-16461")
3232
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "CockroachDB uses SERIALIZABLE isolation, and does not support this")
33+
@SkipForDialect(dialectClass = MariaDBDialect.class, majorVersion = 11, minorVersion = 6, microVersion = 2, reason = "MariaDB will throw an error DB_RECORD_CHANGED when acquiring a lock on a record that have changed")
3334
public class OptimisticAndPessimisticLockTest {
3435

3536
public Stream<LockMode> pessimisticLockModes() {

hibernate-core/src/test/java/org/hibernate/orm/test/locking/OptimisticLockTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
import jakarta.persistence.Version;
1111
import org.hibernate.annotations.OptimisticLock;
1212
import org.hibernate.dialect.CockroachDialect;
13+
import org.hibernate.dialect.MariaDBDialect;
1314
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
14-
import org.hibernate.testing.SkipForDialect;
15+
import org.hibernate.testing.orm.junit.SkipForDialect;
1516
import org.junit.Test;
1617

1718
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
@@ -29,7 +30,8 @@ protected Class<?>[] getAnnotatedClasses() {
2930
}
3031

3132
@Test
32-
@SkipForDialect(value = CockroachDialect.class, comment = "Fails at SERIALIZABLE isolation")
33+
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Fails at SERIALIZABLE isolation")
34+
@SkipForDialect(dialectClass = MariaDBDialect.class, majorVersion = 11, minorVersion = 6, microVersion = 2, reason = "MariaDB will throw an error DB_RECORD_CHANGED when acquiring a lock on a record that have changed")
3335
public void test() {
3436
doInJPA(this::entityManagerFactory, entityManager -> {
3537
Phone phone = new Phone();

hibernate-core/src/test/java/org/hibernate/orm/test/optlock/OptimisticLockTest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.hibernate.StaleStateException;
1414
import org.hibernate.dialect.CockroachDialect;
1515
import org.hibernate.dialect.Dialect;
16+
import org.hibernate.dialect.MariaDBDialect;
1617
import org.hibernate.dialect.SQLServerDialect;
1718

1819
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
@@ -23,6 +24,7 @@
2324
import org.junit.jupiter.api.AfterEach;
2425
import org.junit.jupiter.api.Test;
2526

27+
import static org.hibernate.testing.orm.junit.DialectContext.getDialect;
2628
import static org.junit.jupiter.api.Assertions.fail;
2729

2830
/**
@@ -189,8 +191,9 @@ else if ( dialect instanceof CockroachDialect && ( (JDBCException) cause ).getSQ
189191
"40001" ) ) {
190192
// CockroachDB always runs in SERIALIZABLE isolation, and uses SQL state 40001 to indicate
191193
// serialization failure.
192-
}
193-
else {
194+
} else if (dialect instanceof MariaDBDialect && getDialect().getVersion().isAfter( 11, 6, 2 )) {
195+
// Mariadb snapshot_isolation throws error
196+
} else {
194197
throw e;
195198
}
196199
}

nightly.Jenkinsfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ stage('Configure') {
2929
// Minimum supported versions
3030
new BuildEnvironment( dbName: 'hsqldb_2_6' ),
3131
new BuildEnvironment( dbName: 'mysql_8_0' ),
32-
new BuildEnvironment( dbName: 'mariadb_11_7' ),
32+
new BuildEnvironment( dbName: 'mariadb_10_5' ),
3333
new BuildEnvironment( dbName: 'postgresql_12' ),
3434
new BuildEnvironment( dbName: 'edb_12' ),
3535
new BuildEnvironment( dbName: 'db2_10_5', longRunning: true ),

0 commit comments

Comments
 (0)