Skip to content

[HHH-18900][JBEAP-30676] Hibernate tests fail with MariaDB 11.6+ #10746

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion databases/mariadb/matrix.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
jdbcDependency 'org.mariadb.jdbc:mariadb-java-client:3.4.0'
jdbcDependency 'org.mariadb.jdbc:mariadb-java-client:3.5.1'
9 changes: 8 additions & 1 deletion docker_db.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ mysql_8_2() {
}

mariadb() {
mariadb_11_4
mariadb_11_7
}

mariadb_wait_until_start()
Expand Down Expand Up @@ -138,6 +138,12 @@ mariadb_11_4() {
mariadb_wait_until_start
}

mariadb_11_7() {
$CONTAINER_CLI rm -f mariadb || true
$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
mariadb_wait_until_start
}

mariadb_verylatest() {
$CONTAINER_CLI rm -f mariadb || true
$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
Expand Down Expand Up @@ -992,6 +998,7 @@ if [ -z ${1} ]; then
echo -e "\thana"
echo -e "\tmariadb"
echo -e "\tmariadb_verylatest"
echo -e "\tmariadb_11_7"
echo -e "\tmariadb_11_4"
echo -e "\tmariadb_11_1"
echo -e "\tmariadb_10_11"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -604,10 +604,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
BasicTypeRegistry basicTypeRegistry = functionContributions.getTypeConfiguration().getBasicTypeRegistry();

SqmFunctionRegistry functionRegistry = functionContributions.getFunctionRegistry();
functionRegistry.noArgsBuilder( "localtime" )
.setInvariantType(basicTypeRegistry.resolve( StandardBasicTypes.TIMESTAMP ))
.setUseParenthesesWhenNoArgs( false )
.register();

// pi() produces a value with 7 digits unless we're explicit
if ( getMySQLVersion().isSameOrAfter( 8 ) ) {
functionRegistry.patternDescriptorBuilder( "pi", "cast(pi() as double)" )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.sql.DatabaseMetaData;
import java.sql.SQLException;

import org.hibernate.PessimisticLockException;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.function.CommonFunctionFactory;
Expand All @@ -21,6 +22,10 @@
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
Expand All @@ -37,6 +42,7 @@
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;

import static org.hibernate.internal.util.JdbcExceptionHelper.extractSqlState;
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC;
import static org.hibernate.type.SqlTypes.GEOMETRY;
import static org.hibernate.type.SqlTypes.OTHER;
Expand Down Expand Up @@ -281,4 +287,44 @@ public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, D
public String getDual() {
return "dual";
}

@Override
public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() {
return (sqlException, message, sql) -> {
switch ( sqlException.getErrorCode() ) {
// If @@innodb_snapshot_isolation is set (default since 11.6.2),
// if an attempt to acquire a lock on a record that does not exist in the current read view is made,
// an error DB_RECORD_CHANGED will be raised.
case 1020:
return new LockAcquisitionException( message, sqlException, sql );
case 1205:
case 3572:
return new PessimisticLockException( message, sqlException, sql );
case 1207:
case 1206:
return new LockAcquisitionException( message, sqlException, sql );
case 1062:
// Unique constraint violation
return new ConstraintViolationException(
message,
sqlException,
sql,
ConstraintViolationException.ConstraintKind.UNIQUE,
getViolatedConstraintNameExtractor().extractConstraintName( sqlException )
);
}

final String sqlState = extractSqlState( sqlException );
if ( sqlState != null ) {
switch ( sqlState ) {
case "41000":
return new LockTimeoutException( message, sqlException, sql );
case "40001":
return new LockAcquisitionException( message, sqlException, sql );
}
}

return null;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -654,11 +654,6 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio

SqmFunctionRegistry functionRegistry = functionContributions.getFunctionRegistry();

functionRegistry.noArgsBuilder( "localtime" )
.setInvariantType(basicTypeRegistry.resolve( StandardBasicTypes.TIMESTAMP ))
.setUseParenthesesWhenNoArgs( false )
.register();

// pi() produces a value with 7 digits unless we're explicit
functionRegistry.patternDescriptorBuilder( "pi", "cast(pi() as double)" )
.setInvariantType( basicTypeRegistry.resolve( StandardBasicTypes.DOUBLE ) )
Expand Down
5 changes: 3 additions & 2 deletions hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -678,8 +678,9 @@ public class SqlTypes {


/**
* A type code representing an {@code embedding vector} type for databases like
* {@link org.hibernate.dialect.PostgreSQLDialect PostgreSQL} and {@link org.hibernate.dialect.OracleDialect Oracle 23ai}.
* A type code representing an {@code embedding vector} type for databases
* like {@link org.hibernate.dialect.PostgreSQLDialect PostgreSQL},
* {@link org.hibernate.dialect.OracleDialect Oracle 23ai} and {@link org.hibernate.dialect.MariaDBDialect MariaDB}.
* An embedding vector essentially is a {@code float[]} with a fixed size.
*
* @since 6.4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.OracleDialect;

import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;

Expand Down Expand Up @@ -72,7 +73,7 @@ public void testBatchAndOptimisticLocking() {
} );

try {
inTransaction( (session) -> {
inTransaction( session -> {
List<Person> persons = session
.createSelectionQuery( "select p from Person p", Person.class )
.getResultList();
Expand Down Expand Up @@ -109,10 +110,18 @@ public void testBatchAndOptimisticLocking() {
);
}
else {
assertEquals(
"Batch update returned unexpected row count from update [1]; actual row count: 0; expected: 1; statement executed: update Person set name=?,version=? where id=? and version=?",
expected.getMessage()
);
if ( getDialect() instanceof MariaDBDialect && getDialect().getVersion().isAfter( 11, 6, 2 )) {
assertTrue(
expected.getMessage()
.contains( "Record has changed since last read in table 'Person'" )
);
} else {
assertTrue(
expected.getMessage()
.startsWith(
"Batch update returned unexpected row count from update [1]; actual row count: 0; expected: 1; statement executed: update Person set name=?,version=? where id=? and version=?" )
);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.exception.SQLGrammarException;
Expand Down Expand Up @@ -106,6 +107,7 @@ public void testStaleVersionedInstanceFoundInQueryResult() {

@Test
@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")
@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")
public void testStaleVersionedInstanceFoundOnLock() {
if ( !readCommittedIsolationMaintained( "repeatable read tests" ) ) {
return;
Expand Down Expand Up @@ -228,6 +230,7 @@ public void testStaleNonVersionedInstanceFoundInQueryResult() {

@Test
@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")
@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")
public void testStaleNonVersionedInstanceFoundOnLock() {
if ( !readCommittedIsolationMaintained( "repeatable read tests" ) ) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import org.hibernate.LockMode;
import org.hibernate.dialect.CockroachDialect;

import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
Expand All @@ -26,6 +26,7 @@
@SessionFactory
@JiraKey("HHH-16461")
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "CockroachDB uses SERIALIZABLE isolation, and does not support this")
@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")
public class OptimisticAndPessimisticLockTest {

public Stream<LockMode> pessimisticLockModes() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
import jakarta.persistence.Version;
import org.hibernate.annotations.OptimisticLock;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.SkipForDialect;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.Test;

import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
Expand All @@ -31,7 +32,8 @@ protected Class<?>[] getAnnotatedClasses() {
}

@Test
@SkipForDialect(value = CockroachDialect.class, comment = "Fails at SERIALIZABLE isolation")
@SkipForDialect(dialectClass = CockroachDialect.class, reason = "Fails at SERIALIZABLE isolation")
@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")
public void test() {
doInJPA(this::entityManagerFactory, entityManager -> {
Phone phone = new Phone();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.hibernate.StaleStateException;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.SQLServerDialect;

import org.hibernate.testing.orm.junit.DialectFeatureChecks;
Expand All @@ -25,6 +26,7 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import static org.hibernate.testing.orm.junit.DialectContext.getDialect;
import static org.junit.jupiter.api.Assertions.fail;

/**
Expand Down Expand Up @@ -191,8 +193,9 @@ else if ( dialect instanceof CockroachDialect && ( (JDBCException) cause ).getSQ
"40001" ) ) {
// CockroachDB always runs in SERIALIZABLE isolation, and uses SQL state 40001 to indicate
// serialization failure.
}
else {
} else if (dialect instanceof MariaDBDialect && getDialect().getVersion().isAfter( 11, 6, 2 )) {
// Mariadb snapshot_isolation throws error
} else {
throw e;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
package org.hibernate.orm.test.temporal;

import java.sql.Time;
import java.sql.Timestamp;

import org.hibernate.dialect.MySQLDialect;
Expand All @@ -18,6 +19,7 @@
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;

import org.hibernate.type.descriptor.java.JdbcTimeJavaType;
import org.junit.jupiter.api.Test;

import static org.junit.Assert.assertEquals;
Expand Down Expand Up @@ -48,11 +50,13 @@ public void testTimeStampFunctions(SessionFactoryScope scope) {
);
Object[] oArray = (Object[]) q.uniqueResult();
for ( Object o : oArray ) {
( (Timestamp) o ).setNanos( 0 );
if ( o instanceof Timestamp) {
( (Timestamp) o ).setNanos( 0 );
}
}
final Timestamp now = (Timestamp) oArray[0];
assertEquals( now, oArray[1] );
assertEquals( now, oArray[2] );
assertTrue( JdbcTimeJavaType.INSTANCE.areEqual( new Time( now.getTime() ), (Time) oArray[2] ) );
assertEquals( now, oArray[3] );
assertTrue( now.compareTo( (Timestamp) oArray[4] ) <= 0 );
}
Expand Down
Loading