From 51505dd9698993e4815d49435f8f2284d81f4dd2 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 2 Jul 2025 01:40:56 +0200 Subject: [PATCH 1/5] fix and skip some locking-related tests for Informix Informix only allows FOR UPDATE with single-table SELECTs --- .../community/dialect/InformixDialect.java | 5 +++++ .../test/jpa/findoptions/FindOptionsTest.java | 4 ++++ .../hibernate/orm/test/jpa/lock/LockTest.java | 6 ++++++ ...tementIsClosedAfterALockExceptionTest.java | 7 +++++++ ...inedInheritancePessimisticLockingTest.java | 4 ++++ .../orm/test/locking/LockModeTest.java | 7 +++++++ ...LockRefreshReferencedAndCascadingTest.java | 20 +++++++++++++++++-- .../locking/LockRefreshReferencedTest.java | 4 ++++ .../test/locking/jpa/FollowOnLockingTest.java | 3 +++ 9 files changed, 58 insertions(+), 2 deletions(-) diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java index 12924b236aac..7873bd8f2d7e 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java @@ -313,6 +313,11 @@ public int getDoublePrecision() { return 16; } + @Override + public boolean doesReadCommittedCauseWritersToBlockReaders() { + return true; + } + @Override public SelectItemReferenceStrategy getGroupBySelectItemReferenceStrategy() { return SelectItemReferenceStrategy.POSITION; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/findoptions/FindOptionsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/findoptions/FindOptionsTest.java index 180cdb064e34..099e58f0a47c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/findoptions/FindOptionsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/findoptions/FindOptionsTest.java @@ -20,8 +20,10 @@ import org.hibernate.Session; import org.hibernate.annotations.FetchProfile; import org.hibernate.annotations.FetchProfileOverride; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.Test; import java.util.List; @@ -35,6 +37,8 @@ @Jpa(annotatedClasses = FindOptionsTest.MyEntity.class) public class FindOptionsTest { @Test + @SkipForDialect(dialectClass = InformixDialect.class, + reason = "Informix disallows FOR UPDATE with multi-table queries") void test(EntityManagerFactoryScope scope) { MyEntity hello = new MyEntity("Hello"); scope.getEntityManagerFactory() diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/LockTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/LockTest.java index b2a978d3c4ad..6fa1d4939902 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/LockTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/LockTest.java @@ -4,6 +4,7 @@ */ package org.hibernate.orm.test.jpa.lock; +import java.sql.Connection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -21,6 +22,7 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.community.dialect.AltibaseDialect; import org.hibernate.community.dialect.FirebirdDialect; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.HANADialect; import org.hibernate.dialect.CockroachDialect; import org.hibernate.community.dialect.DerbyDialect; @@ -68,6 +70,10 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { @Override protected void addConfigOptions(Map options) { super.addConfigOptions( options ); + if ( getDialect() instanceof InformixDialect ) { + options.put( AvailableSettings.ISOLATION, + Connection.TRANSACTION_REPEATABLE_READ ); + } // We can't use a shared connection provider if we use TransactionUtil.setJdbcTimeout because that is set on the connection level // options.remove( AvailableSettings.CONNECTION_PROVIDER ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/StatementIsClosedAfterALockExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/StatementIsClosedAfterALockExceptionTest.java index b30d2e6a07ee..1fafd6166559 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/StatementIsClosedAfterALockExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/StatementIsClosedAfterALockExceptionTest.java @@ -4,13 +4,16 @@ */ package org.hibernate.orm.test.jpa.lock; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; import jakarta.persistence.LockModeType; import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.community.dialect.AltibaseDialect; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.CockroachDialect; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider; @@ -48,6 +51,10 @@ protected Map getConfig() { org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, CONNECTION_PROVIDER ); + if ( getDialect() instanceof InformixDialect ) { + config.put( AvailableSettings.ISOLATION, + Connection.TRANSACTION_REPEATABLE_READ ); + } return config; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/JoinedInheritancePessimisticLockingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/JoinedInheritancePessimisticLockingTest.java index 0730f7d7a007..6fee347cf632 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/JoinedInheritancePessimisticLockingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/JoinedInheritancePessimisticLockingTest.java @@ -12,9 +12,11 @@ import jakarta.persistence.InheritanceType; import jakarta.persistence.LockModeType; import jakarta.persistence.Version; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -55,6 +57,8 @@ public void tearDown(EntityManagerFactoryScope scope) { } @Test + @SkipForDialect(dialectClass = InformixDialect.class, + reason = "Informix disallows FOR UPDATE with multi-table queries") public void findWithLock(EntityManagerFactoryScope scope) { scope.inTransaction(entityManager -> { BaseThing t = entityManager.find( BaseThing.class, 1L, LockModeType.PESSIMISTIC_WRITE ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockModeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockModeTest.java index 6f74fa7fcdad..1b2571f63b7f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockModeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockModeTest.java @@ -4,6 +4,7 @@ */ package org.hibernate.orm.test.locking; +import java.sql.Connection; import java.util.Collections; import jakarta.persistence.LockModeType; @@ -14,7 +15,9 @@ import org.hibernate.LockMode; import org.hibernate.Session; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.community.dialect.AltibaseDialect; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.SQLServerDialect; import org.hibernate.dialect.SybaseASEDialect; @@ -62,6 +65,10 @@ protected void applySettings(StandardServiceRegistryBuilder ssrBuilder) { super.applySettings( ssrBuilder ); // We can't use a shared connection provider if we use TransactionUtil.setJdbcTimeout because that is set on the connection level // ssrBuilder.getSettings().remove( AvailableSettings.CONNECTION_PROVIDER ); + if ( getDialect() instanceof InformixDialect ) { + ssrBuilder.applySetting( AvailableSettings.ISOLATION, + Connection.TRANSACTION_REPEATABLE_READ ); + } } @BeforeEach diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedAndCascadingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedAndCascadingTest.java index 955069bd5aa1..6ec32e42b51a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedAndCascadingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedAndCascadingTest.java @@ -6,9 +6,12 @@ import org.hibernate.Hibernate; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.Jpa; -import org.junit.jupiter.api.BeforeAll; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import jakarta.persistence.CascadeType; @@ -34,7 +37,7 @@ ) public class LockRefreshReferencedAndCascadingTest { - @BeforeAll + @BeforeEach public void setUp(EntityManagerFactoryScope scope) { scope.inTransaction( entityManager -> { @@ -52,7 +55,14 @@ public void setUp(EntityManagerFactoryScope scope) { ); } + @AfterEach + public void tearDown(EntityManagerFactoryScope scope) { + scope.getEntityManagerFactory().getSchemaManager().truncate(); + } + @Test + @SkipForDialect(dialectClass = InformixDialect.class, + reason = "Informix disallows FOR UPDATE with multi-table queries") public void testRefreshBeforeRead(EntityManagerFactoryScope scope) { scope.inTransaction( entityManager -> { @@ -100,6 +110,8 @@ public void testRefresh(EntityManagerFactoryScope scope) { } @Test + @SkipForDialect(dialectClass = InformixDialect.class, + reason = "Informix disallows FOR UPDATE with multi-table queries") public void testRefreshAfterRead(EntityManagerFactoryScope scope) { scope.inTransaction( entityManager -> { @@ -124,6 +136,8 @@ public void testRefreshAfterRead(EntityManagerFactoryScope scope) { } @Test + @SkipForDialect(dialectClass = InformixDialect.class, + reason = "Informix disallows FOR UPDATE with multi-table queries") public void testRefreshLockMode(EntityManagerFactoryScope scope) { scope.inTransaction( entityManager -> { @@ -150,6 +164,8 @@ public void testRefreshLockMode(EntityManagerFactoryScope scope) { } @Test + @SkipForDialect(dialectClass = InformixDialect.class, + reason = "Informix disallows FOR UPDATE with multi-table queries") public void testFindWithLockMode(EntityManagerFactoryScope scope) { scope.inTransaction( session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedTest.java index 2e133330d7f0..24b615644560 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/LockRefreshReferencedTest.java @@ -7,9 +7,11 @@ import org.hibernate.Hibernate; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -117,6 +119,8 @@ public void testRefreshAfterRead(EntityManagerFactoryScope scope) { @Test + @SkipForDialect(dialectClass = InformixDialect.class, + reason = "Informix disallows FOR UPDATE with multi-table queries") public void testFindWithLockMode(EntityManagerFactoryScope scope) { scope.inTransaction( session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/jpa/FollowOnLockingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/jpa/FollowOnLockingTest.java index 063b4d9b6c10..176022ca8a7b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/jpa/FollowOnLockingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/jpa/FollowOnLockingTest.java @@ -8,6 +8,7 @@ import java.util.concurrent.TimeUnit; import org.hibernate.community.dialect.AltibaseDialect; +import org.hibernate.community.dialect.InformixDialect; import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.OracleDialect; @@ -39,6 +40,8 @@ @SkipForDialect(dialectClass = CockroachDialect.class, reason = "Cockroach allows the concurrent access but cancels one or both transactions at the end") @SkipForDialect(dialectClass = OracleDialect.class, majorVersion = 11, reason = "Timeouts don't work on Oracle 11 when using a driver other than ojdbc6, but we can't test with that driver") @SkipForDialect(dialectClass = AltibaseDialect.class, reason = "Altibase does not support timeout in statement level") +@SkipForDialect(dialectClass = InformixDialect.class, reason = "Test requires REPEATABLE_READ (and then it passes)") +//@ServiceRegistry(settings = @Setting(name = AvailableSettings.ISOLATION, value = "REPEATABLE_READ")) public class FollowOnLockingTest { @Test From 93a6e01814640c13ad0475b94cb18d7335054e70 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 2 Jul 2025 02:33:49 +0200 Subject: [PATCH 2/5] use 'reuse storage keep statistics' to truncate on Informix --- .../org/hibernate/community/dialect/InformixDialect.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java index 7873bd8f2d7e..3c39617d6085 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java @@ -510,6 +510,12 @@ public String getAddPrimaryKeyConstraintString(String constraintName) { return " add constraint primary key constraint " + constraintName + " "; } + @Override + public String getTruncateTableStatement(String tableName) { + return super.getTruncateTableStatement( tableName ) + + " reuse storage keep statistics"; + } + @Override public SequenceSupport getSequenceSupport() { return sequenceSupport; From 2329474e1d329238322b6c243bd821482fc7a344 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 2 Jul 2025 03:01:26 +0200 Subject: [PATCH 3/5] disable constraints while truncating tables on Informix --- .../community/dialect/InformixDialect.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java index 3c39617d6085..77e176406f7d 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java @@ -579,6 +579,21 @@ public boolean dropConstraints() { return !getVersion().isSameOrAfter( 12, 10 ); } + @Override + public boolean canDisableConstraints() { + return true; + } + + @Override + public String getDisableConstraintStatement(String tableName, String name) { + return "set constraints " + name + " disabled"; + } + + @Override + public String getEnableConstraintStatement(String tableName, String name) { + return "set constraints " + name + " enabled"; + } + @Override public boolean supportsOrderByInSubquery() { // This is just a guess From 462ff2263a3aa7f88001ee311088496563acf791 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 2 Jul 2025 07:21:28 +0200 Subject: [PATCH 4/5] minor cleanups to InformixDialect --- .../community/dialect/InformixDialect.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java index 77e176406f7d..4f5cc8000383 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java @@ -146,14 +146,14 @@ public String[] getSqlCreateStrings( ForeignKey foreignKey, Metadata metadata, SqlStringGenerationContext context) { - String[] results = super.getSqlCreateStrings( foreignKey, metadata, context ); + final String[] results = super.getSqlCreateStrings( foreignKey, metadata, context ); for ( int i = 0; i < results.length; i++ ) { - String result = results[i]; + final String result = results[i]; if ( result.contains( " on delete " ) ) { - String constraintName = "constraint " + foreignKey.getName(); - result = result.replace( constraintName + " ", "" ); - result = result + " " + constraintName; - results[i] = result; + final String constraintName = "constraint " + foreignKey.getName(); + results[i] = + result.replace( constraintName + " ", "" ) + + " " + constraintName; } } return results; @@ -174,11 +174,12 @@ protected String primaryKeyString(PrimaryKey key) { } constraint.append( column.getQuotedName( dialect ) ); } - constraint.append(')'); + constraint.append( ')' ); final UniqueKey orderingUniqueKey = key.getOrderingUniqueKey(); if ( orderingUniqueKey != null && orderingUniqueKey.isNameExplicit() ) { constraint.append( " constraint " ) - .append( orderingUniqueKey.getName() ).append( ' ' ); + .append( orderingUniqueKey.getName() ) + .append( ' ' ); } return constraint.toString(); } @@ -356,11 +357,11 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio functionFactory.variance(); functionFactory.bitLength_pattern( "length(?1)*8" ); functionFactory.varPop_sumCount(); - functionFactory.hypotheticalOrderedSetAggregates(); final SqmFunctionRegistry functionRegistry = functionContributions.getFunctionRegistry(); final TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration(); - final BasicType stringBasicType = typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING ); + final BasicType stringBasicType = + typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING ); functionRegistry.registerAlternateKey( "var_samp", "variance" ); @@ -378,8 +379,10 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio .setArgumentTypeResolver( impliedOrInvariant( typeConfiguration, STRING ) ) .setArgumentListSignature( "(STRING string, STRING pattern)" ) .register(); + if ( supportsWindowFunctions() ) { functionFactory.windowFunctions(); + functionFactory.hypotheticalOrderedSetAggregates(); } functionRegistry.register( "overlay", From 873fef717d021bd370fd995db48dbfc9141dc099 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 2 Jul 2025 07:22:00 +0200 Subject: [PATCH 5/5] Informix has native least() and greatest() functions --- .../org/hibernate/community/dialect/InformixDialect.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java index 4f5cc8000383..18336a539d12 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/InformixDialect.java @@ -371,8 +371,12 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio //coalesce() and nullif() both supported since Informix 12 - functionRegistry.register( "least", new CaseLeastGreatestEmulation( true ) ); - functionRegistry.register( "greatest", new CaseLeastGreatestEmulation( false ) ); + // least() and greatest() supported since 12.10 + if ( getVersion().isBefore( 12, 10 ) ) { + functionRegistry.register( "least", new CaseLeastGreatestEmulation( true ) ); + functionRegistry.register( "greatest", new CaseLeastGreatestEmulation( false ) ); + } + functionRegistry.namedDescriptorBuilder( "matches" ) .setInvariantType( stringBasicType ) .setExactArgumentCount( 2 )