fix(mapper): honor first-position @Or/@And in compound composition - #210
Open
shihyuho wants to merge 1 commit into
Open
fix(mapper): honor first-position @Or/@And in compound composition#210shihyuho wants to merge 1 commit into
shihyuho wants to merge 1 commit into
Conversation
CompoundSpecification folded its specs with a seedless reduce, so the first-declared spec became the accumulator and its @Or/@and wrapper was never inspected. The same annotated criteria class therefore produced opposite SQL depending on field declaration order. The fold now stably sorts elements that override the compound's default operator to the end before reducing, so composition is permutation invariant — any declaration order of the same annotated fields yields the same predicate. Previously-ignored first-position wrappers now take effect, so affected consumers' result sets change. Also narrows the specs field/constructor from Collection to List so the ordered fold is an explicit contract, and rejects a field carrying both @and and @or with a descriptive IllegalArgumentException instead of silently resolving to And. Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
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.
Closes #193
Problem
CompoundSpecification.toPredicatefolded its specs with a seedlessspecs.stream().reduce(this::combine).combine(result, element)only ever inspects the element's wrapper, so the first-declared spec — which becomes the accumulator — never had its@Or/@Andwrapper examined.The same annotation therefore produced opposite SQL depending purely on field declaration position:
[@Or nickname, name]nickname AND name(the@Orsilently dropped)name OR nickname[name, @Or nickname]name OR nicknamename OR nicknameMirrored for a field-level
@Andunder a class-level@Or(Disjunction).Changes
CompoundSpecification(COR-15/COR-16) — before reducing, the fold now stably sorts elements that override the compound's default operator to the end. The accumulator is then always a default-operator element, so no wrapper is ever ignored. Composition becomes permutation invariant: any declaration order of the same annotated fields yields the same predicate.CompoundSpecification(MAINT-08) —specsfield and theConjunction/Disjunctionconstructors narrowed fromCollectiontoList, making the ordered fold an explicit contract rather than a dependency on an unspecified iteration order. All existing call sites (SpecMapper,JoinSpecificationResolver,JoinFetchSpecificationResolver) already pass aList, so no caller changed.Conjunction/Disjunction— newoverridesOperator(element)hook expressing which wrapper flips the default operator;combineis now defined in terms of it, so the two stay in sync.SimpleSpecificationResolver(MAINT-09) — a field carrying both@Andand@Ornow fails fast with a descriptiveIllegalArgumentExceptionnaming the offending class and field, instead of silently resolving toAnd.Verification
make testgreen — 119 mapper + 15 starter tests, 0 failures (JDK 17, the project's declared target).ConjunctionTest/DisjunctionTest(8 tests each) — direct unit tests forcombineandoverridesOperator; first-position wrapper honored; two-element and exhaustive three-element permutation-invariance assertions over real query result sets; all-wrapped and empty-spec edge cases.SimpleSpecificationResolverTest—forceOr3/forceAnd2pin a first-position@Orunder class-level AND and a first-position@Andunder class-level OR;forceOrIsPermutationInvariant/forceAndIsPermutationInvariantassert every declaration order of the same criteria returns the same rows;andOrAreMutuallyExclusivepins the new fail-fast.Previously-ignored first-position
@Or/@Andwrappers now take effect, so affected consumers' result sets change. This is a bug fix restoring the annotations' documented semantics — a first-position@Orthat used to be silently dropped now actually ORs.The existing
forceOr2test is the concrete illustration:[name, @Or birthday, gender]previously folded to(name OR birthday) AND genderand returned 2 rows; it now folds to(name AND gender) OR birthdayand returns 3 rows — the same result as its permutationforceOr([name, gender, @Or birthday]), which was already returning 3. Its assertion was updated accordingly, and that update is the regression proof for COR-15.Two notes for reviewers:
Collection→Listnarrowing of the publicConjunction/Disjunctionconstructors is source-incompatible for anyone constructing them directly with a non-ListCollection. Per the plan this is framed as a fix rather than a breaking change, so noBREAKING CHANGE:footer was added — flagging it in case you'd rather it drive a major bump.NestedSpecificationResolverhas the same silent@And-wins behavior as MAINT-09, but it sits outside this work package's scope and was deliberately left untouched.🤖 Generated with Claude Code