Markus Flatscher
Markus Flatscher

Reputation: 127

Namespace declarations inside search:search options node get dropped when 2+ prefixes are declared for the same URI

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/

Upvotes: 2

Views: 259

Answers (3)

grtjn
grtjn

Reputation: 20414

I can confirm it runs in 7.0-5.1, but not in 8.0-4. I have reported this as a bug internally.

Looks like the way additional-queries are handled has changed. But if you move the namespace declarations one of two elements down, you won't get any errors. Maybe that is a useful workaround for you?

HTH!

Upvotes: 1

joemfb
joemfb

Reputation: 3056

If you're using the Search API directly within an XQuery context, you could use fn:QName() to get a consistent serialization of the element QName:

<cts:element>{ fn:QName("myuri:baz", "child") }</cts:element>

evaluates to:

<cts:element xmlns:_1="myuri:baz">_1:child</cts:element>

Alternately, xs:QName() will use available in-scope prefixes:

declare namespace foo ="myuri:baz";
<cts:element>{ xs:QName("foo:child") }</cts:element>

evaluates to:

<cts:element xmlns:foo="myuri:baz">foo:child</cts:element>

Of course, this approach won't help if you're using the REST API with stored search options.

Upvotes: 3

ehennum
ehennum

Reputation: 7335

I believe the Search API has always had this limitation. I agree that the surprise is unfortunate -- the standards do not endorse the limitation -- but would it be difficult for you to work around the limitation by using one and only one prefix for each namespace uri? If nothing else, that makes the declaration simpler.

Upvotes: 0

Related Questions