Skip to content
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 @@ -996,6 +1002,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 @@ -8,6 +8,7 @@
import java.sql.SQLException;
import java.sql.Types;

import org.hibernate.PessimisticLockException;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.aggregate.AggregateSupport;
Expand All @@ -22,6 +23,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.query.sqm.CastType;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.SqlAstTranslator;
Expand All @@ -38,6 +43,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 @@ -315,6 +321,46 @@ 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;
};
}

@Override
public boolean equivalentTypes(int typeCode1, int typeCode2) {
return typeCode1 == Types.LONGVARCHAR && typeCode2 == SqlTypes.JSON
Expand Down
4 changes: 2 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 @@ -682,8 +682,8 @@ 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}.
* 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 @@ -14,6 +14,7 @@
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.CockroachDialect;

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

Expand Down Expand Up @@ -69,7 +70,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 @@ -107,10 +108,19 @@ public void testBatchAndOptimisticLocking() {
}
else {
assertEquals( OptimisticLockException.class, expected.getClass() );
assertTrue(
expected.getMessage()
.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=?]")
);

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 (expected row count 1 but was 0) [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,8 @@
import org.hibernate.StaleObjectStateException;
import org.hibernate.cfg.AvailableSettings;

import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.exception.TransactionSerializationException;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.DomainModel;
Expand All @@ -32,6 +34,7 @@
import jakarta.persistence.Version;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.hibernate.testing.orm.junit.DialectContext.getDialect;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

Expand Down Expand Up @@ -136,7 +139,12 @@ public void testFailedUpdate(SessionFactoryScope scope) {
fail();
}
catch (OptimisticLockException ole) {
assertTrue( ole.getCause() instanceof StaleObjectStateException );
if (getDialect() instanceof MariaDBDialect && getDialect().getVersion().isAfter( 11, 6, 2 )) {
// if @@innodb_snapshot_isolation is set, database throw an exception if record is not available anymore
assertTrue( ole.getCause() instanceof LockAcquisitionException );
} else {
assertTrue( ole.getCause() instanceof StaleObjectStateException );
}
}
//CockroachDB errors with a Serialization Exception
catch (RollbackException rbe) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,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 @@ -105,6 +106,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 @@ -10,7 +10,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 @@ -30,6 +30,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 @@ -10,8 +10,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 @@ -29,7 +30,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 @@ -13,6 +13,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 @@ -23,6 +24,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 @@ -189,8 +191,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
@@ -0,0 +1,91 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.vector;

import org.hibernate.dialect.Dialect;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.BasicExtractor;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class BinaryVectorJdbcType extends ArrayJdbcType {

public BinaryVectorJdbcType(JdbcType elementJdbcType) {
super( elementJdbcType );
}

@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.VECTOR;
}

@Override
public <T> JavaType<T> getJdbcRecommendedJavaTypeMapping(
Integer precision,
Integer scale,
TypeConfiguration typeConfiguration) {
return typeConfiguration.getJavaTypeRegistry().resolveDescriptor( float[].class );
}

@Override
public void appendWriteExpression(String writeExpression, SqlAppender appender, Dialect dialect) {
appender.append( writeExpression );
}

@Override
public <X> ValueExtractor<X> getExtractor(JavaType<X> javaTypeDescriptor) {
return new BasicExtractor<>( javaTypeDescriptor, this ) {
@Override
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( rs.getObject( paramIndex, float[].class ), options );
}

@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getObject( index, float[].class ), options );
}

@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getObject( name, float[].class ), options );
}

};
}

@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaTypeDescriptor) {
return new BasicBinder<>( javaTypeDescriptor, this ) {

@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
st.setObject( index, value );
}

@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
st.setObject( name, value, java.sql.Types.ARRAY );
}

@Override
public Object getBindValue(X value, WrapperOptions options) {
return value;
}
};
}
}
Loading
Loading