fix: prevent stale tuples after a join - #2532
Open
triceo wants to merge 1 commit into
Open
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses correctness issues in Bavet join/ifExists processing where tuples can be observed “stale” (already retracting/inactive) when read from join storage, leading to invalid filtering evaluation and potentially incorrect intermediate tuple propagation.
Changes:
- Add “from right” guarded insertion/update paths in
AbstractJoinNodeand use them when iterating right tuples from storage during left insert/update. - Update indexed and unindexed join nodes to avoid inserting out-tuples based on retracting right tuples.
- Add regression tests covering the “stale right” scenario across join variants (indexed/unindexed, join/ifExists).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/bi/AbstractBiConstraintStreamTest.java | Adds regression tests reproducing stale-right tuple scenarios for join/ifExists. |
| core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractUnindexedJoinNode.java | Uses guarded insertion when iterating right tuples from list storage during left insert. |
| core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractJoinNode.java | Introduces guarded “from right” insertion and update processing to skip inactive right tuples read from storage. |
| core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractIndexedJoinNode.java | Uses guarded insertion when iterating right tuples from index storage during left propagation. |
| core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractIfExistsNode.java | Skips updating counters from left when the right tuple is already retracting/inactive. |
|
Christopher-Chianelli
approved these changes
Jul 27, 2026
| * Reachable when the right input's node sits in a higher layer than the left input's, | ||
| * which delivers the left's inserts before the right's retracts. | ||
| */ | ||
| protected final void insertOutTupleFilteredFromRight(LeftTuple_ leftTuple, UniTuple<Right_> rightTuple) { |
Contributor
There was a problem hiding this comment.
TBH not in love with this name; maybe insertOutTupleIfRightActiveFiltered?
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.



I fixed a left input variant of this a long time ago.
With the help of Claude, I was finally able to reproduce this from the right as well, and fixed it.