Skip to content
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

HHH-18359 Fixes to Informix functions errors #8652

Open
wants to merge 9 commits into
base: 6.6
Choose a base branch
from

Conversation

VladoKuruc
Copy link
Contributor

@VladoKuruc VladoKuruc commented Jul 10, 2024

Cumulative PR for all subtasks [HHH-18360, HHH-18361, HHH-18362, HHH-18363, HHH-18364, HHH-18365, HHH-18168, HHH-18366, HHH-18367, HHH-18368, HHH-18369]


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.


@hibernate-github-bot
Copy link

hibernate-github-bot bot commented Jul 10, 2024

Thanks for your pull request!

This pull request appears to follow the contribution rules.

› This message was automatically generated.

Copy link
Contributor

@beikov beikov left a comment

Choose a reason for hiding this comment

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

Other than what I commented, this looks good.

@@ -269,19 +276,41 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
functionFactory.initcap();
functionFactory.yearMonthDay();
functionFactory.ceiling_ceil();
functionFactory.concat_pipeOperator();
functionFactory.concat_pipeOperator( SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS );
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you sure NO_PLAIN_PARAMETER won't work? Inlining parameters really is the last resort and shouldn't be done lightly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After extensive testing, I discovered that the concat() function does not behave identically to concat_operator(). Using concat_operator() with Informix 11 results in an error in the test org.hibernate.orm.test.hql.ASTParserLoadingTest.testExpressionWithParamInFunction.

Here's the failing HQL query:
s.createQuery( "from Animal where lower(upper('foo') || upper(:bar)) like 'f%'" ).setParameter( "bar", "xyz" ).list();

This translates to the following incorrect SQL:
select ... where lower((upper('foo')||upper(?))) like 'f%'

The error message is:

org.hibernate.exception.SQLGrammarException: could not prepare statement [A syntax error has occurred.] [select a1_0.id,case when a1_5.reptile is not null then 2 when a1_1.mammal is not null then 5 when a1_2.mammal is not null then 6 when a1_3.mammal is not null then 4 when a1_4.mammal is not null then 7 when a1_7.animal is not null then 1 when a1_6.animal is not null then 3 when a1_0.id is not null then 0 end,a1_0.body_weight,a1_0.description,a1_0.father_id,a1_0.mother_id,a1_0.serialNumber,a1_0.zoo_id,a1_3.owner,a1_4.bigDecimalValue,a1_4.bigIntegerValue,a1_4.floatValue,a1_4.height_centimeters / 2.54E0,a1_4.intValue,a1_4.name_first,a1_4.name_initial,a1_4.name_last,a1_4.nickName,a1_6.birthdate,a1_6.pregnant,a1_7.bodyTemperature from Animal a1_0 left join Cat a1_1 on a1_0.id=a1_1.mammal left join Dog a1_2 on a1_0.id=a1_2.mammal left join DomesticAnimal a1_3 on a1_0.id=a1_3.mammal left join Human a1_4 on a1_0.id=a1.mammal left join Lizard a1_5 on a1_0.id=a1_5.reptile left join Mammal a1_6 on a1_0.id=a1_6.animal left join Reptile a1_7 on a1_0.id=a1_7.animal where lower((upper('foo')||upper(?))) like 'f%']

The previously proposed solution using concat_operator( SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS ) can therefore be replaced with the default concat() function.

As mentioned in issue HHH-18366, the problem also affects CaseSearchedExpression. I attempted to address it using a similar approach as in other dialects.

Overriding visitAnsiCaseSearchedExpression() as done in DB2 and others is not effective.

Overriding visitCaseSearchedExpression() with a call to visitDecodeCaseSearchedExpression() doesn't help either.

Overriding visitCaseSearchedExpression() with a call to renderSelectExpressionWithCastedOrInlinedPlainParameters() doesn't resolve this issue because the parameter is nested within the concat() function. However, this approach can eliminate other errors, such as the one in the unit test org.hibernate.orm.test.hql.BulkManipulationTest.testSimpleInsertWithNamedParam().

org.hibernate.exception.SQLGrammarException: could not prepare statement [A syntax error has occurred.] [insert into Pickup(id,owner,vin) select c1_0.id,?,c1_0.vin from Car c1_0]
In my opinion, these tests should be marked with:

@RequiresDialectFeature(
		value = DialectChecks.SupportsParametersInInsertSelectCheck.class,
		comment = "dialect does not support parameter in INSERT ... SELECT"
)

Therefore, the solution I propose is to use:

