fix(document): handle nested paths in optimisticConcurrency include/exclude#16094
Conversation
…xclude When using `optimisticConcurrency` with an include array or an exclude object, nested paths were not handled properly. Excluding `profile` did not exclude `profile.firstName`, and including `profile.address.country` did not trigger versioning when setting `profile.address`. Added two helper functions that check parent-child path relationships: - `_isExcludedByOptCon`: a path is excluded if it or any parent prefix is in the exclude set - `_isIncludedByOptCon`: a path is included if it or any parent prefix is in the include set, or if any included path is a child of the modified path Fixes Automattic#16054
There was a problem hiding this comment.
Pull request overview
This PR fixes optimisticConcurrency path matching so include/exclude configurations correctly account for nested path relationships (parent ↔ child), addressing gh-16054.
Changes:
- Update
Document#$__delta()optimistic concurrency checks to treat nested paths as relevant when appropriate (prefix/descendant matching rather than exactSet.has()). - Add internal helpers for include/exclude nested-path evaluation.
- Add a focused test suite for nested include/exclude behavior in optimistic concurrency.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
lib/document.js |
Implements nested-path aware include/exclude checks for optimistic concurrency in $__delta() via new helper functions. |
test/versioning.test.js |
Adds regression tests covering nested include/exclude behavior for optimistic concurrency. |
| // Check if any included path is a child (e.g. modifying 'profile.address' is | ||
| // relevant when 'profile.address.country' is included) | ||
| const prefix = path + '.'; | ||
| for (const p of includeSet) { | ||
| if (p.startsWith(prefix)) { | ||
| return true; | ||
| } | ||
| } |
vkarpov15
left a comment
There was a problem hiding this comment.
A couple of performance related suggestions.
| return true; | ||
| } | ||
| // Check if any parent prefix is included (e.g. 'profile' includes 'profile.name') | ||
| const pieces = path.split('.'); |
There was a problem hiding this comment.
Need to make sure to check if path contains '.' before splitting for performance
| * handling nested paths in both directions. | ||
| */ | ||
|
|
||
| function _isIncludedByOptCon(path, includeSet) { |
There was a problem hiding this comment.
I'd like to see if there is a way to reuse split values in this function similar to hasIncludedChildren for projections in lib/document.js. This check is very expensive - lots of split() calls, loops. It would be ideal to avoid this by precomputing the split values from this.$__schema.options.optimisticConcurrency
Problem
When using
optimisticConcurrencywith include arrays or exclude objects, nested paths are not handled properly.Exclude case: Excluding
profiledoes not excludeprofile.firstName. Modifyingprofile.firstNamestill triggers versioning even though its parent is excluded.Include case: Including
profile.address.countrydoes not trigger versioning when settingprofile.address(the parent).Root Cause
The existing code uses
Set.has()for exact path matching, which doesn't account for parent-child path relationships.Solution
Added two helper functions:
_isExcludedByOptCon(path, excludeSet): a path is excluded if it matches exactly OR any of its parent prefixes is in the exclude set (e.g.profileexcludesprofile.firstName)_isIncludedByOptCon(path, includeSet): a path is included if it matches exactly, OR any parent prefix is included, OR any included path is a child of the modified path (e.g. modifyingprofile.addresstriggers whenprofile.address.countryis included)Testing
Added 5 new test cases in
test/versioning.test.jscovering:All 22 optimisticConcurrency tests pass.
Fixes #16054