8000 HHH-19974 Refactor InformixDialect to override getSelectClauseNullString by namuuCY · Pull Request #11460 · hibernate/hibernate-orm · GitHub
[go: up one dir, main page]

Skip to content

Conversation

@namuuCY
Copy link
@namuuCY namuuCY commented Dec 17, 2025

Refactor InformixDialect to override the new getSelectClauseNullString(SqlTypedMapping, TypeConfiguration) signature.

Instead of relying on the legacy integer-based sqlType logic, this change utilizes the DdlTypeRegistry to dynamically determine the correct cast type name. This ensures better compatibility and aligns the dialect with the standard Hibernate 6 API for null handling in select clauses.

Changes

  • Override getSelectClauseNullString(SqlTypedMapping, TypeConfiguration) in InformixDialect.
  • Use DdlTypeRegistry to resolve the cast type string.

Note on scope

Initially, this PR attempted to address columnDefinition usage for PostgreSQL. However, based on the discussion with @gavinking, we identified that the PostgreSQL issues could be resolved via proper mapping (@JdbcTypeCode(SqlTypes.INET) or InetAddress).
Therefore, per the agreement, I have dropped the PostgreSQL changes and columnDefinition logic from this PR and focused solely on refactoring InformixDialect to use the modern API.


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license
and can be relicensed under the terms of the LGPL v2.1 license in the future at the maintainers' discretion.
For more information on licensing, please check here.


https://hibernate.atlassian.net/browse/HHH-19974

Copy link
Member
@gavinking gavinking left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please roll back all the unrelated whitespace changes so that we can see the substance of the proposed change

.getCastTypeName( sqlType.toSize(), (SqlExpressible) sqlType.getJdbcMapping(), ddlTypeRegistry );
// PostgreSQL assumes a plain null literal in the select statement to be of type text,
// which can lead to issues in e.g. the union subclass strategy, so do a cast
String castTypeName = sqlType.getColumnDefinition();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that this is the substance of the proposed change?

But this doesn't look right to me. If this getColumnDefinition() method returns the content of @Column(columnDefinition), then that is not a column type and should never be treated as such.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are absolutely right.

If columnDefinition contains constraints like default ... or not null, passing it directly to cast() will indeed cause a syntax error. I missed that edge case.

The underlying issue is: The field is mapped as String in Java, so Hibernate currently defaults to casting the null literal as varchar. Howeve 8000 r, the actual DB column is inet, and PostgreSQL throws a type mismatch error (UNION types inet and character varying cannot be matched).

Since we can't safely use columnDefinition, is there a recommended way in Hibernate to extract only the SQL type name (e.g. inet) for these native types, or should we register these types explicitly in the Dialect to avoid the varchar fallback?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably do it by registering a DdlType and a JdbcType. Something like that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean it looks like we already have this stuff built in:

You just write:

public InetAddress addr;

Or if you want to use a string:

@JdbcTypeCode(SqlTypes.INET)
public String addrStr;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gavinking

I have verified your suggestions.

You are absolutely right regarding the inet type.
I confir 8000 med that both approaches work perfectly without any changes to the dialect:

  1. Using @JdbcTypeCode(SqlTypes.INET) on a String field.
  2. Using the InetAddress Java type directly (e.g., private InetAddress addr;).

In both cases, the test passes and Hibernate correctly generates cast(null as inet) in the SQL.
So, at least for types that can be mapped to a standard SqlTypes (or have a built-in mapping like InetAddress), the current mechanism works fine if the mapping is configured correctly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, regarding InformixDialect:

I noticed that InformixDialect currently only overrides the deprecated getSelectClauseNullString(int sqlType, TypeConfiguration typeConfiguration) method.

Even if we drop the columnDefinition logic (as agreed), would you be open to a PR that refactors InformixDialect to override the new getSelectClauseNullString(SqlTypedMapping sqlType, TypeConfiguration typeConfiguration) signature instead?

@Override
public String getSelectClauseNullString(SqlTypedMapping sqlType, TypeConfiguration typeConfiguration) {
	final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
	final String castTypeName = ddlTypeRegistry
			.getDescriptor( sqlType.getJdbcMapping().getJdbcType().getDdlTypeCode() )
			.getCastTypeName( sqlType.toSize(), (SqlExpressible) sqlType.getJdbcMapping(), ddlTypeRegistry );
	return "cast(null as " + castTypeName + ")";
}

This would align the dialect with the modern API and make future type handling improvements easier.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if we drop the columnDefinition logic (as agreed), would you be open to a PR that refactors InformixDialect to override the new getSelectClauseNullString(SqlTypedMapping sqlType, TypeConfiguration typeConfiguration) signature instead?

Yes, sure, thanks.

@namuuCY
Copy link
Author
namuuCY commented Dec 18, 2025

@gavinking
Cleaned up the whitespace!

PostgreSQLLegacyDialectTest.InetEntity.class,
PostgreSQLLegacyDialectTest.EmptyEntity.class
})
@SessionFactory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the standard statement collector instead. Same for the other PostgreSQL test

