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

Fix Enum Serialization in CaseBuilder for Hibernate Compatibility #966

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

chadongmin
Copy link

@chadongmin chadongmin commented Mar 6, 2025

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.

@Override
   public void visitConstant(Object constant) {
       if (constant instanceof Enum && inCaseOperation) {
           visitLiteral(constant);
           return;
       }
       var wrap = templates.wrapConstant(constant);
       if (wrap) {
           append("(");
       }
       super.visitConstant(constant);
       if (wrap) {
           append(")");
       }
   }

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'

public String asLiteral(Object constant) {
    if (constant instanceof Enum) {
        return "'" + ((Enum<?>) constant).name() + "'";
    }
    // ... existing logic ...
}

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.

@Override
protected void visitOperation(Class<?> type, Operator operator, List<? extends Expression<?>> args) {
    var oldInCaseOperation = inCaseOperation;
    inCaseOperation = CASE_OPS.contains(operator) || operator.equals(Ops.CASE_WHEN);
    var oldWrapElements = wrapElements;
    wrapElements = templates.wrapElements(operator);
    // ... existing logic ...
    inCaseOperation = oldInCaseOperation;
    wrapElements = oldWrapElements;
}

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.

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.

@chadongmin chadongmin force-pushed the fix/enum-case-builder-hibernate branch from 58336f4 to ec9d247 Compare March 6, 2025 07:53
@chadongmin
Copy link
Author

@velo
Hi! I've submitted this PR to fix enum serialization in CaseBuilder (details in the description). It's passing all tests locally, and I’d appreciate your review whenever you have time. Happy to address any feedback!

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.

1 participant