Repository navigation
Conversation
A constant or parameter is converted through the mapping of its local base. Only a direct operand of a comparison or of an UPDATE SET got the attribute as local base, so an argument of CASE, COALESCE or NULLIF kept the ExpressionBuilder and an enum was bound as its ordinal, or printed as its bare name, even for @Enumerated(STRING) and converted attributes. - FunctionExpression: CASE, COALESCE and NULLIF pass the attribute they are compared with or assigned to down to their result arguments, convert a value compared with them through their attribute argument, and convert a remaining constant or parameter result argument through that attribute when normalizing. NULLIF converts a value argument through its attribute argument. Only an attribute is used as local base, so local bases cannot reference each other. - Expression.from: pass the attribute to a CASE, COALESCE or NULLIF value. - ExpressionQueryMechanism: also set the local base of such a function assigned in UPDATE SET. Fixes eclipse-ee4j#2848 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A fully qualified enum literal compared with an attribute is converted through the attribute's mapping (
t.status = com.example.Status.DONEbindsDONEfor@Enumerated(STRING)). The same literal, or an enum parameter, used as an argument ofCASE,COALESCEorNULLIFis written as its ordinal or as its bare name (H2):UPDATE Todo t SET t.status = CASE WHEN t.id = 1 THEN <DONE> ELSE <PENDING> ENDTHEN 1 ELSE 0, stores'1'/'0'without errorUPDATE Todo t SET t.status = COALESCE(NULLIF(t.status, t.status), <DONE>)1, stores'1'without errorUPDATE Todo t SET t.status = CASE t.id WHEN 1 THEN <DONE> ELSE t.status ENDTHEN DONE, unknown columnUPDATE Todo t SET t.status = NULLIF(t.status, <DONE>)1, data conversion errorSELECT t FROM Todo t WHERE t.status = CASE WHEN t.id = 1 THEN <PENDING> ELSE <DONE> ENDTHEN 0 ELSE 1, conversion error or wrong rowsSELECT t FROM Todo t WHERE COALESCE(t.status, <PENDING>) = <DONE>0, 1SELECT t FROM Todo t WHERE NULLIF(t.status, <DONE>) IS NULL1SELECT t FROM Todo t WHERE :p = CASE WHEN t.id = 1 THEN t.status ELSE :p END1The Criteria API is affected the same way (
cb.coalesce,cb.selectCase,cb.nullif).Root cause
A constant or parameter is converted by
localBase.getFieldValue(); with theExpressionBuilderas local base,Expression.getFieldValue()turns an enum into its ordinal. Only direct operands get the attribute as local base:Expression.from()for a comparison andExpressionQueryMechanism.prepareUpdateAll()forUPDATE SET(value expressions only). For the functions:Expression.from()never passes the attribute to a function operand, andFunctionExpression.setLocalBase()only forwards to the first child.UPDATE SETskips a function value.FunctionExpression.create()gives every function argument theExpressionBuilderas local base, soNULLIFdoes not convert its second argument like its first.COALESCE(t.status, <PENDING>) = <DONE>), nothing converts the literal inside the function, and the value compared with the function is converted through the function, which has no mapping.Fix
FunctionExpression, forCASE,COALESCEandNULLIF(functions whose value is one of their arguments):setLocalBase()passes the attribute down to the result arguments (THEN/ELSEresults, allCOALESCEarguments, bothNULLIFarguments).getFieldValue()converts through the first attribute result argument.normalize()converts a constant or parameter result argument that has no attribute local base through the function's own attribute argument. It is done when normalizing becauseCASEandCOALESCEarguments are added one at a time.create(): a constant or parameterNULLIFargument is converted through the other argument when that one is an attribute.Expression.from()passes the attribute to such a function operand, andExpressionQueryMechanismsets the local base of such a function assigned inUPDATE SET.Only an attribute (a query key, a field, or the first attribute result argument of such a function) is ever used as local base. Passing a value down lets local bases reference each other: an early version of this change failed
TestQuerySyntaxComparisonTests.testNullIf2_*(?1 = NULLIF(?2, s.strVal1)) with aStackOverflowErrorinParameterExpression.getBuilder(), the cycle noted inExpression.from()(3107049). SimpleCASEWHENvalues keep the operand as local base.Tests
TestQueryEnumFunctionArguments(jse, 14 tests): JPQLUPDATE SETwith searchedCASE, simpleCASE,COALESCE(literal and parameter) andNULLIF;SELECTwithCASE,COALESCEon the left of the comparison,NULLIF ... IS NULL, a value on both sides ofNULLIFand ofCASE; Criteriacoalesce,selectCase,nullifin an update. Updates are checked by reading theSTATUScolumn with a native query.eclipselink.core.testSRG (1243), testappsjpql(613),criteria(329),advanced(442),advanced2(159),eclipselink.jpa.test.jse(783, 33 skipped, includingTestQueryCase,TestQueryCoalesce,TestQuerySyntaxComparisonTests,TestCoalesceFunction,TestSimpleCase,TestParameterBinding). Without the fix the 14 new tests fail (9 wrong values or rows, 5 Derby type errors); with the fix all pass, and no other test changed status.Not covered
These were listed in #2848 but do not depend on the enum mapping; they happen with a
Stringattribute as well:COALESCE(?, ?)(Unknown data type: "?"), e.g.t.status = COALESCE(:p, <DONE>). The values are now bound converted.UPDATE ... SET x = COALESCE(:p, ...),getParameterType()of:pisnull.SELECTprojection ofCASE/COALESCEover an enum attribute returns the column value (String), not the enum.Fixes #2848
🤖 Generated with Claude Code