Skip to content

Fix #2205: context problem with map:get() in xpath predicate#6088

Open
joewiz wants to merge 1 commit intoeXist-db:developfrom
joewiz:fix/2205-context-problem-map-get-predicate
Open

Fix #2205: context problem with map:get() in xpath predicate#6088
joewiz wants to merge 1 commit intoeXist-db:developfrom
joewiz:fix/2205-context-problem-map-get-predicate

Conversation

@joewiz
Copy link
Member

@joewiz joewiz commented Mar 3, 2026

Summary

  • Fix GeneralComparison.nodeSetCompare() else branch to evaluate the right operand per-node using the context chain (item.getContext().getNode()) instead of the entire contextSequence. This resolves XPTY0004 errors when predicates like [@k2 = map:get($map, @k1)] are evaluated against stored (on-disk) nodes.
  • Add regression tests for both = (general comparison) and eq (value comparison) variants of the bug.
  • Add a GeneralComparisonBenchmark for measuring predicate evaluation performance (guarded by -Dexist.run.benchmarks=true).

Root cause

In nodeSetCompare(), the else branch (used when the context documents contain the node documents) evaluated the right operand with contextSequence (the whole node set) and null contextItem. For map:get($map, @k1), this caused @k1 to resolve against all nodes at once instead of per-node, producing multiple values where a single one was expected.

The fix mirrors the if-branch's approach: use context.getNode().toSequence() to evaluate the right operand against the correct parent element for each node.

Performance benchmark

GeneralComparisonBenchmark is excluded from the normal test suite (the class name does not match Surefire's *Test discovery pattern, and an Assume guard requires -Dexist.run.benchmarks=true). To run it:

mvn test -pl exist-core -Dtest=GeneralComparisonBenchmark \
    -Dexist.run.benchmarks=true -Ddependency-check.skip=true

Benchmarked with an embedded eXist instance, 3 warmup + 5 measured iterations per query. Two query types exercise the unchanged code path (no context chain needed); two exercise the changed code path (context-dependent right operand).

Query Size develop (before) fix branch (after) Δ
@v = 'v0' (literal) 100 2.37 ms 2.43 ms +3%
500 4.22 ms 4.61 ms +9%
2000 8.07 ms 9.99 ms +24%
@v = $x (variable) 100 1.14 ms 0.90 ms −21%
500 2.84 ms 1.78 ms −37%
2000 6.87 ms 4.44 ms −35%
@v = map:get($map, @k) 100 ❌ XPTY0004 2.50 ms ✅ fixed
500 ❌ XPTY0004 5.35 ms ✅ fixed
2000 ❌ XPTY0004 13.60 ms ✅ fixed
@v = local:get(@k) 100 ❌ XPTY0004 2.77 ms ✅ fixed
500 ❌ XPTY0004 6.54 ms ✅ fixed
2000 ❌ XPTY0004 15.59 ms ✅ fixed

Key takeaways:

  • No performance regression on the unchanged code paths. The literal-comparison results are within normal JVM microbenchmark noise; the variable-comparison path actually improved (likely JIT warmup variance).
  • The context-dependent queries (map:get, local:get) crashed on develop with XPTY0004 and now work correctly at reasonable latency on the fix branch.
  • The fix adds a single if (ctxItem != null) branch check per node — negligible overhead.

Test plan

  • xquery.optimizer.OptimizerTests — 60/60 passed (includes 2 new regression tests)
  • xquery.dates.DateTests — 121/121 passed (no regressions)
  • xquery.maps.MapTests — 124/124 passed (no regressions)
  • Full exist-core test suite — 6496 tests, 0 failures, 0 errors
  • GeneralComparisonBenchmark — all 4 query variants pass at 3 data sizes

🤖 Generated with Claude Code

@joewiz joewiz requested a review from a team as a code owner March 3, 2026 15:38
@joewiz joewiz force-pushed the fix/2205-context-problem-map-get-predicate branch from 0aabb46 to a122438 Compare March 3, 2026 15:40
@joewiz
Copy link
Member Author

joewiz commented Mar 3, 2026

cc/ @djbpitt

Fix eXist-db#2205: When a predicate like [@K2 = map:get($map, @k1)] is
evaluated against stored nodes, the nodeSetCompare() else branch in
GeneralComparison evaluated the right operand with contextSequence
(the entire node set) rather than per-node. This caused @k1 to resolve
against all nodes at once, producing XPTY0004 errors.

Fix the else branch to use the context chain (item.getContext()) when
available, mirroring the if-branch's per-node evaluation via
context.getNode().toSequence(). Falls back to contextSequence only when
no context chain exists.

Add regression tests for the = (general comparison) and eq (value
comparison) variants, plus a performance benchmark for predicate
evaluation with context-dependent right operands.

Closes eXist-db#2205

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@joewiz joewiz force-pushed the fix/2205-context-problem-map-get-predicate branch from a122438 to eea8395 Compare March 5, 2026 01:40
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.

1 participant