Skip to content

[BUG] Unnamed kind tests on the self axis are always true in predicate position on persistent nodes #6705

Description

@joewiz

[This issue was prompted by Joe, drafted by Claude Code, and reviewed by Joe.]

Summary

Used as an axis-step predicate, an unnamed kind test on the self axis — [self::comment()], [self::text()], [self::element()], [self::processing-instruction()] — matches every node on the axis when the nodes are persistent. Nothing is filtered, so the step silently returns more than it should.

The same tests are correct on in-memory nodes, and correct in step position (/self::comment()) on persistent nodes. Only the predicate form on persistent nodes is affected.

This is a silent wrong-result bug in the opposite direction from #6689: that one dropped data, this one adds it. Neither raises an error.

Reproducing

Store a document with three child nodes — two processing instructions and one element:

xmldb:store('/db', 'probe.xml', '<root><?target inner?><?other x?><a/></root>', 'application/xml')

Then count what each predicate selects:

let $r := doc('/db/probe.xml')/root
return (
    count($r/node()[self::comment()]),              (: expected 0 :)
    count($r/node()[self::text()]),                 (: expected 0 :)
    count($r/node()[self::element()]),              (: expected 1 :)
    count($r/node()/self::comment())                (: expected 0 — step position :)
)

Actual

expression persistent in-memory expected
$r/node()[self::comment()] 3 0 0
$r/node()[self::text()] 3 0 0
$r/node()[self::element()] 3 1 1
$r/node()/self::comment() (step) 0 0 0
$r/node()[self::element(a)] (named) 1 1 1

Three is the total child count — every predicate returns the whole axis.

Cause

In LocationStep.getSelf, the wildcard branch splits on whether a context id is in play:

if (Expression.NO_CONTEXT_ID != contextId) {
    // ... stamps contexts on matching nodes ...
    return contextSet;          // ← the whole context set, unfiltered
} else {
    final NewArrayNodeSet results = new NewArrayNodeSet();
    for (final NodeProxy p : contextSet) {
        if (test.matches(p)) {
            results.add(p);     // ← correctly filtered
        }
    }
    return results;
}

The NO_CONTEXT_ID path — step position — filters correctly. The predicate path returns contextSet whole, having only stamped context nodes onto the members that actually matched. Whatever downstream filtering that stamping was meant to drive does not happen, so the predicate is satisfied by every node.

Named tests are unaffected because they take the other branch entirely: a NameTest is not a wildcard test, so it goes to the structural-index lookup, which filters by construction.

Relationship to other issues

Notes

I have not attempted a fix. The predicate path looks like it is relying on the context-stamping mechanism to filter downstream, so the right change depends on whether that mechanism is supposed to filter here and is failing to, or whether this branch should simply filter like its sibling does. That is a question for someone who knows what the context-id path is meant to guarantee.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugissue confirmed as bugxqueryissue is related to xquery implementation

    Type

    No type

    Projects

    • Status
      In progress

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions