Skip to content

Commit 43d7f85

Browse files
committed
HHH-18900 MariaDB Vector support
+ adding support and test correction for mariadb 11.6.2 snapshot isolation
1 parent 4fd457e commit 43d7f85

File tree

17 files changed

+504
-20
lines changed

17 files changed

+504
-20
lines changed

databases/mariadb/matrix.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
55
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
66
*/
7-
jdbcDependency 'org.mariadb.jdbc:mariadb-java-client:3.4.0'
7+
jdbcDependency 'org.mariadb.jdbc:mariadb-java-client:3.5.1'

docker_db.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ mysql_8_2() {
9292
}
9393

9494
mariadb() {
95-
mariadb_11_4
95+
mariadb_11_7
9696
}
9797

9898
mariadb_wait_until_start()
@@ -138,6 +138,12 @@ mariadb_11_4() {
138138
mariadb_wait_until_start
139139
}
140140

141+
mariadb_11_7() {
142+
$CONTAINER_CLI rm -f mariadb || true
143+
$CONTAINER_CLI run --name mariadb -e MARIADB_USER=hibernate_orm_test -e MARIADB_PASSWORD=hibernate_orm_test -e MARIADB_DATABASE=hibernate_orm_test -e MARIADB_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d ${DB_IMAGE_MARIADB_11_7:-docker.io/mariadb:11.7-rc} --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake --lower_case_table_names=2
144+
mariadb_wait_until_start
145+
}
146+
141147
mariadb_verylatest() {
142148
$CONTAINER_CLI rm -f mariadb || true
143149
$CONTAINER_CLI run --name mariadb -e MARIADB_USER=hibernate_orm_test -e MARIADB_PASSWORD=hibernate_orm_test -e MARIADB_DATABASE=hibernate_orm_test -e MARIADB_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d ${DB_IMAGE_MARIADB_VERYLATEST:-quay.io/mariadb-foundation/mariadb-devel:verylatest} --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --skip-character-set-client-handshake --lower_case_table_names=2
@@ -996,6 +1002,7 @@ if [ -z ${1} ]; then
9961002
echo -e "\thana"
9971003
echo -e "\tmariadb"
9981004
echo -e "\tmariadb_verylatest"
1005+
echo -e "\tmariadb_11_7"
9991006
echo -e "\tmariadb_11_4"
10001007
echo -e "\tmariadb_11_1"
10011008
echo -e "\tmariadb_10_11"

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

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

11+
import org.hibernate.PessimisticLockException;
1112
import org.hibernate.boot.model.FunctionContributions;
1213
import org.hibernate.boot.model.TypeContributions;
1314
import org.hibernate.dialect.aggregate.AggregateSupport;
@@ -22,6 +23,10 @@
2223
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
2324
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
2425
import org.hibernate.engine.spi.SessionFactoryImplementor;
26+
import org.hibernate.exception.ConstraintViolationException;
27+
import org.hibernate.exception.LockAcquisitionException;
28+
import org.hibernate.exception.LockTimeoutException;
29+
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
2530
import org.hibernate.query.sqm.CastType;
2631
import org.hibernate.service.ServiceRegistry;
2732
import org.hibernate.sql.ast.SqlAstTranslator;
@@ -38,6 +43,7 @@
3843
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
3944
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
4045

46+
import static org.hibernate.internal.util.JdbcExceptionHelper.extractSqlState;
4147
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC;
4248
import static org.hibernate.type.SqlTypes.GEOMETRY;
4349
import static org.hibernate.type.SqlTypes.OTHER;
@@ -315,6 +321,46 @@ public String getDual() {
315321
return "dual";
316322
}
317323

324+
@Override
325+
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
326+
return (sqlException, message, sql) -> {
327+
switch ( sqlException.getErrorCode() ) {
328+
// If @@innodb_snapshot_isolation is set (default since 11.6.2),
329+
// if an attempt to acquire a lock on a record that does not exist in the current read view is made,
330+
// an error DB_RECORD_CHANGED will be raised.
331+
case 1020:
332+
return new LockAcquisitionException( message, sqlException, sql );
333+
case 1205:
334+
case 3572:
335+
return new PessimisticLockException( message, sqlException, sql );
336+
case 1207:
337+
case 1206:
338+
return new LockAcquisitionException( message, sqlException, sql );
339+
case 1062:
340+
// Unique constraint violation
341+
return new ConstraintViolationException(
342+
message,
343+
sqlException,
344+
sql,
345+
ConstraintViolationException.ConstraintKind.UNIQUE,
346+
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
347+
);
348+
}
349+
350+
final String sqlState = extractSqlState( sqlException );
351+
if ( sqlState != null ) {
352+
switch ( sqlState ) {
353+
case "41000":
354+
return new LockTimeoutException( message, sqlException, sql );
355+
case "40001":
356+
return new LockAcquisitionException( message, sqlException, sql );
357+
}
358+
}
359+
360+
return null;
361+
};
362+
}
363+
318364
@Override
319365
public boolean equivalentTypes(int typeCode1, int typeCode2) {
320366
return typeCode1 == Types.LONGVARCHAR && typeCode2 == SqlTypes.JSON

hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -682,8 +682,8 @@ public class SqlTypes {
682682

683683
/**
684684
* A type code representing an {@code embedding vector} type for databases
685-
* like {@link org.hibernate.dialect.PostgreSQLDialect PostgreSQL} and
686-
* {@link org.hibernate.dialect.OracleDialect Oracle 23ai}.
685+
* like {@link org.hibernate.dialect.PostgreSQLDialect PostgreSQL},
686+
* {@link org.hibernate.dialect.OracleDialect Oracle 23ai} and {@link org.hibernate.dialect.MariaDBDialect MariaDB}.
687687
* An embedding vector essentially is a {@code float[]} with a fixed size.
688688
*
689689
* @since 6.4

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

Lines changed: 15 additions & 5 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

@@ -69,7 +70,7 @@ public void testBatchAndOptimisticLocking() {
6970
} );
7071

7172
try {
72-
inTransaction( (session) -> {
73+
inTransaction( session -> {
7374
List<Person> persons = session
7475
.createSelectionQuery( "select p from Person p", Person.class )
7576
.getResultList();
@@ -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
}

0 commit comments

Comments
 (0)