Fix Enum Serialization in CaseBuilder for Hibernate Compatibility #966
+36
−18
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.
Description
This PR addresses an issue where enums used in CaseBuilder within QueryDSL are serialized as bind parameters (?) or full class paths (e.g., com.example.JobFunction.MANAGER), causing Hibernate to fail with a ConversionException: Could not determine ValueMapping for SqmParameter. This behavior disrupts compatibility with Hibernate, especially in CASE WHEN expressions.
The goal of this change is to serialize enums as string literals (e.g., 'MANAGER') in JPQL, ensuring Hibernate can process them correctly without binding issues.
Changes
1. JPQLSerializer Modification:
Updated visitConstant to handle Enum instances by delegating to visitLiteral, preventing them from being treated as bind parameters.
Before: Enums were passed to super.visitConstant(), resulting in ? parameters.
After: Enums are serialized as literals via visitLiteral.
2. JPQLTemplates Modification:
Modified asLiteral to convert enums to string literals using .name() instead of full class paths.
Before: com.example.JobFunction.MANAGER
After: 'MANAGER'
3. visitOperation Adjustment:
Enhanced visitOperation to include Ops.CASE_WHEN in the inCaseOperation check, ensuring consistency across when, then, and otherwise in CaseBuilder.
Before: inCaseOperation was only true for Ops.CASE_ELSE, causing then to bind as ? inconsistently.
After: Added || operator.equals(Ops.CASE_WHEN) to inCaseOperation check to cover CaseBuilder’s when clause processing.
Why Ops.CASE_WHEN was added: Initially, CASE_OPS only included Ops.CASE_ELSE, which meant inCaseOperation was not set to true during the processing of CaseBuilder’s when clause. Running tests revealed that the when clause was being bound as a parameter (e.g., ?), while then was correctly serialized as a literal (e.g., 'MANAGER'). This inconsistency arose because CaseBuilder internally uses Ops.CASE_WHEN for when conditions, but it wasn’t recognized by the inCaseOperation logic. Adding Ops.CASE_WHEN ensures that inCaseOperation is true throughout the entire CaseExpression, aligning the behavior of when, then, and otherwise.
4. Test Case:
Added a test to verify enum serialization in CaseBuilder.
Ensures CASE WHEN true THEN 'MANAGER' ELSE 'CONSULTANT' END is generated correctly.
Motivation
This change resolves compatibility issues with Hibernate (e.g., version 6.2.22) where enum parameters in CASE WHEN clauses lead to exceptions like ConversionException. It also aligns with common JPQL practices by using string literals for enums, improving query readability and reliability.
Related Issues
Potentially related to Issue #3755, where enum handling in QueryDSL causes problems with Hibernate.
Impact
Backward Compatibility: Minimal impact expected, as this change only affects enum handling in CaseBuilder and similar constructs. Existing parameter binding behavior for non-enum constants remains unchanged.
Performance: No significant overhead; the change avoids unnecessary parameter binding for enums.
Looking forward to feedback from the community! This is my first contribution, so please let me know if there's anything I can improve.