adapter page indicated the urn of the function

module namespace page = 'http://basex.org/modules/web-page';

import module namespace csr_proc = "https://github.com/openhie/openinfoman/csr_proc";
import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm";
import module namespace csd_webconf =  "https://github.com/openhie/openinfoman/csd_webconf";
declare namespace csd = "urn:ihe:iti:csd:2013";

declare
  %rest:path("/CSD/adapter")
  %rest:GET
  %output:method("xhtml")
  function page:show_types()
{
  let $funcs := (csr_proc:stored_functions($csd_webconf:db), csr_proc:stored_updating_functions($csd_webconf:db))
  let $searches :=

      {
	for $adapter_func in $funcs[./csd:extension[@urn='urn:openhie.org:openinfoman:adapter']]
        let $desc := $adapter_func/csd:description
        let $types := $adapter_func/csd:extension[@urn='urn:openhie.org:openinfoman:adapter']/@type
	let $urn := string($adapter_func/@urn)
	return
	  for $type in $types
	  let $s_type := string($type)
	  return

Stored Function {$urn}

	      Type ({$s_type})

		Act on a Document

{string($desc)}

      }

  return page:wrapper($searches)
};

declare
  %rest:path("/CSD/adapter/{$type}/{$search_name}")
  %output:method("xhtml")
  function page:show_endpoints($search_name,$type)
{
  let $function := csr_proc:get_any_function_definition($csd_webconf:db,$search_name)
  let $extensions :=  $function/csd:extension[@urn='urn:openhie.org:openinfoman:adapter' and  @type=$type]

  let $contents :=
    if (count($extensions) = 0)
      (:not a read fhir entity query. should 404 or whatever is required by FHIR :)
    then ("Not a " , $type , " compatible stored functions" )
    else

{$type} Documents

{string($function/@urn)}

          {
  	    for $doc_name in csd_dm:registered_documents($csd_webconf:db)
	    return

	      {string($doc_name)}

	  }

  return csd_webconf:wrapper($contents)

};

declare
  %rest:path("/CSD/adapter/{$type}")
  %rest:GET
  %output:method("xhtml")
  function page:show_type($type)
{
  let $funcs := (csr_proc:stored_functions($csd_webconf:db), csr_proc:stored_updating_functions($csd_webconf:db))[./csd:extension[@type = $type] ]
  let $adaptations :=

{$type}

        {
	    for $adapter_func in $funcs
            let $desc := $adapter_func/csd:description
	    let $urn := string($adapter_func/@urn)
	    return

	      Type ({$type})

	      Adapter Document Index (
	      {$urn}
			       )

{$desc}

	}

  let $contents :=
    (
      Adapters
      ,$adaptations
      )
  return page:wrapper($contents)
};

