Add @Formula2 support on @ManyToOne association properties#3798
Merged
Conversation
Extends @formula2 so it can be placed on a @manytoone field. Instead of embedding physical SQL aliases (as @formula(join=...) requires), a @formula2 expression uses logical bean paths — the required joins are derived automatically. Example `` // Before — physical SQL, brittle alias wiring: @formula(select = "coalesce(${ta}.some_bean_id, j1.some_bean_id)", join = PARENTS_JOIN) @manytoone EBasic effectiveBean; ``` ``` // After — logical paths, joins resolved automatically: @formula2("coalesce(someBean.id, parent.someBean.id)") @manytoone EBasic effectiveBean; `` The generated SQL is identical; the annotation is far more readable and maintainable. What changed Annotation parsing (AnnotationAssocOnes) — @formula2 on a @manytoone is now recognised and the expression parsed into a select fragment and a set of dependency join paths. Query tree building (SqlTreeBuilder) — three scenarios all handled correctly: - Fetched as a tree node — dependency joins are inserted before the formula2 join using addChildFirst - Partial parent fetch — dependency joins are registered even when the parent chunk only selects its ID column - Predicate-only (where clause, no fetch) — addFormula2JoinsFromPredicates runs before buildSelectChain so dependency join paths are populated before buildExtraJoins constructs the extra-join tree; IncludesDistiller uses addChildFirst to preserve ordering within the extra-join tree Init ordering (BeanDescriptorManager) — initFormula2Properties() moved to a dedicated pass 5, after all descriptors are fully initialised, so cross-descriptor path resolution (e.g. parent.parent.someBean.id) is always safe. SqlTreeNodeExtraJoin — gained addChildFirst() to match SqlTreeNodeBean, enabling formula2 dependency joins to be prepended ahead of the formula2 property join in the extra-join tree. Fixes Supersedes and resolves #2773 — the reported bug (wrong join ordering when combining fetch and where on a formula-joined field) is eliminated for ChildPerson.effectiveBean and ParentPerson.effectiveBean, which are now expressed as @formula2.
rob-bygrave
added a commit
that referenced
this pull request
Jul 1, 2026
Extends @formula2 so it can be placed on a @manytoone field. Instead of embedding physical SQL aliases (as @formula(join=...) requires), a @formula2 expression uses logical bean paths — the required joins are derived automatically. Example `` // Before — physical SQL, brittle alias wiring: @formula(select = "coalesce(${ta}.some_bean_id, j1.some_bean_id)", join = PARENTS_JOIN) @manytoone EBasic effectiveBean; ``` ``` // After — logical paths, joins resolved automatically: @formula2("coalesce(someBean.id, parent.someBean.id)") @manytoone EBasic effectiveBean; `` The generated SQL is identical; the annotation is far more readable and maintainable. What changed Annotation parsing (AnnotationAssocOnes) — @formula2 on a @manytoone is now recognised and the expression parsed into a select fragment and a set of dependency join paths. Query tree building (SqlTreeBuilder) — three scenarios all handled correctly: - Fetched as a tree node — dependency joins are inserted before the formula2 join using addChildFirst - Partial parent fetch — dependency joins are registered even when the parent chunk only selects its ID column - Predicate-only (where clause, no fetch) — addFormula2JoinsFromPredicates runs before buildSelectChain so dependency join paths are populated before buildExtraJoins constructs the extra-join tree; IncludesDistiller uses addChildFirst to preserve ordering within the extra-join tree Init ordering (BeanDescriptorManager) — initFormula2Properties() moved to a dedicated pass 5, after all descriptors are fully initialised, so cross-descriptor path resolution (e.g. parent.parent.someBean.id) is always safe. SqlTreeNodeExtraJoin — gained addChildFirst() to match SqlTreeNodeBean, enabling formula2 dependency joins to be prepended ahead of the formula2 property join in the extra-join tree. Fixes Supersedes and resolves #2773 — the reported bug (wrong join ordering when combining fetch and where on a formula-joined field) is eliminated for ChildPerson.effectiveBean and ParentPerson.effectiveBean, which are now expressed as @formula2. Co-authored-by: robin.bygrave <robin.bygrave@eroad.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.
Extends
@Formula2so it can be placed on a@ManyToOnefield. Instead of embedding physical SQL aliases (as@Formula(join=...)requires), a@Formula2expression uses logical bean paths — the required joins are derived automatically.Example
The generated SQL is identical; the annotation is far more readable and maintainable.
What changed
Annotation parsing (AnnotationAssocOnes) —
@Formula2on a@ManyToOneis now recognised and the expression parsed into a select fragment and a set of dependency join paths.Query tree building (SqlTreeBuilder) — three scenarios all handled correctly:
Init ordering (BeanDescriptorManager) — initFormula2Properties() moved to a dedicated pass 5, after all descriptors are fully initialised, so cross-descriptor path resolution (e.g. parent.parent.someBean.id) is always safe.
SqlTreeNodeExtraJoin — gained addChildFirst() to match SqlTreeNodeBean, enabling formula2 dependency joins to be prepended ahead of the formula2 property join in the extra-join tree.
Fixes
Supersedes and resolves #2773 — the reported bug (wrong join ordering when combining fetch and where on a formula-joined field) is eliminated for ChildPerson.effectiveBean and ParentPerson.effectiveBean, which are now expressed as
@Formula2.