Skip to content

Add TypeWrapperFactoryExpression for Type-Safe Custom Number Mapping in Querydsl Aggregations#1181

Merged
velo merged 2 commits intoOpenFeign:masterfrom
chadongmin:type-wrapper-factoryexpression
Jun 9, 2025
Merged

Add TypeWrapperFactoryExpression for Type-Safe Custom Number Mapping in Querydsl Aggregations#1181
velo merged 2 commits intoOpenFeign:masterfrom
chadongmin:type-wrapper-factoryexpression

Conversation

@chadongmin
Copy link
Contributor

Background & Problem

Querydsl’s aggregation functions (e.g., sum(), sumAggregate()) return raw numeric types like BigDecimal or Double. However, when attempting to map these directly to custom Number subtypes (e.g., Money, MyCustomNumber), Querydsl throws:

IllegalArgumentException: Unsupported target type : MyCustomNumber

This limitation forces developers to write manual conversion logic, increasing boilerplate and risking errors in complex queries. The issue was originally reported in Issue #3550 and persists in the latest Querydsl release (6.11). A reproducible example is provided here:
https://github.com/chadongmin/querydsl-jpa-customnumber-aggregate-conversion-bug


Discussion

The problem stems from Querydsl’s internal casting mechanism (MathUtils#cast), which lacks support for custom Number subclasses. Community feedback highlighted two viable strategies:

  1. Extend MathUtils#cast
    Modify the core cast method to automatically handle custom Number subclasses.

  2. Introduce TypeWrapper Abstraction
    Create a TypeWrapper mechanism to carry target-type metadata for specific expressions, enabling type-safe mapping only when explicitly applied.


Deliberation

Extending MathUtils#cast would have required invasive changes to a core utility used by every numeric operation, risking regressions and complicating future QueryDSL upgrades. By contrast, TypeWrapper lets me opt in only where needed, avoids touching existing casting logic, and cleanly isolates the custom-type support in one place—greatly simplifying maintenance and preserving backward compatibility.

Chosen Solution

I’ve implemented the TypeWrapper extension for FactoryExpression contexts. Now, any expression—be it sum(), avg(), or a factory-based projection—wrapped with TypeWrapper.wrap(expr, MyCustomNumber.class) is correctly serialized and mapped back to the custom type, while all unwrapped code paths remain unchanged.

  • Added TypeWrapper<S,T> to querydsl-core as a FactoryExpression implementation.
  • Added JPAQueryCustomTypeWrapperTest in querydsl-jpa covering:
    • failure without wrapper
    • success with direct wrapper
    • success in @QueryProjection DTO.

Next Steps
If there’s a better approach or any improvements you’d suggest, please let me know—I’ll be happy to incorporate your feedback into this PR.

- Implement TypeWrapper<S, T> as a FactoryExpression
- Allows converting a source Expression (e.g. BigDecimal) to a domain type (e.g. Money)
- Prepares QueryDSL core to support custom aggregation projections without core API changes
- Verify IllegalArgumentException for unsupported custom types without wrapper
- Verify sum-and-wrap to Money via TypeWrapper in both direct and DTO projection use cases
- Ensure both positive and negative paths are exercised
@codecov
Copy link

codecov bot commented May 28, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 71.04%. Comparing base (4980432) to head (3de6536).
Report is 33 commits behind head on master.

✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1181      +/-   ##
============================================
+ Coverage     61.19%   71.04%   +9.85%     
  Complexity       84       84              
============================================
  Files           831      858      +27     
  Lines         32015    32359     +344     
  Branches       3590     3600      +10     
============================================
+ Hits          19590    22988    +3398     
+ Misses        11179     8148    -3031     
+ Partials       1246     1223      -23     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@velo velo merged commit 63b4a04 into OpenFeign:master Jun 9, 2025
6 checks passed
velo pushed a commit that referenced this pull request Jun 9, 2025
Add `TypeWrapperFactoryExpression` for Type-Safe Custom Number Mapping in Querydsl Aggregations (#1181)

* Introduce TypeWrapper to wrap Expression results into custom types

- Implement TypeWrapper<S, T> as a FactoryExpression
- Allows converting a source Expression (e.g. BigDecimal) to a domain type (e.g. Money)
- Prepares QueryDSL core to support custom aggregation projections without core API changes

* Add JPAQueryCustomTypeWrapperTest covering success and failure scenarios

- Verify IllegalArgumentException for unsupported custom types without wrapper
- Verify sum-and-wrap to Money via TypeWrapper in both direct and DTO projection use cases
- Ensure both positive and negative paths are exercised

Co-authored-by: 차동민 <40655807+chadongmin@users.noreply.github.com>
chadongmin added a commit to chadongmin/querydsl that referenced this pull request Jun 9, 2025
…g in Querydsl Aggregations (OpenFeign#1181)

* Introduce TypeWrapper to wrap Expression results into custom types

- Implement TypeWrapper<S, T> as a FactoryExpression
- Allows converting a source Expression (e.g. BigDecimal) to a domain type (e.g. Money)
- Prepares QueryDSL core to support custom aggregation projections without core API changes

* Add JPAQueryCustomTypeWrapperTest covering success and failure scenarios

- Verify IllegalArgumentException for unsupported custom types without wrapper
- Verify sum-and-wrap to Money via TypeWrapper in both direct and DTO projection use cases
- Ensure both positive and negative paths are exercised
velo added a commit that referenced this pull request Jun 9, 2025
* Add `TypeWrapperFactoryExpression` for Type-Safe Custom Number Mapping in Querydsl Aggregations (#1181)

* Introduce TypeWrapper to wrap Expression results into custom types

- Implement TypeWrapper<S, T> as a FactoryExpression
- Allows converting a source Expression (e.g. BigDecimal) to a domain type (e.g. Money)
- Prepares QueryDSL core to support custom aggregation projections without core API changes

* Add JPAQueryCustomTypeWrapperTest covering success and failure scenarios

- Verify IllegalArgumentException for unsupported custom types without wrapper
- Verify sum-and-wrap to Money via TypeWrapper in both direct and DTO projection use cases
- Ensure both positive and negative paths are exercised

* Fix: Improve handling of `contains()` on JPA collections mapped with `@Converter` to basic types (#1199)

* Fix: Early error for .contains() on @converter mapped JPA collections

* Test : add test code

* Add test cases to improve test coverage

* style: revert formatting change based on review feedback for consistency

* fix: correct escape & anchor handling in likeToRegex/regexToLike (#1174)

* fix: correct escape & anchor handling in likeToRegex/regexToLike
- treat \% \_ as literals, escape regex metachars, strip outer ^/$ only
- add unit tests to guarantee accurate LIKE ↔ regex round-trips

* Fix code format

Signed-off-by: Marvin Froeder <velo.br@gmail.com>

---------

Signed-off-by: Marvin Froeder <velo.br@gmail.com>
Co-authored-by: Marvin Froeder <velo.br@gmail.com>

---------

Signed-off-by: Marvin Froeder <velo.br@gmail.com>
Co-authored-by: 차동민 <40655807+chadongmin@users.noreply.github.com>
Co-authored-by: renechoi <115696395+renechoi@users.noreply.github.com>
Co-authored-by: Marvin Froeder <velo.br@gmail.com>
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