MarkLogic version: 8.0-3.2
It appears that if one has multiple namespace declarations with varying prefixes but same URIs inside a search:search options node (as in <element xmlns:bar="myuri:baz" xmlns:foo="myuri:baz">), every prefix except the first one gets lost in a search:search call.
Is this expected behavior? It is not present under ML7.0-4.3, and I am not aware of multiple declarations of the same namespace uri under different prefixes being in violation of XML Namespaces or xQuery specs.
Any insight is much appreciated.
Test setup script:
(:~
: Two transactions:
: (1) Create an element range index on element {myuri:baz}child in "Documents" database
: (2) Insert a test document at /baz/test/test-baz.xml
:
: Expected output: empty sequence
:)
(: Transaction (1): Set up index :)
xquery version "1.0-ml";
import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy";
let $config := admin:get-configuration(),
$dbid := xdmp:database("Documents"),
$rangespec := admin:database-range-element-index("string", "myuri:baz", "child", "http://marklogic.com/collation/", fn:false(), "reject")
return
try {
admin:save-configuration-without-restart(
admin:database-add-range-element-index($config, $dbid, $rangespec)
)
} catch($e) {
"Index already exists? Check logs.",
xdmp:log($e, "debug")
}
;
(: Transaction (2): Insert test document :)
xquery version "1.0-ml";
declare namespace baz = "myuri:baz";
let $uri := "/baz/test/test-baz.xml",
$document :=
<baz:root>
<baz:child>TEST</baz:child>
</baz:root>
return
xdmp:document-insert($uri, $document)
Test script (verbose for clarity/readability):
xquery version "1.0-ml";
import module namespace search = "http://marklogic.com/appservices/search" at "/MarkLogic/appservices/search/search.xqy";
(: additional-query: xmlns:foo first, xmlns:bar second; cts:element: foo:child. Succeeds. :)
declare variable $search-options-1 :=
<options xmlns="http://marklogic.com/appservices/search">
<additional-query xmlns:foo="myuri:baz" xmlns:bar="myuri:baz">
<cts:element-range-query operator="=">
<cts:element>foo:child</cts:element>
<cts:value xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">TEST</cts:value>
</cts:element-range-query>
</additional-query>
</options>;
(: additional-query: xmlns:foo first, xmlns:bar second; cts:element: bar:child. Fails. :)
declare variable $search-options-2 :=
<options xmlns="http://marklogic.com/appservices/search">
<additional-query xmlns:foo="myuri:baz" xmlns:bar="myuri:baz">
<cts:element-range-query operator="=">
<cts:element>bar:child</cts:element>
<cts:value xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">TEST</cts:value>
</cts:element-range-query>
</additional-query>
</options>;
(: additional-query: xmlns:bar first, xmlns:foo second; cts:element: bar:child. Succeeds. :)
declare variable $search-options-3 :=
<options xmlns="http://marklogic.com/appservices/search">
<additional-query xmlns:bar="myuri:baz" xmlns:foo="myuri:baz">
<cts:element-range-query operator="=">
<cts:element>bar:child</cts:element>
<cts:value xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">TEST</cts:value>
</cts:element-range-query>
</additional-query>
</options>;
(: additional-query: xmlns:bar first, xmlns:foo second; cts:element: foo:child. Fails. :)
declare variable $search-options-4 :=
<options xmlns="http://marklogic.com/appservices/search">
<additional-query xmlns:bar="myuri:baz" xmlns:foo="myuri:baz">
<cts:element-range-query operator="=">
<cts:element>foo:child</cts:element>
<cts:value xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">TEST</cts:value>
</cts:element-range-query>
</additional-query>
</options>;
for $search-options in ($search-options-1, $search-options-2, $search-options-3, $search-options-4)
return
try {
let $test := search:search("", $search-options, 1) instance of element(search:response)
return
if ($test) then "PASS"
else "FAIL" (: won't reach :)
} catch($e) {
$e/error:format-string/fn:string(.)
}
Test output (note error substring "No string element range index for child"---no namespace)
PASS
XDMP-ELEMRIDXNOTFOUND: cts:search(fn:collection(), cts:and-query(cts:element-range-query(xs:QName("bar:child"), "=", "TEST", ("collation=http://marklogic.com/collation/"), 1), ()), ("score-logtfidf", cts:score-order("descending")), xs:double("1"), ()) -- No string element range index for child http://marklogic.com/collation/
PASS
XDMP-ELEMRIDXNOTFOUND: cts:search(fn:collection(), cts:and-query(cts:element-range-query(xs:QName("foo:child"), "=", "TEST", ("collation=http://marklogic.com/collation/"), 1), ()), ("score-logtfidf", cts:score-order("descending")), xs:double("1"), ()) -- No string element range index for child http://marklogic.com/collation/
Update: element-level namespace declaration (w/ direct constructor or serialized via fn:QName()) also breaks under ML8 (not ML7) when an ancestor declares the same namespace URI under different prefixes more than once
Prefix lost at self::* when any ancestor declares the same URI under different prefixes more than once:
xquery version "1.0-ml";
import module namespace search = "http://marklogic.com/appservices/search" at "/MarkLogic/appservices/search/search.xqy";
(: Serialize namespace w/ fn:QName(), no namespace inheritance: PASS :)
declare variable $search-options-1 :=
<options xmlns="http://marklogic.com/appservices/search">
<additional-query>
<cts:element-range-query operator="=">
<cts:element>{fn:QName("myuri:baz", "child")}</cts:element>
<cts:value xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">TEST</cts:value>
</cts:element-range-query>
</additional-query>
</options>;
(: Serialize namespace w/ fn:QName(), namespace inheritance, declared once: PASS :)
declare variable $search-options-2 :=
<options xmlns="http://marklogic.com/appservices/search">
<additional-query xmlns:foo="myuri:baz">
<cts:element-range-query operator="=">
<cts:element>{fn:QName("myuri:baz", "child")}</cts:element>
<cts:value xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">TEST</cts:value>
</cts:element-range-query>
</additional-query>
</options>;
(: Serialize namespace w/ fn:QName(), namespace inheritance, declared twice: FAIL :)
declare variable $search-options-3 :=
<options xmlns="http://marklogic.com/appservices/search">
<additional-query xmlns:foo="myuri:baz" xmlns:bar="myuri:baz">
<cts:element-range-query operator="=">
<cts:element>{fn:QName("myuri:baz", "child")}</cts:element>
<cts:value xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">TEST</cts:value>
</cts:element-range-query>
</additional-query>
</options>;
for $search-options in ($search-options-1, $search-options-2, $search-options-3)
return
try {
let $test := search:search("", $search-options, 1) instance of element(search:response)
return
if ($test) then "PASS"
else "FAIL" (: won't reach :)
} catch($e) {
$e/error:format-string/fn:string(.)
}
Output ML7.0-4.3:
PASS
PASS
PASS
Output ML8.0-3.2:
PASS
PASS
XDMP-ELEMRIDXNOTFOUND: cts:search(fn:collection(), cts:and-query(cts:element-range-query(xs:QName("bar:child"), "=", "TEST", ("collation=http://marklogic.com/collation/"), 1), ()), ("score-logtfidf", cts:score-order("descending")), xs:double("1"), ()) -- No string element range index for child http://marklogic.com/collation/