Skip to content

Commit 15aa501

Browse files
committed
HHH-18900 adding support and test correction for mariadb 11.6.2 snapshot isolation
see https://mariadb.com/kb/en/innodb-system-variables/#innodb_snapshot_isolation
1 parent c337381 commit 15aa501

File tree

10 files changed

+64
-10
lines changed

10 files changed

+64
-10
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
2222
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
2323
import org.hibernate.engine.spi.SessionFactoryImplementor;
24+
import org.hibernate.exception.LockAcquisitionException;
25+
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
2426
import org.hibernate.query.sqm.CastType;
2527
import org.hibernate.service.ServiceRegistry;
2628
import org.hibernate.sql.ast.SqlAstTranslator;
@@ -37,6 +39,7 @@
3739
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
3840
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
3941

42+
import static org.hibernate.internal.util.JdbcExceptionHelper.extractErrorCode;
4043
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC;
4144
import static org.hibernate.type.SqlTypes.GEOMETRY;
4245
import static org.hibernate.type.SqlTypes.OTHER;
@@ -313,4 +316,20 @@ public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, D
313316
public String getDual() {
314317
return "dual";
315318
}
319+
320+
@Override
321+
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
322+
return (sqlException, message, sql) -> {
323+
final int errorCode = extractErrorCode( sqlException );
324+
325+
return switch ( errorCode ) {
326+
// If @@innodb_snapshot_isolation is set (default since 11.6.2),
327+
// if an attempt to acquire a lock on a record that does not exist in the current read view is made,
328+
// an error DB_RECORD_CHANGED will be raised.
329+
case 1020 -> new LockAcquisitionException( message, sqlException, sql );
330+
// returning null allows other delegates to operate
331+
default -> null;
332+
};
333+
};
334+
}
316335
}

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

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

17+
import org.hibernate.dialect.MariaDBDialect;
18+
import org.hibernate.exception.LockAcquisitionException;
1719
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
1820
import org.junit.Test;
1921

@@ -107,10 +109,19 @@ public void testBatchAndOptimisticLocking() {
107109
}
108110
else {
109111
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-
);
112+
113+
if ( getDialect() instanceof MariaDBDialect && expected.getCause() instanceof LockAcquisitionException) {
114+
assertTrue(
115+
expected.getMessage()
116+
.contains( "Record has changed since last read in table 'Person'" )
117+
);
118+
} else {
119+
assertTrue(
120+
expected.getMessage()
121+
.startsWith(
122+
"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=?]" )
123+
);
124+
}
114125
}
115126
}
116127
}

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 ) {
143+
// if @@innodb_snapshot_isolation is set, database throw an exception if record is not available anymore
144+
assertTrue( ole.getCause() instanceof StaleObjectStateException || 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/function/json/JsonExistsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void testPassing(SessionFactoryScope scope) {
8585
}
8686

8787
@Test
88-
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB reports the error 4038 as warning and simply returns null")
88+
@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")
8989
public void testOnError(SessionFactoryScope scope) {
9090
scope.inSession( em -> {
9191
try {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
1111
import org.hibernate.cfg.AvailableSettings;
1212
import org.hibernate.dialect.CockroachDialect;
13+
import org.hibernate.dialect.MariaDBDialect;
1314
import org.hibernate.dialect.SQLServerDialect;
1415
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
1516
import org.hibernate.orm.test.jpa.model.AbstractJPATest;
@@ -56,6 +57,7 @@ protected void tearDown() {
5657
@Test
5758
@JiraKey( value = "HHH-8786" )
5859
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995")
60+
@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")
5961
public void testLockTimeoutFind() {
6062
final Item item = new Item( "find" );
6163

@@ -97,6 +99,7 @@ public void testLockTimeoutFind() {
9799

98100
@Test
99101
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach uses SERIALIZABLE by default and seems to fail reading a row that is exclusively locked by a different TX")
102+
@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")
100103
public void testLockTimeoutRefresh() {
101104
final Item item = new Item( "refresh" );
102105

@@ -139,6 +142,7 @@ public void testLockTimeoutRefresh() {
139142

140143
@Test
141144
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach uses SERIALIZABLE by default and seems to fail reading a row that is exclusively locked by a different TX")
145+
@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")
142146
public void testLockTimeoutLock() {
143147
final Item item = new Item( "lock" );
144148

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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.hibernate.LockMode;
1212
import org.hibernate.dialect.CockroachDialect;
1313

14+
import org.hibernate.dialect.MariaDBDialect;
1415
import org.hibernate.testing.orm.junit.DomainModel;
1516
import org.hibernate.testing.orm.junit.JiraKey;
1617
import org.hibernate.testing.orm.junit.SessionFactory;
@@ -30,6 +31,7 @@
3031
@SessionFactory
3132
@JiraKey("HHH-16461")
3233
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "CockroachDB uses SERIALIZABLE isolation, and does not support this")
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 class OptimisticAndPessimisticLockTest {
3436

3537
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: 4 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;
@@ -189,8 +190,9 @@ else if ( dialect instanceof CockroachDialect && ( (JDBCException) cause ).getSQ
189190
"40001" ) ) {
190191
// CockroachDB always runs in SERIALIZABLE isolation, and uses SQL state 40001 to indicate
191192
// serialization failure.
192-
}
193-
else {
193+
} else if ( dialect instanceof MariaDBDialect && ( (JDBCException) cause ).getErrorCode() == 1020 ) {
194+
// Mariadb snapshot_isolation throws error
195+
} else {
194196
throw e;
195197
}
196198
}

hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/InsertConflictOnConstraintTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.hibernate.community.dialect.DerbyDialect;
1313
import org.hibernate.dialect.HSQLDialect;
1414
import org.hibernate.dialect.H2Dialect;
15+
import org.hibernate.dialect.MariaDBDialect;
1516
import org.hibernate.dialect.MySQLDialect;
1617
import org.hibernate.dialect.OracleDialect;
1718
import org.hibernate.dialect.PostgreSQLDialect;
@@ -20,6 +21,7 @@
2021
import org.hibernate.testing.orm.junit.RequiresDialect;
2122
import org.hibernate.testing.orm.junit.SessionFactory;
2223
import org.hibernate.testing.orm.junit.SessionFactoryScope;
24+
import org.hibernate.testing.orm.junit.SkipForDialect;
2325
import org.junit.jupiter.api.Test;
2426

2527
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -42,6 +44,7 @@ public class InsertConflictOnConstraintTest {
4244
@RequiresDialect( MySQLDialect.class )
4345
@RequiresDialect( HSQLDialect.class )
4446
@RequiresDialect( DerbyDialect.class )
47+
@SkipForDialect( dialectClass = MariaDBDialect.class )
4548
@Test void testDoNothing(SessionFactoryScope scope) {
4649
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
4750
scope.inTransaction( s -> s.persist(new Constrained()));

0 commit comments

Comments
 (0)