Visitar URL original
fix(#10512, #10513, #10515): batch-boundary sensitive writes after a read of more than 100 rows by lvca · Pull Request #10549 · ArcadeData/arcadedb · GitHub
Skip to content

fix(#10512, #10513, #10515): batch-boundary sensitive writes after a read of more than 100 rows - #10549

Merged
lvca merged 2 commits into
mainfrom
fix/issues-10512-10513-10515
Oct 21, 2026
Merged

lvca merged 2 commits into
mainfrom
fix/issues-10512-10513-10515

Conversation

@lvca

@lvca lvca commented Oct 21, 2026

Copy link
Copy Markdown
Member

Fixes #10512, fixes #10513, fixes #10515.

Summary

A no-match OPTIONAL MATCH ... WHERE false followed by WITH * is the identity on the rows that reach it, yet it changed what a write placed after it did to a result of more than 100 rows. The pipeline hands rows over in batches of 100, and three code paths were sensitive to where the batch ended.

Verification

Not in this PR

UNWIND ... MERGE ... OPTIONAL MATCH ()-[r3]->() (unanchored) still answers 102 x 100 rows where Neo4j answers 102 x 102: the chained scan of MatchNodeStep is a snapshot of the first batch. Making it complete needs a write-then-read eager barrier, a larger semantic change that deserves its own issue.

…walk first, a write reaches the paths of its row, MERGE and CREATE variables are bound for the MATCH after them

A no-match OPTIONAL MATCH followed by WITH * changed what a write did past the 100th row, because the pipeline hands rows over in batches of 100 and three paths were sensitive to where the batch ended. A CALL subquery whose body deletes now gets the read-ahead barrier of an outer DELETE when the rows come from a variable-length or disconnected MATCH. A write through a variable now also updates the nodes and relationships of the paths the row carries. The variables a MERGE or CREATE binds are counted as bound, so a following MATCH ending at one starts from that entity instead of scanning from a snapshot taken before the later batches were written.
@lvca lvca added this to the 26.11.1 milestone Oct 21, 2026
@lvca lvca self-assigned this Oct 21, 2026
@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 10 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d3b68f23-6b9b-4201-90cb-a878c952364d
📥 Commits

Reviewing files that changed from the base of the PR and between 1ba4226 and ca903f1.

📒 Files selected for processing: 6
  • engine/src/main/java/com/arcadedb/query/opencypher/executor/CypherExecutionPlan.java
  • engine/src/main/java/com/arcadedb/query/opencypher/executor/RowAliases.java
  • engine/src/main/java/com/arcadedb/query/opencypher/executor/steps/AggregationStep.java
  • engine/src/main/java/com/arcadedb/query/opencypher/traversal/TraversalPath.java
  • engine/src/test/java/com/arcadedb/query/opencypher/Issue10512IdentityOptionalMatchBatchBoundaryTest.java
  • engine/src/test/java/com/arcadedb/query/opencypher/traversal/TraversalPathWithRecordTest.java
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mergify

mergify Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again.

@claude

claude Bot commented Oct 21, 2026

Copy link
Copy Markdown

Review

Overall this looks solid. The three fixes are small and targeted, they reuse existing mechanisms (withEagerBarrier, matchClausesNeedEagerDelete, collectPatternVariables), and the tests cover each issue and its neighbours. I found no security concerns: there are no new dependencies, no new I/O and no change to auth paths.

What I checked

  • TraversalPath.withRecord is copy-on-write and returns this by identity when nothing changed. That keeps shared paths safe and makes the common case cheap. The private constructor it calls exists. Repeated visits of the same node are handled by scanning from firstStale.
  • RowAliases.propagateUpdate only walks the row's top-level values. Rows without a path variable pay one instanceof check per property, which is a small cost.
  • collectPatternVariables was refactored to take a List<PathPattern>, and it now also adds path variables. It is shared with the MATCH case, so path variables are now counted as bound after a MATCH too. That looks right, but it is a behaviour change worth keeping in mind for the reversal logic.
  • The SUBQUERY barrier is gated on subqueryBodyDeletes and on the same conditions as the outer DELETE, so plain scans keep streaming. The explain test pins this.

Minor suggestions (non-blocking)

  1. propagateUpdate replaces the stale path in the current row only. The AggregationStep comment says another row's path is not touched. That is fine, but a short test that two rows sharing the same TraversalPath instance don't affect each other would document the copy-on-write contract end to end.
  2. In subqueryBodyDeletes, a body with no getClausesInOrder() falls back to body.hasDelete(). A test with a UNION body, or a nested subquery containing a delete, would exercise the recursive branches. At the moment only the direct DETACH DELETE body is covered.
  3. In TraversalPathWithRecordTest, the field alignment (private Vertex a;) is slightly inconsistent with the rest of the code base. This is cosmetic.
  4. The new regression test class runs ~100-row queries and a tetrahedron with variable-length paths up to depth 5. It looks fast, but if it shows up in the CI timings, consider @Tag("slow").

I did not run the build or tests locally. Nothing blocks the merge from my side.

🤖 Generated with Claude Code

@codacy-production

codacy-production Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 14 complexity

Metric Results
Complexity 14

View in Codacy

🟢 Coverage 94.92% diff coverage · -4.90% coverage variation

Metric Results
Coverage variation ✅ -4.90% coverage variation
Diff coverage ✅ 94.92% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (8b93bb7) 247758 215233 86.87%
Head commit (ca903f1) 280516 (+32758) 229947 (+14714) 81.97% (-4.90%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#10549) 59 56 94.92%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@claude

claude Bot commented Oct 21, 2026

Copy link
Copy Markdown

Review

Overall this is a clean, well-scoped fix for the three batch-boundary issues (#10512, #10513, #10515). I found no security concerns: no new dependencies, no I/O or parsing surface, and the changes are confined to the Cypher planner and row/path handling.

What looks good

Minor suggestions (non-blocking)

  1. In propagateUpdate, the loop now calls withRecord for every path in every row of every SET. It is cheap, but for long variable-length paths (up to *1..N) it is O(path length) per row per write. You could add a cheap pre-check (e.g. skip when the replacement is not a Vertex/Edge, which withRecord already does) - fine as is, just worth keeping in mind for benchmarks.
  2. withRecord only replaces members by RID. If the same path is shared across rows and the written record differs per row, that is fine because the path is never mutated, but a short comment in propagateUpdate saying so would help future readers.
  3. The comment in AggregationStep now has a very long line; consider re-wrapping.
  4. Issue10512...Test has some long query-string lines; fine, but consider text blocks for readability.

LGTM pending CI.

@codecov

codecov Bot commented Oct 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.74576% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.76%. Comparing base (8b93bb7) to head (ca903f1).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...query/opencypher/executor/CypherExecutionPlan.java 80.00% 2 Missing and 4 partials ⚠️
...dedb/query/opencypher/traversal/TraversalPath.java 86.36% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #10549      +/-   ##
============================================
- Coverage     78.77%   78.76%   -0.01%     
- Complexity     4736     4737       +1     
============================================
  Files          2284     2284              
  Lines        247758   247815      +57     
  Branches      53945    53968      +23     
============================================
+ Hits         195168   195194      +26     
- Misses        32565    32595      +30     
- Partials      20025    20026       +1     

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lvca
lvca merged commit fb6d4c0 into main Oct 21, 2026
33 of 37 checks passed
@lvca
lvca deleted the fix/issues-10512-10513-10515 branch October 21, 2026 23:49
robfrank pushed a commit that referenced this pull request Nov 5, 2026
…read of more than 100 rows (#10549)

* fix(#10512, #10513, #10515): a deleting CALL body drains a breakable walk first, a write reaches the paths of its row, MERGE and CREATE variables are bound for the MATCH after them

A no-match OPTIONAL MATCH followed by WITH * changed what a write did past the 100th row, because the pipeline hands rows over in batches of 100 and three paths were sensitive to where the batch ended. A CALL subquery whose body deletes now gets the read-ahead barrier of an outer DELETE when the rows come from a variable-length or disconnected MATCH. A write through a variable now also updates the nodes and relationships of the paths the row carries. The variables a MERGE or CREATE binds are counted as bound, so a following MATCH ending at one starts from that entity instead of scanning from a snapshot taken before the later batches were written.

* test(#10512): cover a delete buried in a FOREACH, a nested subquery and a UNION branch of a CALL body

(cherry picked from commit fb6d4c0)
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.

MERGE-created relationship is missing from the following OPTIONAL MATCH on row 101 Path endpoint properties disagree with the same bound node after REMOVE A semantically inert OPTIONAL MATCH changes correlated delete results

1 participant