eXist-db / exist

eXist Native XML Database and Application Platform
https://exist-db.org
GNU Lesser General Public License v2.1
429 stars 179 forks source link

[BUG] intersect expression triggers error on persistent elements #4255

Open joewiz opened 2 years ago

joewiz commented 2 years ago

Describe the bug

When the intersect expression is called on elements stored in the database, eXist throws the following unexpected error on functions whose signatures indicate their return type must be element():

Return type of function name_of_function. exerr:ERROR Type error: expected type: element(); got: node()

I believe the error is related to this line in the eXist source code: https://github.com/eXist-db/exist/blob/5bc6a35fd6d4a91742637aefe4ad53c2324284ee/exist-core/src/main/java/org/exist/xquery/Intersect.java#L48

Expected behavior

When called on elements, the intersect expression should not cause functions that expect to return an element to throw an error.

To Reproduce

The following xqsuite tests the effect of intersect on nodes stored in the database and in memory. It shows that a bug affects intersect when used on nodes stored in the database, when functions declare that they return element(). The bug is not exhibited with affect in-memory nodes.

xquery version "3.1";

module namespace t="http://exist-db.org/xquery/test";

declare namespace test="http://exist-db.org/xquery/xqsuite";

declare variable $t:XML := document {
    <root>
        <x/>
        <y/>
        <z/>
    </root>
};

declare
    %test:setUp
function t:setup() {
    let $testCol := xmldb:create-collection("/db", "test")
    return
        xmldb:store($testCol, "test.xml", $t:XML)
};

declare
    %test:tearDown
function t:tearDown() {
    xmldb:remove("/db/test")
};

declare
    %test:assertEquals("<y/>")
function t:intersect-no-return-type-db() {
    let $root := doc("/db/test/test.xml")/root
    let $seq1 := ($root/x, $root/y)
    let $seq2 := ($root/y, $root/z)
    let $intersect := $seq1 intersect $seq2
    return
        $intersect
};

declare
    %test:assertEquals("<y/>")
function t:intersect-element-return-type-db() as element() {
    let $root := doc("/db/test/test.xml")/root
    let $seq1 := ($root/x, $root/y)
    let $seq2 := ($root/y, $root/z)
    let $intersect := $seq1 intersect $seq2
    return
        $intersect
};

declare
    %test:assertEquals("<y/>")
function t:intersect-no-return-type-mem() {
    let $root := $t:XML/root
    let $seq1 := ($root/x, $root/y)
    let $seq2 := ($root/y, $root/z)
    let $intersect := $seq1 intersect $seq2
    return
        $intersect
};

declare
    %test:assertEquals("<y/>")
function t:intersect-element-return-type-mem() as element() {
    let $root := $t:XML/root
    let $seq1 := ($root/x, $root/y)
    let $seq2 := ($root/y, $root/z)
    let $intersect := $seq1 intersect $seq2
    return
        $intersect
};

This xqsuite returns the following error:

<testsuite package="http://exist-db.org/xquery/test" timestamp="2022-02-26T01:05:11.139-05:00"
    tests="4" failures="0" errors="1" pending="0" time="PT0.002S">
    <testcase name="intersect-element-return-type-db" class="t:intersect-element-return-type-db">
        <error type="err:XPTY0004"
            message="It is a type error if, during the static analysis phase, an expression is found to have a static type that is not appropriate for the context in which the expression occurs, or during the dynamic evaluation phase, the dynamic type of a value does not match a required type as specified by the matching rules in 2.5.4 SequenceType Matching. Return type of function 't:intersect-element-return-type-db'. exerr:ERROR Type error: expected type: element(); got: node()"
        />
    </testcase>
    <testcase name="intersect-element-return-type-mem" class="t:intersect-element-return-type-mem"/>
    <testcase name="intersect-no-return-type-db" class="t:intersect-no-return-type-db"/>
    <testcase name="intersect-no-return-type-mem" class="t:intersect-no-return-type-mem"/>
</testsuite>

In my tests, the other constructor expressions—union and except—do not have this problem.

Screenshots

n/a

Context (please always complete the following information):

Additional context

joewiz commented 2 years ago

Digging back into the history of the code, it appears that the code applying "toNodeSet" to the inputs for the case of persistent data (i.e., data stored in the database) was added here:

https://github.com/eXist-db/exist/blame/a2aab73f1666dba23ba9eca3c98598acec134a4a/src/org/exist/xquery/Intersection.java#L61-L62