withParameterRenderingMode(
	SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS,
	() -> super.visitCaseSearchedExpression( caseSearchedExpression, inSelect )
);

);
}
else {
functionFactory.coalesce( SqlAstNodeRenderingMode.INLINE_ALL_PARAMETERS );
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you sure NO_PLAIN_PARAMETER won't work? Inlining parameters really is the last resort and shouldn't be done lightly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using functionFactory.coalesce( SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER ) generates a SQL select statement that casts the parameter. However, this still results in the same error:
org.hibernate.exception.GenericJDBCException: could not prepare statement [Not implemented yet.] [select p1_0.id,p1_0.name,p1_0.surname from Person p1_0 where p1_0.name=coalesce(cast(? as varchar(255)),p1_0.name)]
According to the documentation COALESCE, "arguments cannot be host variables" for the coalesce function.

@beikov
Copy link
Contributor

beikov commented Jul 16, 2024

Can you please try to rebase on top of #8675 and make use of the SqlAstNodeRenderingMode.WRAP_ALL_PARAMETERS for nvl, coalesce and case expressions? That should work according to jOOQ/jOOQ#12064

@VladoKuruc
Copy link
Contributor Author

Can you please try to rebase on top of #8675 and make use of the SqlAstNodeRenderingMode.WRAP_ALL_PARAMETERS for nvl, coalesce and case expressions? That should work according to jOOQ/jOOQ#12064

Can you help me with this rebase as you suggested? I'm not sure how to rebase on top of pull request #8675. In my local clone of the repository, I added your remote fork https://github.com/hibernate/hibernate-orm. Then I fetched from it and finally rebases onto the remotes/beikov/wrap-parameter-rendering-mode branch. After my code changes, should I just push to my remote origin branch from which my pull request is created to get the expected result?

@VladoKuruc
Copy link
Contributor Author

After changing to functionFactory.coalesce( SqlAstNodeRenderingMode.WRAP_ALL_PARAMETERS ); , a java.lang.StackOverflowError occurs.

	at org.hibernate.sql.ast.spi.AbstractSqlAstTranslator.visitParameter(AbstractSqlAstTranslator.java:7057)
	at org.hibernate.sql.exec.internal.AbstractJdbcParameter.accept(AbstractJdbcParameter.java:63)
	at org.hibernate.sql.ast.spi.AbstractSqlAstTranslator.render(AbstractSqlAstTranslator.java:7113)
	at org.hibernate.sql.ast.spi.AbstractSqlAstTranslator.renderWrappedParameter(AbstractSqlAstTranslator.java:7085)
	at org.hibernate.sql.ast.spi.AbstractSqlAstTranslator.visitParameter(AbstractSqlAstTranslator.java:7057)

@VladoKuruc VladoKuruc changed the base branch from 6.5 to main July 23, 2024 12:05
@VladoKuruc
Copy link
Contributor Author

After making small changes to avoid cycling, I tested using rendering mode WRAP_ALL_PARAMETERS in the coalesce function. This is a workaround for unsupported host variables in Informix. The problem is with older Informix 11, where it is syntactically correct after this, but the database incorrectly determines metadata about columns with expressions (I can send a protocol trace) and therefore returns incorrect values. I isolated a simple test:

TypedQuery<String> query = session.createQuery( "select :name", String.class );
query.setParameter("name", "Johannes");
List<String> resultList = query.getResultList();
assertThat(resultList, hasItem("Johannes"));

Which ends with the error:

    select
        cast(? as varchar(255)) 
    from
        systables 
    where
        tabid=1
[subsystem] TRACE g.hibernate.orm.jdbc.bind JdbcBindingLogging:24 - binding parameter (1:VARCHAR) <- [Johannes]
[subsystem] TRACE ibernate.orm.jdbc.extract JdbcExtractingLogging:24 - extracted value (1:VARCHAR) -> [J]
java.lang.AssertionError: 
Expected: a collection containing "Johannes"
     but: was "J"

Informix 14 did not have this problem.

For the concat() function in the specified test, the result remains similar to the DEFAULT rendering mode.

    select
        case 
            when te1_0.id is not null 
                then concat((select
                    cast(? as varchar(255))), '_1') 
            else 'Empty' 
    end,
    trim(BOTH from concat((select
        cast(? as varchar(255))), 'Test   ')) 
from
    TEST_ENTITY te1_0

There is no syntax problem, but again it is the metadata. The changes are taken into account according to the cast type, but it is still not good. The latest Informix 14 also has a problem with this. Therefore, I stick to my proposal. After all, I assume that there may be problems with this metadata in other SQL selects as well, as contained in the tests.

@VladoKuruc VladoKuruc requested a review from beikov July 25, 2024 08:05
@beikov
Copy link
Contributor

beikov commented Aug 7, 2024

Hi Vlado, sorry for not responding in a while. I was working hard on some performance improvements and had to focus a while.
I just merged the PR I mentioned with a fix for the cycle, so please revert the changes you did in AbstractSqlAstTranslator. Can you please rebase your work on top of it?

There is no syntax problem, but again it is the metadata. The changes are taken into account according to the cast type, but it is still not good. The latest Informix 14 also has a problem with this. Therefore, I stick to my proposal. After all, I assume that there may be problems with this metadata in other SQL selects as well, as contained in the tests.

I just tested this myself on Informix 14 and it seems to work fine. What am I missing?

session.createNativeQuery( "select case when 1 is not null then concat((select cast(? as varchar(255))), '_1') else 'Empty' end,trim(BOTH from concat((select cast(? as varchar(255))), 'Test   '))  from systables where tabid=1" ).setParameter( 1, "abc" ).setParameter( 2, "abc" ).getResultList()

@VladoKuruc
Copy link
Contributor Author

Hi Chris,

I just got back from vacation on Tuesday evening, so the longer silence didn't bother me. Thanks for your support. I tried the rebase as you recommended. You solved the looping issue, but now in the resulting WRAP_ALL_PARAMETERS rendering mode, it's coalesce((select ?), p1_0.name) instead of the expected coalesce((select cast(? as varchar(255))), p1_0.name). That's why I used renderCasted() in my version.

I just tested this myself on Informix 14 and it seems to work fine. What am I missing?

session.createNativeQuery( "select case when 1 is not null then concat((select cast(? as varchar(255))), '_1') else 'Empty' end,trim(BOTH from concat((select cast(? as varchar(255))), 'Test   '))  from systables where tabid=1" ).setParameter( 1, "abc" ).setParameter( 2, "abc" ).getResultList()

Try using my exact scenario. Run the test org.hibernate.orm.test.jpa.criteria.basic.ConcatTest.testSelectCaseWithConcat in your branch with functionFactory.concat( SqlAstNodeRenderingMode.WRAP_ALL_ PARAMETERS ); set in the InformixDialect.
For this scenario, I've extended CommonFunctionFactory.java:

public void concat() {
    concat(SqlAstNodeRenderingMode.DEFAULT);
}

public void concat(SqlAstNodeRenderingMode inferenceArgumentRenderingMode) {
    functionRegistry.namedDescriptorBuilder("concat")
            .setInvariantType(stringType)
            .setMinArgumentCount(1)
            .setArgumentTypeResolver(
                    StandardFunctionArgumentTypeResolvers.impliedOrInvariant(typeConfiguration, STRING)
            )
            .setArgumentListSignature("(STRING string0[,   
 STRING string1[, ...]])")
            .setArgumentRenderingMode(inferenceArgumentRenderingMode)
            .register();
}

@beikov beikov changed the base branch from main to 6.6 August 16, 2024 11:48
@beikov
Copy link
Contributor

beikov commented Aug 16, 2024

Please rebase your PR to resolve the conflicts. Also, please change the code to use renderCasted like you described.

@VladoKuruc
Copy link
Contributor Author

I've done a rebase, but I can't directly use just renderCasted() because it will cause the infinite loop that you've fixed once before.

@VladoKuruc
Copy link
Contributor Author

As I predicted, problems with parameters are common in SqlSelections. Another example is from the test org.hibernate.orm.test.query.hql.FunctionTests.testTrimFunctionParameters

org.hibernate.exception.SQLGrammarException: could not prepare statement [A syntax error has occurred.] [select trim(BOTH from ?) from (select 0 from systables where tabid=1) dual]

Therefore, I propose a different, more radical solution: in Informix, for SqlSelections, set the default parameter rendering mode to INLINE_ALL_PARAMETERS.

@VladoKuruc
Copy link
Contributor Author

I omitted the fixes for subtasks HHH-18366 Informix concat pipe operator error and HHH-18368 Informix function coalesce() error due to an ambiguous problematic solution.

assertEquals( 2, count.intValue() );
count = (Number) s.createQuery( "select count(distinct case when bodyWeight > 100 then nickName else null end) from Human" ).uniqueResult();
assertEquals( 2, count.intValue() );
if ( !( getDialect() instanceof InformixDialect && getDialect().getVersion().isBefore( 11, 70 ) ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use the @SkipForDialect annotation for this instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will have to rework this test into JUnit 5 because the current deprecated annotation @SkipForDialect does not have the attributes majorVersion, minorVersion.

@beikov
Copy link
Contributor

beikov commented Nov 28, 2024

I also found this documentation saying:

In general, you cannot use variables (for example, host variables in an Informix
ESQL/C application) in the select list by themselves. A variable is valid in the
select list, however, if an arithmetic or concatenation operator connects it to a
constant.

How about connecting it with an empty string in case the parameter is a string and +0 if it's a number? Maybe you can add 0 seconds to timestamps parameters as well?

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.

2 participants