declare function page:wrapper($response) {
  let $headers :=
  (<link rel="stylesheet" type="text/css" media="screen"   href="{$csd_webconf:baseurl}static/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css"/>
  ,

module namespace page = 'http://basex.org/modules/web-page';

import module namespace csr_proc = "https://github.com/openhie/openinfoman/csr_proc";
import module namespace csd_dm = "https://github.com/openhie/openinfoman/csd_dm";
import module namespace csd_webconf =  "https://github.com/openhie/openinfoman/csd_webconf";
declare namespace csd = "urn:ihe:iti:csd:2013";

declare
  %rest:path("/CSD/adapter")
  %rest:GET
  %output:method("xhtml")
  function page:show_types()
{
  let $funcs := (csr_proc:stored_functions($csd_webconf:db), csr_proc:stored_updating_functions($csd_webconf:db))
  let $searches :=

      {
	for $adapter_func in $funcs[./csd:extension[@urn='urn:openhie.org:openinfoman:adapter']]
        let $desc := $adapter_func/csd:description
        let $types := $adapter_func/csd:extension[@urn='urn:openhie.org:openinfoman:adapter']/@type
	let $urn := string($adapter_func/@urn)
	return
	  for $type in $types
	  let $s_type := string($type)
	  return

Stored Function {$urn}

	      Type ({$s_type})

		Act on a Document

{string($desc)}

      }

  return page:wrapper($searches)
};

declare
  %rest:path("/CSD/adapter/{$type}/{$search_name}")
  %output:method("xhtml")
  function page:show_endpoints($search_name,$type)
{
  let $function := csr_proc:get_any_function_definition($csd_webconf:db,$search_name)
  let $extensions :=  $function/csd:extension[@urn='urn:openhie.org:openinfoman:adapter' and  @type=$type]

  let $contents :=
    if (count($extensions) = 0)
      (:not a read fhir entity query. should 404 or whatever is required by FHIR :)
    then ("Not a " , $type , " compatible stored functions" )
    else

{$type} Documents

          {
  	    for $doc_name in csd_dm:registered_documents($csd_webconf:db)
	    return

	      {string($doc_name)}

	  }

  return csd_webconf:wrapper($contents)

};

declare
  %rest:path("/CSD/adapter/{$type}")
  %rest:GET
  %output:method("xhtml")
  function page:show_type($type)
{
  let $funcs := (csr_proc:stored_functions($csd_webconf:db), csr_proc:stored_updating_functions($csd_webconf:db))[./csd:extension[@type = $type] ]
  let $adaptations :=

{$type}

        {
	    for $adapter_func in $funcs
            let $desc := $adapter_func/csd:description
	    let $urn := string($adapter_func/@urn)
	    return

	      Type ({$type})

	      Adapter Document Index (
	      {$urn}
			       )

{$desc}

	}

  let $contents :=
    (
      Adapters
      ,$adaptations
      )
  return page:wrapper($contents)
};

declare function page:wrapper($response) {
  let $headers :=
  (<link rel="stylesheet" type="text/css" media="screen"   href="{$csd_webconf:baseurl}static/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css"/>
  ,


Small tweaks to signatures

Small tweaks to signatures

xquery version "3.1";

(:~ Origami μ-templates :)

(: TODO: add XML serialization options :)
(: TODO: add option to move all ns declarations at document element (sane namespaces) !? :)
(: TODO: better json serialization :)
(: TODO: remove args from :xml :)
(: TODO: can we scan $items for function items and if so return a function automatically? :)

(: DONE: add ways to configure how names are mapped to qnames (e.g. define a mapping a la JSON-LD) :)
(: DONE: add default element namespace :)

module namespace μ = 'http://xokomola.com/xquery/origami/μ';

declare function μ:xml($items as item()*)
{
    μ:xml($items, [], μ:qname-resolver())
};

declare function μ:xml($items as item()*, $resolver-or-args as function(*))
as node()*
{
    if ($resolver-or-args instance of array(*)) then
        μ:xml($items, $resolver-or-args, μ:qname-resolver())
    else
        μ:xml($items, [], $resolver-or-args)
};

declare function μ:xml($items as item()*, $args as array(*), $name-resolver as function(*))
as node()*
{
    μ:to-xml($items, $args, $name-resolver)
};

declare function μ:json($items as item()*)
{
    μ:json($items, [], function($name) { $name })
};

declare function μ:json($items as item()*, $resolver-or-args as function(*))
as node()*
{
    if ($resolver-or-args instance of array(*)) then
        μ:json($items, $resolver-or-args, μ:qname-resolver())
    else
        μ:json($items, [], $resolver-or-args)
};

declare function μ:json($items as item()*, $args as array(*), $name-resolver as function(*))
{
    serialize(μ:to-json(if (count($items) gt 1) then array { $items } else $items, $args, $name-resolver), map { 'method': 'json' })
};

declare function μ:mu($xml)
{
    μ:from-xml($xml)
};

declare %private function μ:to-json($items as item()*, $args as array(*), $name-resolver as function(*))
{
    for $item in $items
    return
        typeswitch ($item)
        case array(*)
        return
            array:fold-left($item, [],
                function($a,$b) {
                    if (count($b) gt 1) then
                        (: splice the sequence into the array to avoid JSON
                         : serialization error :)
                        array:join((
                            $a,
                            fold-left($b, [],
                                function($c,$d) {
                                    array:append($c, μ:to-json($d, $args, $name-resolver))
                                })
                        ))
                    else
                        array:append($a, μ:to-json($b, $args, $name-resolver))
                }
            )
        case map(*)
        return
            map:merge(
                map:for-each($item,
                    function($a,$b) {
                        map:entry($a, μ:to-json($b, $args, $name-resolver)) }))
        case function(*) return μ:to-json(apply($item, $args), $args, $name-resolver)
        case node() return μ:from-xml($item)
        default return $item
};

declare %private function μ:from-xml($xml)
as item()*
{
    for $node in $xml
    return
        typeswitch($node)
        case element()
        return
            array {
                name($node),
                if ($node/@*) then
                    map:merge((
                        for $a in $node/@*
                        return map:entry(name($a), data($a))))
                else
                    (),
                μ:from-xml($node/node())
            }
        case comment() | processing-instruction() return ()
        default return string($node)
};

declare %private function μ:to-xml($items as item()*, $args as array(*), $name-resolver as function(*))
as node()*
{
    for $item in $items
    return
        typeswitch ($item)
        case array(*) return μ:to-element($item, $args, $name-resolver)
        case map(*) return  μ:to-attributes($item, $args, $name-resolver)
        case function(*) return μ:to-xml(apply($item, $args), $args, $name-resolver)
        case empty-sequence() return ()
        case node() return $item
        default return text { $item }
};

declare %private function μ:to-element($item as array(*), $args as array(*), $name-resolver as function(*))
as element()?
{
    if (array:size($item) gt 0) then
        element { $name-resolver(array:head($item)) } {
            array:fold-left(
                array:tail($item),
                (),
                function($n, $i) {
                    ($n, μ:to-xml($i, $args, $name-resolver))
                }
            )
        }
    else
        ()
};

declare %private function μ:to-attributes($item as map(*), $args as array(*), $name-resolver as function(*))
as attribute()*
{
    map:for-each($item,
        function($k,$v) {
            if (not(starts-with($k,'μ:'))) then
                attribute { $name-resolver($k) } {
                    data(
                        typeswitch ($v)
                        case function(*)
                        return apply($v, $args)
                        default
                        return $v
                    ) }
            else
                ()
        })
};

declare function μ:qname-resolver()
as function(xs:string) as xs:QName
{
    μ:qname(?, μ:ns(), ())
};

declare function μ:qname-resolver($ns-map as map(*))
as function(xs:string) as xs:QName
{
    μ:qname(?, $ns-map, ())
};

declare function μ:qname-resolver($ns-map as map(*), $default-ns as xs:string)
as function(xs:string) as xs:QName
{
    μ:qname(?, $ns-map, $default-ns)
};

(:~
 : As XQuery doesn't allow access to namespace nodes (as XSLT does)
 : construct them indirectly via QName#2.
 :)
declare function μ:qname($name as xs:string, $ns-map as map(*))
as xs:QName
{
    μ:qname($name, $ns-map, ())
};

declare function μ:qname($name as xs:string, $ns-map as map(*), $default-ns as xs:string?)
as xs:QName
{
    if (contains($name, ':')) then
        let $prefix := substring-before($name,':')
        let $local := substring-after($name, ':')
        let $ns := $ns-map($prefix)
        return
            if ($ns = $default-ns) then
                QName($ns, $local)
            else
                QName($ns, concat($prefix,':',$local))
    else
        if ($default-ns) then
            QName($default-ns, $name)
        else
            QName((), $name)
};

declare function μ:ns()
as map(*)
{
    μ:ns(map {})
};

declare function μ:ns($ns-map as map(*))
as map(*)
{
    map:merge((
        $ns-map,
        for $ns in doc(concat(file:base-dir(),'/nsmap.xml'))/nsmap/*
        return
            map:entry(string($ns/@prefix), string($ns/@uri))
    ))
};

declare function μ:cons($a,$b)
as item()*
{
    ($a,$b)
};

declare function μ:head($mu as array(*)?)
as item()*
{
    if (empty($mu)) then
        ()
    else
        array:head($mu)
};

declare function μ:tail($mu as array(*)?)
as item()*
{
    if (not(empty($mu))) then
        tail(μ:seq($mu))
    else
        ()
};

(:~
 : Remove level of array and change it into a normal sequence.
 :)
declare function μ:seq($mu as array(*))
as item()*
{
    typeswitch ($mu)
        case array(*)
            return array:fold-left($mu, (), μ:cons#2)
        default
            return $mu
};

declare function μ:content($mu)
as item()*
{
    typeswitch($mu)
    case array(*)
    return
        let $c := array:tail($mu)
        return
            if (array:head($c) instance of map(*)) then
                array:tail($c)
            else
                $c
    default
    return $mu/node()
};

xquery version "3.1";

(:~ Origami μ-templates :)

(: TODO: add XML serialization options :)
(: TODO: add option to move all ns declarations at document element (sane namespaces) !? :)
(: TODO: better json serialization :)
(: TODO: remove args from :xml :)
(: TODO: can we scan $items for function items and if so return a function automatically? :)

(: DONE: add ways to configure how names are mapped to qnames (e.g. define a mapping a la JSON-LD) :)
(: DONE: add default element namespace :)

module namespace μ = 'http://xokomola.com/xquery/origami/μ';

declare function μ:xml($items as item()*)
{
    μ:xml($items, [], μ:qname-resolver())
};

declare function μ:xml($items as item()*, $resolver-or-args as function(*))
as node()*
{
    if ($resolver-or-args instance of array(*)) then
        μ:xml($items, $resolver-or-args, μ:qname-resolver())
    else
        μ:xml($items, [], $resolver-or-args)
};

declare function μ:xml($items as item()*, $args as array(*), $name-resolver as function(*))
as node()*
{
    μ:to-xml($items, $args, $name-resolver)
};

declare function μ:json($items as item()*)
{
    μ:json($items, [], function($name) { $name })
};

declare function μ:json($items as item()*, $resolver-or-args as function(*))
as node()*
{
    if ($resolver-or-args instance of array(*)) then
        μ:json($items, $resolver-or-args, μ:qname-resolver())
    else
        μ:json($items, [], $resolver-or-args)
};

declare function μ:json($items as item()*, $args as array(*), $name-resolver as function(*))
{
    serialize(μ:to-json(if (count($items) gt 1) then array { $items } else $items, $args, $name-resolver), map { 'method': 'json' })
};

declare function μ:mu($xml)
{
    μ:from-xml($xml)
};

declare %private function μ:to-json($items as item()*, $args as array(*), $name-resolver as function(*))
{
    for $item in $items
    return
        typeswitch ($item)
        case array(*)
        return
            array:fold-left($item, [],
                function($a,$b) {
                    if (count($b) gt 1) then
                        (: splice the sequence into the array to avoid JSON
                         : serialization error :)
                        array:join((
                            $a,
                            fold-left($b, [],
                                function($c,$d) {
                                    array:append($c, μ:to-json($d, $args, $name-resolver))
                                })
                        ))
                    else
                        array:append($a, μ:to-json($b, $args, $name-resolver))
                }
            )
        case map(*)
        return
            map:merge(
                map:for-each($item,
                    function($a,$b) {
                        map:entry($a, μ:to-json($b, $args, $name-resolver)) }))
        case function(*) return μ:to-json(apply($item, $args), $args, $name-resolver)
        case node() return μ:from-xml($item)
        default return $item
};

declare %private function μ:from-xml($xml)
as item()*
{
    for $node in $xml
    return
        typeswitch($node)
        case element()
        return
            array {
                name($node),
                if ($node/@*) then
                    map:merge((
                        for $a in $node/@*
                        return map:entry(name($a), data($a))))
                else
                    (),
                μ:from-xml($node/node())
            }
        case comment() | processing-instruction() return ()
        default return string($node)
};

declare %private function μ:to-xml($items as item()*, $args as array(*), $name-resolver as function(*))
as node()*
{
    for $item in $items
    return
        typeswitch ($item)
        case array(*) return μ:to-element($item, $args, $name-resolver)
        case map(*) return  μ:to-attributes($item, $args, $name-resolver)
        case function(*) return μ:to-xml(apply($item, $args), $args, $name-resolver)
        case empty-sequence() return ()
        case node() return $item
        default return text { $item }
};

declare %private function μ:to-element($item as array(*), $args as array(*), $name-resolver as function(*))
as element()?
{
    if (array:size($item) gt 0) then
        element { $name-resolver(array:head($item)) } {
            array:fold-left(
                array:tail($item),
                (),
                function($n, $i) {
                    ($n, μ:to-xml($i, $args, $name-resolver))
                }
            )
        }
    else
        ()
};

declare %private function μ:to-attributes($item as map(*), $args as array(*), $name-resolver as function(*))
as attribute()*
{
    map:for-each($item,
        function($k,$v) {
            if (not(starts-with($k,'μ:'))) then
                attribute { $name-resolver($k) } {
                    data(
                        typeswitch ($v)
                        case function(*)
                        return apply($v, $args)
                        default
                        return $v
                    ) }
            else
                ()
        })
};

declare function μ:qname-resolver()
as function(xs:string) as xs:QName
{
    μ:qname(?, μ:ns(), ())
};

declare function μ:qname-resolver($ns-map as map(*))
as function(xs:string) as xs:QName
{
    μ:qname(?, $ns-map, ())
};

declare function μ:qname-resolver($ns-map as map(*), $default-ns as xs:string)
as function(xs:string) as xs:QName
{
    μ:qname(?, $ns-map, $default-ns)
};

(:~
 : As XQuery doesn't allow access to namespace nodes (as XSLT does)
 : construct them indirectly via QName#2.
 :)
declare function μ:qname($name as xs:string, $ns-map as map(*))
as xs:QName
{
    μ:qname($name, $ns-map, ())
};

declare function μ:qname($name as xs:string, $ns-map as map(*), $default-ns as xs:string?)
as xs:QName
{
    if (contains($name, ':')) then
        let $prefix := substring-before($name,':')
        let $local := substring-after($name, ':')
        let $ns := $ns-map($prefix)
        return
            if ($ns = $default-ns) then
                QName($ns, $local)
            else
                QName($ns, concat($prefix,':',$local))
    else
        if ($default-ns) then
            QName($default-ns, $name)
        else
            QName((), $name)
};

declare function μ:ns()
{
    μ:ns(map {})
};

declare function μ:ns($ns-map as map(*))
{
    map:merge((
        $ns-map,
        for $ns in doc(concat(file:base-dir(),'/nsmap.xml'))/nsmap/*
        return
            map:entry(string($ns/@prefix), string($ns/@uri))
    ))
};

declare function μ:cons($a,$b) {
    ($a,$b)
};

declare function μ:head($mu as array(*)?)
{
    if (empty($mu)) then
        ()
    else
        array:head($mu)
};

declare function μ:tail($mu as array(*)?)
as array(*)*
{
    if (not(empty($mu))) then
        tail(μ:seq($mu))
    else
        ()
};

(:~
 : Remove level of array and change it into a normal sequence.
 :)
declare function μ:seq($mu as array(*))
as item()*
{
    typeswitch ($mu)
        case array(*)
            return array:fold-left($mu, (), μ:cons#2)
        default
            return $mu
};

declare function μ:content($mu)
as item()*
{
    typeswitch($mu)
    case array(*)
    return
        let $c := array:tail($mu)
        return
            if (array:head($c) instance of map(*)) then
                array:tail($c)
            else
                $c
    default
    return $mu/node()
};


[testsuite] Add testcase for query rewrite failure

xquery version "3.0";

(:~
 : Test query rewriting for range index.
 :
 : Expressions use the @test:stats annotation to retrieve execution statistics
 : for each test function. All comparisons should be fully optimized.
 :)
module namespace ot="http://exist-db.org/xquery/range/optimizer/test";

import module namespace range="http://exist-db.org/xquery/range" at "java:org.exist.xquery.modules.range.RangeIndexModule";
import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql";

declare namespace tei="http://www.tei-c.org/ns/1.0";
declare namespace stats="http://exist-db.org/xquery/profiling";

declare variable $ot:COLLECTION_CONFIG :=
    <collection xmlns="http://exist-db.org/collection-config/1.0">
        <index xmlns:xs="http://www.w3.org/2001/XMLSchema"
            xmlns:tei="http://www.tei-c.org/ns/1.0">
            <lucene>
                <text field="lucene-name" qname="name"/>
            </lucene>
            <range>
                <create match="//tei:placeName">
                    <field name="name" type="xs:string" nested="no"/>
                    <field name="type" match="@type" type="xs:string"/>
                    <field name="subtype" match="@subtype" type="xs:string"/>
                </create>
                <create qname="tei:orth" type="xs:string" collation="?lang=sr&strength=primary" case="no"/>
                <create match="//address">
                    <filter class="org.apache.lucene.analysis.core.LowerCaseFilter"/>
                    <field name="address-city" match="city" type="xs:string"/>
                    <field name="address-street" match="street" type="xs:string"/>
                    <field name="address-email" match="contact/email" type="xs:string"/>
                    <field name="address-name" match="name3" type="xs:string"/>
                </create>
                <create match="/test/address/name"/>
                <create match="/test/address/name2">
                    <filter class="org.apache.lucene.analysis.core.LowerCaseFilter"/>
                    <filter class="org.apache.lucene.analysis.miscellaneous.ASCIIFoldingFilter"/>
                </create>
                <create match="/test/address/city/@code" type="xs:integer"/>
                <create qname="@id" type="xs:string"/>
            </range>
        </index>
    </collection>;

declare variable $ot:DATA_NESTED :=
    <place xmlns="http://www.tei-c.org/ns/1.0">
        <placeName xml:id="ODB_S00004004_NAM001" xml:lang="de-DE" type="ref" subtype="inofficial">Hofthiergarten<note type="source">
                <date ana="#notBefore">1750</date>
                <date ana="#notAfter">1820</date>
            </note>
        </placeName>
        <placeName xml:id="ODB_S00004004_NAM002" xml:lang="de-DE" type="main" subtype="official">Hofthiergarten<note type="source">
                <date ana="#when">2011-08-24</date>
            </note>
        </placeName>
        <placeName xml:id="ODB_A00000393_NAM001" xml:lang="de-DE" type="main" subtype="official">Dorfprozelten<note type="source">
                <date ana="#when">2001-04-07</date>
                <bibl>
                    <ptr target="#HAB/Laube"/>
                </bibl>
            </note>
        </placeName>
    </place>;

declare variable $ot:DATA :=
    <test>
        <address id="muh">
            <name>Berta Muh</name>
            <name2>Berta Muh</name2>
            <name3>Berta Muh</name3>
            <street>Wiesenweg 14</street>
            <city code="65463">Almweide</city>
            <contact>
                <email>berta@milchvieh.org</email>
            </contact>
        </address>
        <address id="rüssel">
            <name>Rudi Rüssel</name>
            <name2>Rudi Rüssel</name2>
            <name3>Rudi Rüssel</name3>
            <street>Elefantenweg 67</street>
            <city code="65428">Rüsselsheim</city>
            <contact>
                <email>rudi@trompeter.de</email>
            </contact>
        </address>
        <address id="amsel">
            <name>Albert Amsel</name>
            <name2>Albert Amsel</name2>
            <name3>Albert Amsel</name3>
            <street>Birkenstraße 77</street>
            <city code="76878">Waldstadt</city>
            <contact>
                <email>albert@zwitschern.de</email>
            </contact>
        </address>
        <address id="reh">
            <name>Pü Reh</name>
            <name2>Pü Reh</name2>
            <name3>Pü Reh</name3>
            <street>Am Waldrand 4</street>
            <city code="89283">Wiesental</city>
            <contact>
                <email>pü@wildsau.net</email>
            </contact>
        </address>
    </test>;

declare variable $ot:DATA_SR_WITH_DIACRITICS :=
    <TEI xmlns="http://www.tei-c.org/ns/1.0">
        <teiHeader>
            <fileDesc>
                <titleStmt><title>sr with diacritics</title></titleStmt>
                <publicationStmt><distributor>sr with diacritics</distributor></publicationStmt>
                <sourceDesc><listOrg><org><name>test</name></org></listOrg></sourceDesc>
            </fileDesc>
        </teiHeader>
        <text>
            <body>
                <div>
                    <entryFree>
                        <form type="lemma">
                            <orth>Мла̀тишума</orth>
                        </form>
                        <form type="lemma">
                            <orth>Млатишума</orth>
                        </form>
                    </entryFree>
                </div>
            </body>
        </text>
    </TEI>;

declare variable $ot:COLLECTION_NAME := "optimizertest";
declare variable $ot:COLLECTION := "/db/" || $ot:COLLECTION_NAME;

declare
    %test:setUp
function ot:setup() {
    xmldb:create-collection("/db/system/config/db", $ot:COLLECTION_NAME),
    xmldb:store("/db/system/config/db/" || $ot:COLLECTION_NAME, "collection.xconf", $ot:COLLECTION_CONFIG),
    xmldb:create-collection("/db", $ot:COLLECTION_NAME),
    xmldb:store($ot:COLLECTION, "test.xml", $ot:DATA),
    xmldb:store($ot:COLLECTION, "nested.xml", $ot:DATA_NESTED),
    xmldb:store($ot:COLLECTION, "diacritics.xml", $ot:DATA_SR_WITH_DIACRITICS)
};

declare
    %test:tearDown
function ot:cleanup() {
    xmldb:remove($ot:COLLECTION),
    xmldb:remove("/db/system/config/db/" || $ot:COLLECTION_NAME)
};

declare
    %test:stats
    %test:args("Rudi Rüssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-eq-string($name as xs:string) {
    collection($ot:COLLECTION)//address[name = $name]
};

declare
    %test:stats
    %test:args("Rudi Rüssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-ne-string($name as xs:string) {
    collection($ot:COLLECTION)//address[name != $name]
};

declare
    %test:stats
    %test:args("Rudi Rüssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-ne-string2($name as xs:string) {
    collection($ot:COLLECTION)//address[name ne $name]
};

declare
    %test:stats
    %test:args("Rudi Rüssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-ne-string-self($name as xs:string) {
    collection($ot:COLLECTION)//address/name[. ne $name]
};

declare
    %test:stats
    %test:args("Rudi")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-starts-with-string($name as xs:string) {
    collection($ot:COLLECTION)//address[starts-with(name, $name)]
};

declare
    %test:stats
    %test:args("Rüssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-ends-with-string($name as xs:string) {
    collection($ot:COLLECTION)//address[ends-with(name, $name)]
};

declare
    %test:stats
    %test:args("udi ")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-contains-string($name as xs:string) {
    collection($ot:COLLECTION)//address[contains(name, $name)]
};

declare
    %test:stats
    %test:args("[rR]udi .*")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-matches-string($name as xs:string) {
    collection($ot:COLLECTION)//address[range:matches(name, $name)]
};

declare
    %test:stats
    %test:args("[rR]udi .*")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 1]")
function ot:optimize-matches-string-filtered($name as xs:string) {
    let $address := collection($ot:COLLECTION)//address
    return
        $address[range:matches(name, $name)]
};

declare
    %test:stats
    %test:args("Rudi Rüssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-eq-string-self($name as xs:string) {
    collection($ot:COLLECTION)//address/name[. = $name]
};

declare
    %test:args("Rudi Rüssel")
function ot:eq-string-self-filtered($name as xs:string) {
    let $n := collection($ot:COLLECTION)//address/name
    return
        $n[. = $name]
};

declare
    %test:stats
    %test:args(65428)
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-eq-int-attribute($code as xs:integer) {
    collection($ot:COLLECTION)//address/city[@code = $code]
};

declare
    %test:stats
    %test:args("Rudi Rüssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-lt-string($name as xs:string) {
    collection($ot:COLLECTION)//address[name < $name]
};

declare
    %test:args("Rudi Rüssel")
    %test:assertEquals(3)
function ot:ne-string($name as xs:string) {
    count(collection($ot:COLLECTION)//address[name != $name])
};

declare
    %test:args("Rudi Rüssel")
    %test:assertEquals(3)
function ot:ne-string-self($name as xs:string) {
    count(collection($ot:COLLECTION)//address/name[. != $name])
};

declare
    %test:args("Berta Muh")
    %test:assertEquals(1)
function ot:lt-string($name as xs:string) {
    count(collection($ot:COLLECTION)//address[name < $name])
};

declare
    %test:stats
    %test:args("Rudi Rüssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-le-string($name as xs:string) {
    collection($ot:COLLECTION)//address[name <= $name]
};

declare
    %test:args("Berta Muh")
    %test:assertEquals(2)
function ot:le-string($name as xs:string) {
    count(collection($ot:COLLECTION)//address[name <= $name])
};

declare
    %test:stats
    %test:args("Rudi Rüssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-gt-string($name as xs:string) {
    collection($ot:COLLECTION)//address[name > $name]
};

declare
    %test:args("Pü Reh")
    %test:assertEquals(1)
function ot:gt-string($name as xs:string) {
    count(collection($ot:COLLECTION)//address[name > $name])
};

declare
    %test:stats
    %test:args("Rudi Rüssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-ge-string($name as xs:string) {
    collection($ot:COLLECTION)//address[name > $name]
};

declare
    %test:args("Pü Reh")
    %test:assertEquals(2)
function ot:ge-string($name as xs:string) {
    count(collection($ot:COLLECTION)//address[name >= $name])
};

declare
    %test:stats
    %test:args("Rüsselsheim")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-eq-field($city as xs:string) {
    collection($ot:COLLECTION)//address[city = $city]
};

declare
    %test:args("Rüsselsheim")
    %test:assertEquals(1)
function ot:eq-field($city as xs:string) {
    count(collection($ot:COLLECTION)//address[city = $city])
};

declare
    %test:stats
    %test:args("Rüsselsheim")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-ne-field($city as xs:string) {
    collection($ot:COLLECTION)//address[city != $city]
};

declare
    %test:args("Rüsselsheim")
    %test:assertEquals(3)
function ot:ne-field($city as xs:string) {
    count(collection($ot:COLLECTION)//address[city != $city])
};

declare
    %test:stats
    %test:args("Rüsselsheim")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-gt-field($city as xs:string) {
    collection($ot:COLLECTION)//address[city > $city]
};

declare
    %test:args("Rüsselsheim")
    %test:assertEquals(2)
function ot:gt-field($city as xs:string) {
    count(collection($ot:COLLECTION)//address[city > $city])
};

declare
    %test:stats
    %test:args("Rüsselsheim")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-ge-field($city as xs:string) {
    collection($ot:COLLECTION)//address[city >= $city]
};

declare
    %test:args("Rüsselsheim")
    %test:assertEquals(3)
function ot:ge-field($city as xs:string) {
    count(collection($ot:COLLECTION)//address[city >= $city])
};

declare
    %test:stats
    %test:args("Rüsselsheim")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-lt-field($city as xs:string) {
    collection($ot:COLLECTION)//address[city < $city]
};

declare
    %test:args("Rüsselsheim")
    %test:assertEquals(1)
function ot:lt-field($city as xs:string) {
    count(collection($ot:COLLECTION)//address[city < $city])
};

declare
    %test:stats
    %test:args("Rüsselsheim")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-le-field($city as xs:string) {
    collection($ot:COLLECTION)//address[city <= $city]
};

declare
    %test:args("Rüsselsheim")
    %test:assertEquals(2)
function ot:le-field($city as xs:string) {
    count(collection($ot:COLLECTION)//address[city <= $city])
};

declare
    %test:stats
    %test:args("Rüssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-starts-with-field($city as xs:string) {
    collection($ot:COLLECTION)//address[starts-with(city, $city)]
};

declare
    %test:stats
    %test:args("heim")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-ends-with-field($city as xs:string) {
    collection($ot:COLLECTION)//address[ends-with(city, $city)]
};

declare
    %test:stats
    %test:args("üssel")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-contains-field($city as xs:string) {
    collection($ot:COLLECTION)//address[contains(city, $city)]
};

declare
    %test:stats
    %test:args("[rR]üssel.*")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-matches-field($city as xs:string) {
    collection($ot:COLLECTION)//address[range:matches(city, $city)]
};

declare
    %test:args("[rR]üssel.*")
function ot:matches-field-filtered($city as xs:string) {
    let $address := collection($ot:COLLECTION)//address
    return
        $address[range:matches(city, $city)]
};

declare
    %test:stats
    %test:args("Rüsselsheim", "Elefantenweg 67")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-eq-field-multi($city as xs:string, $street as xs:string) {
    collection($ot:COLLECTION)//address[city = $city][street = $street]
};

declare
    %test:args("Rüsselsheim", "Elefantenweg 67")
    %test:assertEquals(1)
function ot:eq-field-multi($city as xs:string, $street as xs:string) {
    count(collection($ot:COLLECTION)//address[city = $city][street = $street])
};

declare
    %test:args("Rüsselsheim", "Elefantenweg 67")
    %test:assertEquals(1)
function ot:eq-field-multi-filtered($city as xs:string, $street as xs:string) {
    let $address := collection($ot:COLLECTION)//address
    return
        count($address[city = $city][street = $street])
};

declare
    %test:stats
    %test:args("Rüsselsheim", "Elefantenweg 67")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-mixed-op-field($city as xs:string, $street as xs:string) {
    collection($ot:COLLECTION)//address[city > $city][street = $street]
};

declare
    %test:args("rüsselsheim", "Elefantenweg 67")
    %test:assertEquals(0)
function ot:mixed-op-field1($city as xs:string, $street as xs:string) {
    count(collection($ot:COLLECTION)//address[city > $city][street = $street])
};

declare
    %test:args("rüsselsheim", "Elefantenweg 67")
    %test:assertEquals(1)
function ot:mixed-op-field2($city as xs:string, $street as xs:string) {
    count(collection($ot:COLLECTION)//address[city >= $city][street = $street])
};

declare
    %test:stats
    %test:args("Rüsselsheim", "Elefantenweg 67")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:no-optimize-field-multi($city as xs:string, $street as xs:string) {
    collection($ot:COLLECTION)//address[street = $street][1]
};

declare
    %test:stats
    %test:args("berta@milchview.org")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-eq-field-nested($email as xs:string) {
    collection($ot:COLLECTION)//address[contact/email = $email]
};

declare
    %test:stats
    %test:args("berta@milchview.org")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-lt-field-nested($email as xs:string) {
    collection($ot:COLLECTION)//address[contact/email < $email]
};

declare
    %test:args("main", "official", "Hofthiergarten")
    %test:assertEquals("Hofthiergarten")
    %test:args("ref", "inofficial", "Hofthiergarten")
    %test:assertEquals("Hofthiergarten")
    %test:args("main", "official", "Dorfprozelten")
    %test:assertEquals("Dorfprozelten")
function ot:equality-field-nested($type as xs:string, $subtype as xs:string, $name as xs:string) {
    collection($ot:COLLECTION)//tei:placeName[@type = $type][@subtype = $subtype][. = $name]/text()
};

declare
    %test:stats
    %test:args("main", "official", "Hofthiergarten")
    %test:assertXPath("$result//stats:index[@type = 'new-range'][@optimization = 2]")
function ot:optimize-field-self($type as xs:string, $subtype as xs:string, $name as xs:string) {
    collection($ot:COLLECTION)//tei:placeName[@type = $type][@subtype = $subtype][. = $name]/text()
};

declare
    %test:assertEquals("Rudi Rüssel")
function ot:parent-attr-equals() {
    collection($ot:COLLECTION)//name[parent::address/@id = "rüssel"]/text()
};

declare
    %test:stats
    %test:assertXPath("$result//stats:index[@type = 'range'][@optimization = 0]")
function ot:optimize-parent-attr-equals() {
    collection($ot:COLLECTION)//name[parent::address/@id = "rüssel"]/text()
};

declare
    %test:assertEquals("Rudi Rüssel")
function ot:parent-nested-attr-equals() {
    collection($ot:COLLECTION)//name[parent::address[@id = "rüssel"]]/text()
};

declare
    %test:stats
    %test:assertXPath("$result//stats:index[@type = 'range'][@optimization = 0]")
function ot:optimize-parent-nested-attr-equals() {
    collection($ot:COLLECTION)//name[parent::address[@id = "rüssel"]]/text()
};

declare
    %test:assertEquals("Rudi Rüssel")
function ot:self-parent-attr-equals() {
    collection($ot:COLLECTION)//name[./parent::address/@id = "rüssel"]/text()
};

declare
    %test:assertEquals("Rudi Rüssel")
function ot:self-parent-nested-attr-equals() {
    collection($ot:COLLECTION)//name[./parent::address[@id = "rüssel"]]/text()
};

(: Filters :)

declare
    %test:args("rudi russel")
    %test:assertEquals("Rudi Rüssel")
    %test:args("rudi rüssel")
    %test:assertEquals("Rudi Rüssel")
function ot:eq-string-filtered($name as xs:string) {
    collection($ot:COLLECTION)//address[name2 = $name]/name2/text()
};

declare
    %test:args("Rudi Russel")
    %test:assertEquals(3)
function ot:ne-string-filtered($name as xs:string) {
    count(collection($ot:COLLECTION)//address[name2 != $name])
};

declare
    %test:args("russel")
    %test:assertEquals("Rudi Rüssel")
function ot:contains-string-filtered($name as xs:string) {
    collection($ot:COLLECTION)//address[contains(name2, $name)]/name2/text()
};

declare
    %test:args("Rudi Rus")
    %test:assertEquals("Rudi Rüssel")
function ot:starts-with-string-filtered($name as xs:string) {
    collection($ot:COLLECTION)//address[starts-with(name2, $name)]/name2/text()
};

declare
    %test:args("ussel")
    %test:assertEquals("Rudi Rüssel")
function ot:ends-with-string-filtered($name as xs:string) {
    collection($ot:COLLECTION)//address[ends-with(name2, $name)]/name2/text()
};

declare
    %test:args("Rudi rüssel")
    %test:assertEquals("Rudi Rüssel")
    %test:args("rudi rüssel")
    %test:assertEquals("Rudi Rüssel")
function ot:eq-field-string-filtered($name as xs:string) {
    collection($ot:COLLECTION)//address[name3 = $name]/name3/text()
};

declare
    %test:args("rudi rüssel")
    %test:assertEquals(3)
function ot:ne-field-string-filtered($name as xs:string) {
    count(collection($ot:COLLECTION)//address[name3 != $name])
};

declare
    %test:args("rüssel")
    %test:assertEquals("Rudi Rüssel")
function ot:contains-field-string-filtered($name as xs:string) {
    collection($ot:COLLECTION)//address[contains(name3, $name)]/name3/text()
};

declare
    %test:args("rudi rüs")
    %test:assertEquals("Rudi Rüssel")
function ot:starts-with-field-string-filtered($name as xs:string) {
    collection($ot:COLLECTION)//address[starts-with(name3, $name)]/name3/text()
};

declare
    %test:args("üssel")
    %test:assertEquals("Rudi Rüssel")
function ot:ends-with-field-string-filtered($name as xs:string) {
    collection($ot:COLLECTION)//address[ends-with(name3, $name)]/name3/text()
};

(: Collations :)

declare
    %test:args("Мла̀тишума")
    %test:assertEquals(2)
    %test:args("Млатишума")
    %test:assertEquals(2)
function ot:eq-string-collation-with-diacritics($name) {
    count(//tei:form[tei:orth = $name])
};

declare
    %test:args("Мла̀тишума")
    %test:assertError("range:EXXQDYFT0001")
    %test:args("Млатишума")
    %test:assertError("range:EXXQDYFT0001")
function ot:contains-string-collation-with-diacritics($name) {
    count(//tei:form[contains(tei:orth, $name)])
};

declare
    %test:args("Мла̀тишума")
    %test:assertEquals(0)
    %test:args("Млатишума")
    %test:assertEquals(0)
function ot:ne-string-collation-with-diacritics($name) {
    count(//tei:form[tei:orth != $name])
};

(:~ See XPath general comparison optimisation bug #2786 :)
declare
    %test:assertEquals(1)
function ot:nested-element-rewrite-bug() {
    let $dita :=
        <dita>
            <topic>
                <prolog>
                    <metadata>
                        <data name="topicType" value="topicValue"/>
                    </metadata>
                </prolog>
                <topic>
                    <prolog>
                        <metadata>
                            <data name="topicType" value="topicValue"/>
                        </metadata>
                    </prolog>
                </topic>
            </topic>
        </dita>
    return
      count($dita/topic/prolog//data[@name eq 'topicType']/@value)
};


Remove the err:code parameter from local:error.

(:
 : Copyright (C) 2018-2019 Reece H. Dunn
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
 :)
xquery version "1.0-ml";

import module namespace sem = "http://marklogic.com/semantics" at "/MarkLogic/semantics.xqy";

declare namespace o = "http://reecedunn.co.uk/xquery/options";
declare option o:implementation "marklogic/6.0";

(:~ Run a query on a MarkLogic server.
 :
 : @param $mode Whether to "run", "profile", or "debug" the query.
 : @param $mimetype The mimetype of the query.
 : @param $module-path The path of the module to invoke, or "" for evaluated scripts.
 : @param $query The query script to evaluate, or "" for invoked modules.
 : @param $vars A map of the variable values by the variable name.
 : @param $types A map of the variable types by the variable name.
 : @param $context-value The context item for XSLT queries as an XML object.
 : @param $context-path The context item for XSLT queries as a module file.
 : @param $rdf-output-format The mimetype to format sem:triple results as, or "" to leave them as is.
 : @param $updating An updating query if "true", or a non-updating query if "false".
 : @param $server The name of the server to use, or "" to not use a server.
 : @param $database The name of the database to use, or "" to not use a database.
 : @param $module-root The path to the server modules, or "" to use the default server value.
 :
 : This script has additional logic to map the semantics of the REST/XCC API to
 : the semantics of the API implemented in the xquery-intellij-plugin to support
 : the Run/Debug Configurations of the IntelliJ IDEs. Specifically:
 :
 : 1.  If there is an error, the REST API returns a "500 Internal Error" HTTP
 :     status. This catches the error and returns an element(err:error) object.
 :
 : 2.  The variables are passed via the REST API as xs:untypedAtomic. This
 :     converts the variables to the type they were specified as in the
 :     xquery-intellij-plugin API.
 :
 : 3.  The REST API only returns the primitive type of each item.
 :)

declare variable $mode as xs:string external;
declare variable $mimetype as xs:string external;
declare variable $module-path as xs:string external;
declare variable $query as xs:string external;
declare variable $vars as xs:string external;
declare variable $types as xs:string external;
declare variable $context-value as xs:string external;
declare variable $context-path as xs:string external;
declare variable $rdf-output-format as xs:string external;
declare variable $updating as xs:string external;
declare variable $server as xs:string external;
declare variable $database as xs:string external;
declare variable $module-root as xs:string external;

declare function local:nullize($value) {
    if (string-length($value) eq 0) then () else $value
};

declare function local:version() {
    substring-before(xdmp:version(), "-") cast as xs:double
};

declare function local:version-minor() {
    substring-after(xdmp:version(), "-") cast as xs:double
};

declare function local:function($ref as xs:string) {
    try { xdmp:eval($ref) } catch * { () }
};

declare function local:item($path as xs:string, $value as xs:string, $type as xs:string) {
    if (string-length($value) ne 0) then
        xdmp:unquote($value)
    else if (string-length($path) ne 0) then
        doc($path)
    else
        ()
};

declare function local:cast-as($value, $type) {
    switch ($type)
    case "xs:anyURI" return $value cast as xs:anyURI
    case "xs:base64Binary" return $value cast as xs:base64Binary
    case "xs:boolean" return $value cast as xs:boolean
    case "xs:byte" return $value cast as xs:byte
    case "xs:date" return $value cast as xs:date
    case "xs:dateTime" return $value cast as xs:dateTime
    case "xs:dateTimeStamp" return $value cast as xs:dateTime (: MarkLogic does not support XML Schema 1.1 Part 2 :)
    case "xs:dayTimeDuration" return $value cast as xs:dayTimeDuration
    case "xs:decimal" return $value cast as xs:decimal
    case "xs:double" return $value cast as xs:double
    case "xs:duration" return $value cast as xs:duration
    case "xs:float" return $value cast as xs:float
    case "xs:gDay" return $value cast as xs:gDay
    case "xs:gMonth" return $value cast as xs:gMonth
    case "xs:gMonthDay" return $value cast as xs:gMonthDay
    case "xs:gYear" return $value cast as xs:gYear
    case "xs:gYearMonth" return $value cast as xs:gYearMonth
    case "xs:hexBinary" return $value cast as xs:hexBinary
    case "xs:int" return $value cast as xs:int
    case "xs:integer" return $value cast as xs:integer
    case "xs:language" return $value cast as xs:language
    case "xs:long" return $value cast as xs:long
    case "xs:negativeInteger" return $value cast as xs:negativeInteger
    case "xs:nonNegativeInteger" return $value cast as xs:nonNegativeInteger
    case "xs:nonPositiveInteger" return $value cast as xs:nonPositiveInteger
    case "xs:normalizedString" return $value cast as xs:normalizedString
    case "xs:numeric" return $value cast as xs:numeric
    case "xs:positiveInteger" return $value cast as xs:positiveInteger
    case "xs:short" return $value cast as xs:short
    case "xs:string" return $value cast as xs:string
    case "xs:time" return $value cast as xs:time
    case "xs:token" return $value cast as xs:token
    case "xs:unsignedByte" return $value cast as xs:unsignedByte
    case "xs:unsignedInt" return $value cast as xs:unsignedInt
    case "xs:unsignedLong" return $value cast as xs:unsignedLong
    case "xs:unsignedShort" return $value cast as xs:unsignedShort
    case "xs:yearMonthDuration" return $value cast as xs:yearMonthDuration
    case "xs:ENTITY" return $value cast as xs:ENTITY
    case "xs:ID" return $value cast as xs:ID
    case "xs:IDREF" return $value cast as xs:IDREF
    case "xs:Name" return $value cast as xs:Name
    case "xs:NCName" return $value cast as xs:NCName
    case "xs:NMTOKEN" return $value cast as xs:NMTOKEN
    case "xs:QName" return $value cast as xs:QName
    default return $value
};

declare function local:parse-vars($values as map:map, $types as map:map) as item()* {
    for $key in map:keys($values)
    let $value := local:cast-as(map:get($values, $key), map:get($types, $key))
    return (xs:QName($key), $value)
};

declare function local:parse-vars-map($values as map:map, $types as map:map) as map:map {
    let $ret := map:map()
    let $_ :=
        for $key in map:keys($values)
        let $value := local:cast-as(map:get($values, $key), map:get($types, $key))
        return map:put($ret, $key, $value)
    return $ret
};

declare function local:derived-type-name($value) {
    (: NOTE: Ordering is important here -- reverse type hierarchy :)
    typeswitch ($value)
    case function(*) return "function(*)"
    (: xs:integer derived types :)
    case xs:positiveInteger return "xs:positiveInteger"
    case xs:unsignedByte return "xs:unsignedByte"
    case xs:unsignedShort return "xs:unsignedShort"
    case xs:unsignedInt return "xs:unsignedInt"
    case xs:unsignedLong return "xs:unsignedLong"
    case xs:nonNegativeInteger return "xs:nonNegativeInteger"
    case xs:byte return "xs:byte"
    case xs:short return "xs:short"
    case xs:int return "xs:int"
    case xs:long return "xs:long"
    case xs:negativeInteger return "xs:negativeInteger"
    case xs:nonPositiveInteger return "xs:nonPositiveInteger"
    (: xs:string derived types :)
    case xs:ENTITY return "xs:ENTITY"
    case xs:IDREF return "xs:IDREF"
    case xs:ID return "xs:ID"
    case xs:NCName return "xs:NCName"
    case xs:Name return "xs:Name"
    case xs:NMTOKEN return "xs:NMTOKEN"
    case xs:language return "xs:language"
    case xs:token return "xs:token"
    case xs:normalizedString return "xs:normalizedString"
    default return () (: primitive type, so does not need the type name to be specified. :)
};

declare function local:rdf-format($mimetype) {
    switch ($mimetype)
    case "application/n-quads" return "nquad"
    case "application/n-triples" return "ntriple"
    case "application/rdf+json" return "rdfjson"
    case "application/rdf+xml" return "rdfxml"
    case "application/trig" return "trig"
    case "application/vnd.marklogic.triples+xml" return "triplexml"
    case "text/n3" return "n3"
    case "text/turtle" return "turtle"
    default return fn:error("UNSUPPORTED-RDF-FORMAT", "Unsupported RDF format: " || $mimetype)
};

declare function local:eval-options() {
    let $server := local:nullize($server) ! xdmp:server(.)
    let $database := local:nullize($database) ! xdmp:database(.)
    return <options xmlns="xdmp:eval">{
        if ($mode eq "validate") then
            <static-check>true</static-check>
        else
            (),
        if (exists($database)) then
            <database>{$database}</database>
        else
            (),
        if (exists($server)) then
            <default-collation>{xdmp:server-collation($server)}</default-collation>
        else
            (),
        let $server-coordinate-system := local:function("xdmp:server-coordinate-system#1")
        return if (exists($server) and exists($server-coordinate-system)) then
            <default-coordinate-system>{$server-coordinate-system($server)}</default-coordinate-system>
        else
            (),
        if (exists($server) and $mimetype eq "application/xquery") then
            <default-xquery-version>{xdmp:server-default-xquery-version($server)}</default-xquery-version>
        else
            (),
        if (exists($server)) then
            <modules>{xdmp:server-modules-database($server)}</modules>
        else
            <modules>0</modules> (: file system :),
        if (exists($server) or string-length($module-root) ne 0) then
            <root>{(local:nullize($module-root), xdmp:server-root($server))[1]}</root>
        else
            (),
        let $major := local:version()
        let $minor := local:version-minor()
        return if ($major lt 7.0 or ($major eq 8.0 and $minor lt 7.0) or ($major eq 9.0 and $minor lt 2.0) or $major gt 9.0) then
            if ($updating) then
                <transaction-mode>update</transaction-mode>
            else
                <transaction-mode>query</transaction-mode>
        else
            (: MarkLogic 8.0-7 and 9.0-2 support <update> :)
            <update>{$updating}</update>
    }</options>
};

declare function local:error(
    $err:module as xs:string?,
    $err:line-number as xs:integer?,
    $err:column-number as xs:integer,
    $err:additional as element(error:error)
) {
    <err:error xmlns:dbg="http://reecedunn.co.uk/xquery/debug">
        <err:code>{
            let $code := $err:additional/error:code/text()
            return if (exists($code) and not(starts-with($code, "XDMP-"))) then $code else "err:XPST0003"
        }</err:code>
        <err:vendor-code>{$err:additional/error:code/text()}</err:vendor-code>
        <err:description>{$err:additional/error:message/text()}</err:description>
        <err:value count="{count($err:additional/error:data/error:datum)}">{
            $err:additional/error:data/error:datum/text() ! <err:item>{.}</err:item>
        }</err:value>
        <err:module line="{$err:line-number}" column="{$err:column-number + 1}">{$err:module}</err:module>
        <dbg:stack>{
            for $frame in $err:additional/error:stack/error:frame[position() != last()]
            let $module := $frame/error:uri/text()
            let $line := $frame/error:line/text() cast as xs:integer?
            let $column := $frame/error:column/text() cast as xs:integer?
            return <dbg:frame><dbg:module line="{$line}" column="{$column + 1}">{$module}</dbg:module></dbg:frame>
        }</dbg:stack>
    </err:error>
};

declare function local:javascript() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $options := local:eval-options()
    return if (local:version() < 8.0) then
        fn:error(
            xs:QName("UNSUPPORTED-VERSION"),
            "MarkLogic " || xdmp:version() || " does not support server-side JavaScript"
        )
    else if (string-length($query) ne 0) then
        switch ($mode)
        case "run" return
            let $f := local:function("xdmp:javascript-eval#3")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        default return ()
    else
        switch ($mode)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:invoke($module-path, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        default return ()
};

declare function local:sparql-query() as item()* {
    let $variables := local:parse-vars-map(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return switch ($mode)
    case "run" return
        let $start := xdmp:elapsed-time()
        let $ret := sem:sparql($query, $variables)
        return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:sparql-update() as item()* {
    let $variables := local:parse-vars-map(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return if (local:version() < 8.0) then
        fn:error(
            xs:QName("UNSUPPORTED-VERSION"),
            "MarkLogic " || xdmp:version() || " does not support SPARQL update queries"
        )
    else switch ($mode)
    case "run" return
        let $start := xdmp:elapsed-time()
        let $ret := sem:sparql-update($query, $variables)
        return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:sql() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return switch ($mode)
    case "run" return
        if (local:version() < 9.0) then
            let $f := local:function("xdmp:sql#1")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query)
            return (xdmp:elapsed-time() - $start, $ret)
        else
            let $f := local:function("xdmp:sql#3")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query, (), $variables)
            return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:xquery() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $options := local:eval-options()
    return if (string-length($query) ne 0) then
        switch ($mode)
        case "profile" return prof:eval($query, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:eval($query, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:eval($query, $variables, $options)
        default return ()
    else
        switch ($mode)
        case "profile" return prof:invoke($module-path, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:invoke($module-path, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:invoke($module-path, $variables, $options)
        default return ()
};

declare function local:xslt() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $input := local:item($context-path, $context-value, "application/xml")
    let $options := local:eval-options()
    return if (string-length($query) ne 0) then
        switch ($mode)
        case "profile" return prof:xslt-eval(xdmp:unquote($query), $input, $variables, $options)
        case "run" return
            let $query := xdmp:unquote($query)
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:xslt-eval($query, $input, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:xslt-eval(xdmp:unquote($query), $input, $variables, $options)
        default return ()
    else
        switch ($mode)
        case "profile" return prof:xslt-invoke($module-path, $input, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:xslt-invoke($module-path, $input, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:xslt-invoke($module-path, $input, $variables, $options)
        default return ()
};

try {
    let $retvals :=
        switch ($mimetype)
        case "application/sparql-query" return local:sparql-query()
        case "application/sparql-update" return local:sparql-update()
        case "application/sql" return local:sql()
        case "application/vnd.marklogic-javascript" return local:javascript()
        case "application/xquery" return local:xquery()
        case "application/xslt+xml" return local:xslt()
        default return ()
    let $triples := for $item in $retvals where $item instance of sem:triple return $item
    let $retvals := for $item in $retvals where not($item instance of sem:triple) return $item
    return if (exists($triples) and $rdf-output-format ne "") then
        let $rdf-output := sem:rdf-serialize($triples, local:rdf-format($rdf-output-format))
        let $_ := xdmp:add-response-header("X-Content-Type-" || (count($retvals) + 1), $rdf-output-format)
        return (
            for $retval at $i in $retvals
            let $_ := local:derived-type-name($retval) ! xdmp:add-response-header("X-Derived-" || $i, .)
            return $retval,
            $rdf-output
        )
    else
        for $retval at $i in ($retvals, $triples)
        let $_ := local:derived-type-name($retval) ! xdmp:add-response-header("X-Derived-" || $i, .)
        return $retval
} catch * {
    let $_ := xdmp:add-response-header("X-Derived-1", "err:error")
    return local:error($err:module, $err:line-number, $err:column-number, $err:additional)
}

(:
 : Copyright (C) 2018-2019 Reece H. Dunn
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
 :)
xquery version "1.0-ml";

import module namespace sem = "http://marklogic.com/semantics" at "/MarkLogic/semantics.xqy";

declare namespace o = "http://reecedunn.co.uk/xquery/options";
declare option o:implementation "marklogic/6.0";

(:~ Run a query on a MarkLogic server.
 :
 : @param $mode Whether to "run", "profile", or "debug" the query.
 : @param $mimetype The mimetype of the query.
 : @param $module-path The path of the module to invoke, or "" for evaluated scripts.
 : @param $query The query script to evaluate, or "" for invoked modules.
 : @param $vars A map of the variable values by the variable name.
 : @param $types A map of the variable types by the variable name.
 : @param $context-value The context item for XSLT queries as an XML object.
 : @param $context-path The context item for XSLT queries as a module file.
 : @param $rdf-output-format The mimetype to format sem:triple results as, or "" to leave them as is.
 : @param $updating An updating query if "true", or a non-updating query if "false".
 : @param $server The name of the server to use, or "" to not use a server.
 : @param $database The name of the database to use, or "" to not use a database.
 : @param $module-root The path to the server modules, or "" to use the default server value.
 :
 : This script has additional logic to map the semantics of the REST/XCC API to
 : the semantics of the API implemented in the xquery-intellij-plugin to support
 : the Run/Debug Configurations of the IntelliJ IDEs. Specifically:
 :
 : 1.  If there is an error, the REST API returns a "500 Internal Error" HTTP
 :     status. This catches the error and returns an element(err:error) object.
 :
 : 2.  The variables are passed via the REST API as xs:untypedAtomic. This
 :     converts the variables to the type they were specified as in the
 :     xquery-intellij-plugin API.
 :
 : 3.  The REST API only returns the primitive type of each item.
 :)

declare variable $mode as xs:string external;
declare variable $mimetype as xs:string external;
declare variable $module-path as xs:string external;
declare variable $query as xs:string external;
declare variable $vars as xs:string external;
declare variable $types as xs:string external;
declare variable $context-value as xs:string external;
declare variable $context-path as xs:string external;
declare variable $rdf-output-format as xs:string external;
declare variable $updating as xs:string external;
declare variable $server as xs:string external;
declare variable $database as xs:string external;
declare variable $module-root as xs:string external;

declare function local:nullize($value) {
    if (string-length($value) eq 0) then () else $value
};

declare function local:version() {
    substring-before(xdmp:version(), "-") cast as xs:double
};

declare function local:version-minor() {
    substring-after(xdmp:version(), "-") cast as xs:double
};

declare function local:function($ref as xs:string) {
    try { xdmp:eval($ref) } catch * { () }
};

declare function local:item($path as xs:string, $value as xs:string, $type as xs:string) {
    if (string-length($value) ne 0) then
        xdmp:unquote($value)
    else if (string-length($path) ne 0) then
        doc($path)
    else
        ()
};

declare function local:cast-as($value, $type) {
    switch ($type)
    case "xs:anyURI" return $value cast as xs:anyURI
    case "xs:base64Binary" return $value cast as xs:base64Binary
    case "xs:boolean" return $value cast as xs:boolean
    case "xs:byte" return $value cast as xs:byte
    case "xs:date" return $value cast as xs:date
    case "xs:dateTime" return $value cast as xs:dateTime
    case "xs:dateTimeStamp" return $value cast as xs:dateTime (: MarkLogic does not support XML Schema 1.1 Part 2 :)
    case "xs:dayTimeDuration" return $value cast as xs:dayTimeDuration
    case "xs:decimal" return $value cast as xs:decimal
    case "xs:double" return $value cast as xs:double
    case "xs:duration" return $value cast as xs:duration
    case "xs:float" return $value cast as xs:float
    case "xs:gDay" return $value cast as xs:gDay
    case "xs:gMonth" return $value cast as xs:gMonth
    case "xs:gMonthDay" return $value cast as xs:gMonthDay
    case "xs:gYear" return $value cast as xs:gYear
    case "xs:gYearMonth" return $value cast as xs:gYearMonth
    case "xs:hexBinary" return $value cast as xs:hexBinary
    case "xs:int" return $value cast as xs:int
    case "xs:integer" return $value cast as xs:integer
    case "xs:language" return $value cast as xs:language
    case "xs:long" return $value cast as xs:long
    case "xs:negativeInteger" return $value cast as xs:negativeInteger
    case "xs:nonNegativeInteger" return $value cast as xs:nonNegativeInteger
    case "xs:nonPositiveInteger" return $value cast as xs:nonPositiveInteger
    case "xs:normalizedString" return $value cast as xs:normalizedString
    case "xs:numeric" return $value cast as xs:numeric
    case "xs:positiveInteger" return $value cast as xs:positiveInteger
    case "xs:short" return $value cast as xs:short
    case "xs:string" return $value cast as xs:string
    case "xs:time" return $value cast as xs:time
    case "xs:token" return $value cast as xs:token
    case "xs:unsignedByte" return $value cast as xs:unsignedByte
    case "xs:unsignedInt" return $value cast as xs:unsignedInt
    case "xs:unsignedLong" return $value cast as xs:unsignedLong
    case "xs:unsignedShort" return $value cast as xs:unsignedShort
    case "xs:yearMonthDuration" return $value cast as xs:yearMonthDuration
    case "xs:ENTITY" return $value cast as xs:ENTITY
    case "xs:ID" return $value cast as xs:ID
    case "xs:IDREF" return $value cast as xs:IDREF
    case "xs:Name" return $value cast as xs:Name
    case "xs:NCName" return $value cast as xs:NCName
    case "xs:NMTOKEN" return $value cast as xs:NMTOKEN
    case "xs:QName" return $value cast as xs:QName
    default return $value
};

declare function local:parse-vars($values as map:map, $types as map:map) as item()* {
    for $key in map:keys($values)
    let $value := local:cast-as(map:get($values, $key), map:get($types, $key))
    return (xs:QName($key), $value)
};

declare function local:parse-vars-map($values as map:map, $types as map:map) as map:map {
    let $ret := map:map()
    let $_ :=
        for $key in map:keys($values)
        let $value := local:cast-as(map:get($values, $key), map:get($types, $key))
        return map:put($ret, $key, $value)
    return $ret
};

declare function local:derived-type-name($value) {
    (: NOTE: Ordering is important here -- reverse type hierarchy :)
    typeswitch ($value)
    case function(*) return "function(*)"
    (: xs:integer derived types :)
    case xs:positiveInteger return "xs:positiveInteger"
    case xs:unsignedByte return "xs:unsignedByte"
    case xs:unsignedShort return "xs:unsignedShort"
    case xs:unsignedInt return "xs:unsignedInt"
    case xs:unsignedLong return "xs:unsignedLong"
    case xs:nonNegativeInteger return "xs:nonNegativeInteger"
    case xs:byte return "xs:byte"
    case xs:short return "xs:short"
    case xs:int return "xs:int"
    case xs:long return "xs:long"
    case xs:negativeInteger return "xs:negativeInteger"
    case xs:nonPositiveInteger return "xs:nonPositiveInteger"
    (: xs:string derived types :)
    case xs:ENTITY return "xs:ENTITY"
    case xs:IDREF return "xs:IDREF"
    case xs:ID return "xs:ID"
    case xs:NCName return "xs:NCName"
    case xs:Name return "xs:Name"
    case xs:NMTOKEN return "xs:NMTOKEN"
    case xs:language return "xs:language"
    case xs:token return "xs:token"
    case xs:normalizedString return "xs:normalizedString"
    default return () (: primitive type, so does not need the type name to be specified. :)
};

declare function local:rdf-format($mimetype) {
    switch ($mimetype)
    case "application/n-quads" return "nquad"
    case "application/n-triples" return "ntriple"
    case "application/rdf+json" return "rdfjson"
    case "application/rdf+xml" return "rdfxml"
    case "application/trig" return "trig"
    case "application/vnd.marklogic.triples+xml" return "triplexml"
    case "text/n3" return "n3"
    case "text/turtle" return "turtle"
    default return fn:error("UNSUPPORTED-RDF-FORMAT", "Unsupported RDF format: " || $mimetype)
};

declare function local:eval-options() {
    let $server := local:nullize($server) ! xdmp:server(.)
    let $database := local:nullize($database) ! xdmp:database(.)
    return <options xmlns="xdmp:eval">{
        if ($mode eq "validate") then
            <static-check>true</static-check>
        else
            (),
        if (exists($database)) then
            <database>{$database}</database>
        else
            (),
        if (exists($server)) then
            <default-collation>{xdmp:server-collation($server)}</default-collation>
        else
            (),
        let $server-coordinate-system := local:function("xdmp:server-coordinate-system#1")
        return if (exists($server) and exists($server-coordinate-system)) then
            <default-coordinate-system>{$server-coordinate-system($server)}</default-coordinate-system>
        else
            (),
        if (exists($server) and $mimetype eq "application/xquery") then
            <default-xquery-version>{xdmp:server-default-xquery-version($server)}</default-xquery-version>
        else
            (),
        if (exists($server)) then
            <modules>{xdmp:server-modules-database($server)}</modules>
        else
            <modules>0</modules> (: file system :),
        if (exists($server) or string-length($module-root) ne 0) then
            <root>{(local:nullize($module-root), xdmp:server-root($server))[1]}</root>
        else
            (),
        let $major := local:version()
        let $minor := local:version-minor()
        return if ($major lt 7.0 or ($major eq 8.0 and $minor lt 7.0) or ($major eq 9.0 and $minor lt 2.0) or $major gt 9.0) then
            if ($updating) then
                <transaction-mode>update</transaction-mode>
            else
                <transaction-mode>query</transaction-mode>
        else
            (: MarkLogic 8.0-7 and 9.0-2 support <update> :)
            <update>{$updating}</update>
    }</options>
};

declare function local:error(
    $err:code as xs:QName,
    $err:module as xs:string?,
    $err:line-number as xs:integer?,
    $err:column-number as xs:integer,
    $err:additional as element(error:error)
) {
    <err:error xmlns:dbg="http://reecedunn.co.uk/xquery/debug">
        <err:code>{$err:code}</err:code>
        <err:vendor-code>{$err:additional/error:code/text()}</err:vendor-code>
        <err:description>{$err:additional/error:message/text()}</err:description>
        <err:value count="{count($err:additional/error:data/error:datum)}">{
            $err:additional/error:data/error:datum/text() ! <err:item>{.}</err:item>
        }</err:value>
        <err:module line="{$err:line-number}" column="{$err:column-number + 1}">{$err:module}</err:module>
        <dbg:stack>{
            for $frame in $err:additional/error:stack/error:frame[position() != last()]
            let $module := $frame/error:uri/text()
            let $line := $frame/error:line/text() cast as xs:integer?
            let $column := $frame/error:column/text() cast as xs:integer?
            return <dbg:frame><dbg:module line="{$line}" column="{$column + 1}">{$module}</dbg:module></dbg:frame>
        }</dbg:stack>
    </err:error>
};

declare function local:javascript() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $options := local:eval-options()
    return if (local:version() < 8.0) then
        fn:error(
            xs:QName("UNSUPPORTED-VERSION"),
            "MarkLogic " || xdmp:version() || " does not support server-side JavaScript"
        )
    else if (string-length($query) ne 0) then
        switch ($mode)
        case "run" return
            let $f := local:function("xdmp:javascript-eval#3")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        default return ()
    else
        switch ($mode)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:invoke($module-path, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        default return ()
};

declare function local:sparql-query() as item()* {
    let $variables := local:parse-vars-map(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return switch ($mode)
    case "run" return
        let $start := xdmp:elapsed-time()
        let $ret := sem:sparql($query, $variables)
        return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:sparql-update() as item()* {
    let $variables := local:parse-vars-map(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return if (local:version() < 8.0) then
        fn:error(
            xs:QName("UNSUPPORTED-VERSION"),
            "MarkLogic " || xdmp:version() || " does not support SPARQL update queries"
        )
    else switch ($mode)
    case "run" return
        let $start := xdmp:elapsed-time()
        let $ret := sem:sparql-update($query, $variables)
        return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:sql() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $query :=
        if (string-length($query) ne 0) then
            $query
        else
            () (: TODO: Read the contents of the query file from the modules database. :)
    return switch ($mode)
    case "run" return
        if (local:version() < 9.0) then
            let $f := local:function("xdmp:sql#1")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query)
            return (xdmp:elapsed-time() - $start, $ret)
        else
            let $f := local:function("xdmp:sql#3")
            let $start := xdmp:elapsed-time()
            let $ret := $f($query, (), $variables)
            return (xdmp:elapsed-time() - $start, $ret)
    default return ()
};

declare function local:xquery() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $options := local:eval-options()
    return if (string-length($query) ne 0) then
        switch ($mode)
        case "profile" return prof:eval($query, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:eval($query, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:eval($query, $variables, $options)
        default return ()
    else
        switch ($mode)
        case "profile" return prof:invoke($module-path, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:invoke($module-path, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:invoke($module-path, $variables, $options)
        default return ()
};

declare function local:xslt() as item()* {
    let $variables := local:parse-vars(xdmp:unquote($vars), xdmp:unquote($types))
    let $input := local:item($context-path, $context-value, "application/xml")
    let $options := local:eval-options()
    return if (string-length($query) ne 0) then
        switch ($mode)
        case "profile" return prof:xslt-eval(xdmp:unquote($query), $input, $variables, $options)
        case "run" return
            let $query := xdmp:unquote($query)
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:xslt-eval($query, $input, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:xslt-eval(xdmp:unquote($query), $input, $variables, $options)
        default return ()
    else
        switch ($mode)
        case "profile" return prof:xslt-invoke($module-path, $input, $variables, $options)
        case "run" return
            let $start := xdmp:elapsed-time()
            let $ret := xdmp:xslt-invoke($module-path, $input, $variables, $options)
            return (xdmp:elapsed-time() - $start, $ret)
        case "validate" return xdmp:xslt-invoke($module-path, $input, $variables, $options)
        default return ()
};

try {
    let $retvals :=
        switch ($mimetype)
        case "application/sparql-query" return local:sparql-query()
        case "application/sparql-update" return local:sparql-update()
        case "application/sql" return local:sql()
        case "application/vnd.marklogic-javascript" return local:javascript()
        case "application/xquery" return local:xquery()
        case "application/xslt+xml" return local:xslt()
        default return ()
    let $triples := for $item in $retvals where $item instance of sem:triple return $item
    let $retvals := for $item in $retvals where not($item instance of sem:triple) return $item
    return if (exists($triples) and $rdf-output-format ne "") then
        let $rdf-output := sem:rdf-serialize($triples, local:rdf-format($rdf-output-format))
        let $_ := xdmp:add-response-header("X-Content-Type-" || (count($retvals) + 1), $rdf-output-format)
        return (
            for $retval at $i in $retvals
            let $_ := local:derived-type-name($retval) ! xdmp:add-response-header("X-Derived-" || $i, .)
            return $retval,
            $rdf-output
        )
    else
        for $retval at $i in ($retvals, $triples)
        let $_ := local:derived-type-name($retval) ! xdmp:add-response-header("X-Derived-" || $i, .)
        return $retval
} catch * {
    let $_ := xdmp:add-response-header("X-Derived-1", "err:error")
    return local:error($err:code, $err:module, $err:line-number, $err:column-number, $err:additional)
}