Suggested change
@SessionFactory
@SessionFactory(useCollectingStatementInspector = true)

Copy link
Author

Choose a reason for hiding this comment

The 8000 reason will be displayed to describe this comment to others. Learn more.

Thanks for the review.
Based on the discussion with Gavin above, we decided to drop the PostgreSQL changes from this PR and focus solely on the Informix refactoring.

Therefore, I will be removing the PostgreSQL-related test files in the next commit, so this change won't be applied. I'll update the PR shortly.

@namuuCY namuuCY changed the title HHH-19974 Use columnDefinition for null casting HHH-19974 Refactor InformixDialect to override getSelectClauseNullString Dec 18, 2025
@namuuCY namuuCY requested review from beikov and gavinking December 18, 2025 11:44
@gavinking
Copy link
Member

This looks fine. Have you run the full Hibernate test suite on Informix?

@gavinking
Copy link
Member

This looks fine. Have you run the full Hibernate test suite on Informix?

I ran them, and it looks like this change breaks two tests:

org.hibernate.orm.test.jpa.compliance.CriteriaToBigDecimalTest

and:

org.hibernate.orm.test.query.criteria.CriteriaWindowFunctionTest

Also, you have not actually included a test for whatever it is that you're fixing in the pull request.

Copy link
Member
@gavinking gavinking left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test, and ensure that this change does not break existing tests.

@gavinking
Copy link
Member

This looks fine. Have you run the full Hibernate test suite on Informix?

I ran them, and it looks like this change breaks two tests:

Excuse me, that's wrong, they're broken on main too. (I attempted to control for that possibility, but I must have messed up.)

…eNullString signature.

- this change utilizes the DdlTypeRegistry to dynamically determine the correct cast type name, ensuring better compatibility and correctness for null handling in select clauses on Informix.
- Added a reproduction test case for InformixDialect to verify the fix.

Signed-off-by: namucy <wkdcjfdud13@gmail.com>
@namuuCY
Copy link
Author
namuuCY commented Dec 19, 2025

@gavinking

I have analyzed the generated SQL and binding behavior to pinpoint the issue.

It turns out that LONGVARCHAR was being incorrectly cast to VARCHAR, and DATETIME types were losing their precision (scale) in the generated UNION queries.

Here is the summary of my findings and the fix:


1. InformixDialect Test Summary

Type Before Fix (Cast to) Result After Fix (Cast to) Result
CLOB clob ✅ Pass clob ✅ Pass
DATETIME datetime year to fraction (Precision Missing) ⚠️ Risk datetime year to fraction(3) ✅ Pass
LONGVARCHAR varchar ❌ Fail lvarchar ✅ Pass

2. Generated SQL Comparison

I've added a reproduction test case. Below is the comparison of the behavior.

Before Fix
Hibernate: 
    select
        be1_0.id,
        be1_0.clazz_,
        be1_0.clobContent,
        be1_0.eventTime,
        be1_0.longContent 
    from
        (select
            id,
            longContent,
            cast(null as datetime year to fraction) as eventTime,
            cast(null as clob) as clobContent,
            1 as clazz_ 
        from
            lvarchar_entity 
        union
        all select
            id,
            cast(null as varchar) as longContent,
            eventTime,
            cast(null as clob) as clobContent,
            2 as clazz_ 
        from
            datetime_entity 
        union
        all select
            id,
            cast(null as varchar) as longContent,
            cast(null as datetime year to fraction) as eventTime,
            clobContent,
            3 as clazz_ 
        from
            clob_entity 
        union
        all select
            id,
            cast(null as varchar) as longContent,
            cast(null as datetime year to fraction) as eventTime,
            cast(null as clob) as clobContent,
            4 as clazz_ 
        from
            empty_entity
    ) be1_0

After Fix
Hibernate: 
    select
        be1_0.id,
        be1_0.clazz_,
        be1_0.clobContent,
        be1_0.eventTime,
        be1_0.longContent 
    from
        (select
            id,
            longContent,
            cast(null as datetime year to fraction(3)) as eventTime,
            cast(null as clob) as clobContent,
            1 as clazz_ 
        from
            lvarchar_entity 
        union
        all select
            id,
            cast(null as lvarchar) as longContent,
            eventTime,
            cast(null as clob) as clobContent,
            2 as clazz_ 
        from
            datetime_entity 
        union
        all select
            id,
            cast(null as lvarchar) as longContent,
            cast(null as datetime year to fraction(3)) as eventTime,
            clobContent,
            3 as clazz_ 
        from
            clob_entity 
        union
        all select
            id,
            cast(null as lvarchar) as longContent,
            cast(null as datetime year to fraction(3)) as eventTime,
            cast(null as clob) as clobContent,
            4 as clazz_ 
        from
            empty_entity
    ) be1_0

3. Regression Test Results

I ran the full regression suite to ensure no side effects.

  • Hibernate Community Dialects: 173 total, 133 passed, 40 ignored.
  • Hibernate Core: 8938 total, 5 failed, 1089 ignored, 7844 passed.

Analysis of Failures:

  • 4 failures: Pre-existing (verified they fail without my changes).
  • 1 failure (CascadeMergeToProxySimpleTest):
    • Status: Flaky / Unrelated.
    • Reason: It failed once with SQLException: ResultSet not open but passed successfully on a retry.
    • Context: My changes affect SQL generation for SELECT clauses (casting), whereas this error is related to SequenceStyleGenerator and connection handling. Given that it passed on retry, this is an intermittent environment issue.
Test Error Log

CascadeMergeToProxySimpleTest 0 ms failed

initializationError

ResultSet not open, operation 'next' not permitted. Verify that autocommit is OFForg.hibernate.HibernateException: Could not fetch the SequenceInformation from the database
at org.hibernate.engine.jdbc.env.internal.ExtractedDatabaseMetaDataImpl.sequenceInformationList(ExtractedDatabaseMetaDataImpl.java:266)
at org.hibernate.engine.jdbc.env.internal.ExtractedDatabaseMetaDataImpl.getSequenceInformationList(ExtractedDatabaseMetaDataImpl.java:230)
at org.hibernate.id.enhanced.SequenceStyleGenerator.getSequenceIncrementValue(SequenceStyleGenerator.java:584)
at org.hibernate.id.enhanced.SequenceStyleGenerator.validatedIncrementSize(SequenceStyleGenerator.java:290)
at org.hibernate.id.enhanced.SequenceStyleGenerator.adjustIncrementSize(SequenceStyleGenerator.java:260)
at org.hibernate.id.enhanced.SequenceStyleGenerator.configure(SequenceStyleGenerator.java:204)
at org.hibernate.boot.model.internal.GeneratorAnnotationHelper.prepareForUse(GeneratorAnnotationHelper.java:303)
at org.hibernate.boot.model.internal.GeneratorAnnotationHelper.lambda$handleSequenceGenerator$0(GeneratorAnnotationHelper.java:179)
at org.hibernate.mapping.SimpleValue.createGenerator(SimpleValue.java:439)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.handleIdentifierValueBinding(InFlightMetadataCollectorImpl.java:2067)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processExportableProducers(InFlightMetadataCollectorImpl.java:2043)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.buildMetadataInstance(InFlightMetadataCollectorImpl.java:2003)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:212)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:131)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:428)
at org.hibernate.testing.orm.junit.DomainModelExtension.lambda$createDomainModelScope$0(DomainModelExtension.java:247)
at org.hibernate.testing.orm.junit.DomainModelExtension$DomainModelScopeImpl.createDomainModel(DomainModelExtension.java:345)
at org.hibernate.testing.orm.junit.DomainModelExtension$DomainModelScopeImpl.<init>(DomainModelExtension.java:338)
at org.hibernate.testing.orm.junit.DomainModelExtension.createDomainModelScope(DomainModelExtension.java:258)
at org.hibernate.testing.orm.junit.DomainModelExtension.postProcessTestInstance(DomainModelExtension.java:128)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:186)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1716)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:153)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:176)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:632)
at java.base/java.util.Optional.orElseGet(Optional.java:364)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
Caused by: java.sql.SQLException: ResultSet not open, operation 'next' not permitted. Verify that autocommit is OFF
at com.informix.lang.Messages.buildExceptionWithMessage(Messages.java:424)
at com.informix.lang.Messages.getSQLException(Messages.java:383)
at com.informix.jdbc.IfxResultSet.getMetaData(IfxResultSet.java:763)
at com.informix.jdbc.IfxResultSet.executeQuery(IfxResultSet.java:136)
at com.informix.jdbc.IfxStatement.executeQuery(IfxStatement.java:744)
at com.informix.jdbc.IfxPreparedStatement.executeQuery(IfxPreparedStatement.java:2830)
at com.informix.jdbc.IfxPreparedStatement.executeQuery(IfxPreparedStatement.java:268)
at com.informix.jdbc.IfxPreparedStatement.executeQuery(IfxPreparedStatement.java:263)
at org.hibernate.tool.schema.extract.spi.ExtractionContext.getQueryResults(ExtractionContext.java:47)
at org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl.extractMetadata(SequenceInformationExtractorLegacyImpl.java:36)
at org.hibernate.engine.jdbc.env.internal.ExtractedDatabaseMetaDataImpl.sequenceInformation(ExtractedDatabaseMetaDataImpl.java:282)
at org.hibernate.engine.jdbc.env.internal.ExtractedDatabaseMetaDataImpl.sequenceInformationList(ExtractedDatabaseMetaDataImpl.java:262)
... 32 more

@namuuCY namuuCY requested a review from gavinking December 19, 2025 19:05
Copy link
Member
@gavinking gavinking left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that all sounds great.

I'll let @beikov look at this because he knows where he was going with that deprecation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

0