Edits to convert instance PR

Edits to convert instance PR

(:
 Copyright 2002-2016 MarkLogic Corporation.  All Rights Reserved.

 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";

(: This module supports code generation for entity services.
 : This module has no public API.  See entity-services.xqy for
 : the public interface.
 :)
module namespace es-codegen = "http://marklogic.com/entity-services-codegen";

import module namespace esi = "http://marklogic.com/entity-services-impl"
    at "entity-services-impl.xqy";

import module namespace functx   = "http://www.functx.com" at "/MarkLogic/functx/functx-1.0-nodoc-2007-01.xqy";

declare namespace es = "http://marklogic.com/entity-services";
declare namespace tde = "http://marklogic.com/xdmp/tde";
declare namespace xq = "http://www.w3.org/2012/xquery";

declare option xdmp:mapping "false";
declare option xq:require-feature "xdmp:three-one";

declare private function es-codegen:casting-function-name(
    $datatype as xs:string
) as xs:string
{
    if ($datatype eq "iri")
    then "sem:iri"
    else "xs:" || $datatype
};

declare private function es-codegen:comment(
    $comment-text as xs:string*
) as xs:string
{
    concat('    (: ', string-join($comment-text, "
       "), '  :)
')
};

declare private function es-codegen:extraction-for(
    $prefix as xs:string,
    $model as map:map,
    $entity-type-name as xs:string,
    $property-name as xs:string
) as xs:string
{
    let $entity-type := $model=>map:get("definitions")=>map:get($entity-type-name)
    let $properties := map:get($entity-type, "properties")
    let $required-properties := if (empty(map:get($entity-type, "required")))
        then ()
        else json:array-values( map:get($entity-type, "required") )
    let $is-required := $property-name =
            ( map:get($entity-type, "primaryKey"), $required-properties )
    let $is-array :=
            map:get(map:get($properties, $property-name), "datatype")
            eq "array"
    let $property-datatype := esi:resolve-datatype($model, $entity-type-name, $property-name)
    let $casting-function-name := es-codegen:casting-function-name($property-datatype)
    let $wrap-if-array := function($str, $fn, $arrity-ok) {
            if ($is-array and $is-required)
            then concat("json:to-array(", $str, " ! ", $fn, "(.) )")
            else
            if ($is-array)
            then concat("es:extract-array(", $str, ", ", $fn, if ($arrity-ok) then "#1" else (), ")")
            else concat($fn, "(", $str, ")")
        }
    let $ref :=
        if ($is-array)
        then $properties=>map:get($property-name)=>map:get("items")=>map:get("$ref")
        else $properties=>map:get($property-name)=>map:get("$ref")
    let $path-to-property := concat("$source-node/", $property-name)
    let $property-comment :=
        if (empty($ref))
        then ""
        else if (contains($ref, "#/definitions"))
        then es-codegen:comment("The following property is a local reference.")
        else
        es-codegen:comment((
            'The following property assigment comes from an external reference.',
            'Its generated value probably requires developer attention.'))
    let $ref-name := functx:substring-after-last($ref, "/")
    let $extract-reference-fn :=
            concat("function($path) { json:object()",
                      "    =>map:with('$type', '", $ref-name, "')",
                      "    =>map:with('$ref', $path/", $ref-name, "/text() ) }")
    let $value :=
        if (empty($ref))
        then
            $wrap-if-array($path-to-property, $casting-function-name, true())
        else
            if (contains($ref, "#/definitions"))
            then
                $wrap-if-array($path-to-property, concat($prefix, ":extract-instance-", $ref-name), true())
            else
                $wrap-if-array($path-to-property, $extract-reference-fn, false())
    (: if a property is required, use map:with to force inclusion :)
    let $function-call-string :=
        if ($is-required)
        then "    =>   map:with("
        else "    =>es:optional("
    return
        fn:concat( $property-comment,
               $function-call-string,
               functx:pad-string-to-length("'" || $property-name || "',", " ", max((  (string-length($property-name)+4), 25) )+1 ),
               $value,
               ")")
};

declare function es-codegen:instance-converter-generate(
    $model as map:map
) as document-node()
{
    let $info := map:get($model, "info")
    let $title := map:get($info, "title")
    let $prefix := lower-case(substring($title,1,1)) || substring($title,2)
    let $version:= map:get($info, "version")
    let $base-uri := esi:resolve-base-uri($info)
    return
document {
<module>xquery version "1.0-ml";

(:
 This module was generated by MarkLogic Entity Services.
 The source entity type document was {$title}-{$version}
 To use this module, examine how you wish to extract data from sources,
 and modify the various extract-instance-{{X}} functions to
 create the instances you wish.

 You may wish to/need to alter
 1.  values.  For example, creating duration values from decimal months.
 2.  references.  This conversion module assumes you want to denormalize
     instances when storing them in documents.  You may choose to remove
     code that denormalizes, and just include reference values in your
     instances instead.
 3.  Source XPath expressions.  The data coming into the extract-instance-{{X}}
     functions will probably not be exactly what this module predicts.

 After modifying this file, put it in your project for deployment to the modules
 database of your application, and check it into your source control system.

 Modification History:
 Generated at timestamp: {fn:current-dateTime()}
 Persisted by AUTHOR
 Date: DATE
 :)

module namespace {$prefix}
    = "{$base-uri}{$title}-{$version}";

import module namespace es = "http://marklogic.com/entity-services"
    at "/MarkLogic/entity-services/entity-services.xqy";

declare option xdmp:mapping "false";

(:
  extract-instance-{{entity-type}} functions

  These functions together take a source document and create a nested
  map structure from it.
  The resulting map is used by instance-to-canonical-xml to create documents
  in the database.

  It is expected that an implementer will edit at least XPath expressions in
  the extraction functions.  It is less likely that you will want to edit
  the instance-to-canonical-xml or envelope functions.
 :)

{
    for $entity-type-name in map:keys(map:get($model, "definitions"))
    return
    <extract-instance>
(:~
 : Creates a map:map instance from some source document.
 : @param $source-node  A document or node that contains
 :   data for populating a {$entity-type-name}
 : @return A map:map instance with extracted data and
 :   metadata about the instance.
 :)
declare function {$prefix}:extract-instance-{$entity-type-name}(
    $source as node()?
) as map:map
{{
    let $source-node := {$prefix}:init-source($source, '{$entity-type-name}')

    let $instance := json:object()
    (: If desired, add the original source document as an attachment. :)
        =>{$prefix}:add-attachments($source-node, $source)
    (: Add type information to the entity instance (required, do not change) :)
        =>map:with('$type', '{ $entity-type-name }')

    return

    (: The following logic may not be required for every extraction       :)
    (: if this $source-node has no child elements, it has a reference key :)
    if (empty($source-node/*))
    then $instance=>map:with('$ref', $source-node/text())
    (: Otherwise, this source node contains instance data. Populate it. :)
    else

    (:
    The following code populates the properties of the '{$entity-type-name}'
    entity type. Ensure that all of the property paths are correct for your
    source data.  The general pattern is
    =>map:with('keyName', casting-function($source-node/path/to/data))
    but you may also wish to convert values
    =>map:with('dateKeyName',
          xdmp:parse-dateTime("[Y0001]-[M01]-[D01]T[h01]:[m01]:[s01].[f1][Z]",
          $source-node/path/to/data/in/the/source))
    You can also implement lookup functions,
    =>map:with('lookupKey',
          cts:search( collection('customers'),
              string($source-node/path/to/lookup/key))/id
    or populate the instance with constants.
    =>map:with('constantValue', 10)
    The output of this function should structurally match the output of
    es:model-get-test-instances($model)
    :)

    $instance
{
    (: Begin code generation block :)
    let $entity-type := $model=>map:get("definitions")=>map:get($entity-type-name)
    let $properties := map:get($entity-type, "properties")
    let $value-lines :=
        for $property-name in map:keys($properties)
        return es-codegen:extraction-for($prefix, $model, $entity-type-name, $property-name)
    return fn:string-join($value-lines, "
")
        (: end code generation block :)
    }
}};
</extract-instance>/text()
}

(:~
 : Turns an entity instance into an XML structure.
 : This out-of-the box implementation traverses a map structure
 : and turns it deterministically into an XML tree.
 : Using this function as-is should be sufficient for most use
 : cases, and will play well with other generated artifacts.
 : @param $entity-instance A map:map instance returned from one of the extract-instance
 :    functions.
 : @return An XML element that encodes the instance.
 :)
declare function {$prefix}:instance-to-canonical-xml(
    $entity-instance as map:map
) as element()
{{
    (: Construct an element that is named the same as the Entity Type :)
    element {{ map:get($entity-instance, "$type") }}  {{
        if ( map:contains($entity-instance, "$ref") )
        then map:get($entity-instance, "$ref")
        else
            for $key in map:keys($entity-instance)
            let $instance-property := map:get($entity-instance, $key)
            where ($key castable as xs:NCName)
            return
                typeswitch ($instance-property)
                (: This branch handles embedded objects.  You can choose to prune
                   an entity's representation of extend it with lookups here. :)
                case json:object+
                    return
                        for $prop in $instance-property
                        return element {{ $key }} {{ {$prefix}:instance-to-canonical-xml($prop) }}
                (: An array can also treated as multiple elements :)
                case json:array
                    return
                        for $val in json:array-values($instance-property)
                        return
                            if ($val instance of json:object)
                            then element {{ $key }} {{
                                attribute datatype {{ "array" }},
                                {$prefix}:instance-to-canonical-xml($val) }}
                            else element {{ $key }} {{
                                attribute datatype {{ "array" }},
                                $val }}
                (: A sequence of values should be simply treated as multiple elements :)
                case item()+
                    return
                        for $val in $instance-property
                        return element {{ $key }} {{ $val }}
                default return element {{ $key }} {{ $instance-property }}
    }}
}};

(:
 : Wraps a canonical instance (returned by instance-to-canonical-xml())
 : within an envelope patterned document, along with the source
 : document, which is stored in an attachments section.
 : @param $entity-instance an instance, as returned by an extract-instance
 : function
 : @return A document which wraps both the canonical instance and source docs.
 :)
declare function {$prefix}:instance-to-envelope(
    $entity-instance as map:map
) as document-node()
{{
    document {{
        element es:envelope {{
            element es:instance {{
                element es:info {{
                    element es:title {{ map:get($entity-instance,'$type') }},
                    element es:version {{ "{$version}" }}
                }},
                {$prefix}:instance-to-canonical-xml($entity-instance)
            }},
            element es:attachments {{
                map:get($entity-instance, "$attachments")
            }}
        }}
    }}
}};

declare private function {$prefix}:init-source(
    $source as node()*,
    $entity-type-name as xs:string
) as node()*
{{
    if ( ($source instance of document-node())
        or (exists
            ($source/element()[fn:node-name(.) eq xs:QName($entity-type-name)] )))
    then $source/node()
    else $source
}};

declare private function {$prefix}:add-attachments(
    $instance as json:object,
    $source-node as node()*,
    $source as node()*
) as json:object
{{
    $instance
    =>map:with('$attachments',
        typeswitch($source-node)
        case object-node() return xdmp:quote($source)
        case array-node() return xdmp:quote($source)
        default return $source)
}};

</module>/text()
}

};

declare private function es-codegen:value-for-conversion(
    $source-model as map:map,
    $target-model as map:map,
    $target-entity-type-name as xs:string,
    $target-property-name as xs:string,
    $display-property-name as xs:string
) as xs:string
{
    let $target-entity-type := $target-model
        =>map:get("definitions")
        =>map:get($target-entity-type-name)
    let $target-property := $target-entity-type
        =>map:get("properties")
        =>map:get($target-property-name)
    let $source-entity-type := $source-model
        =>map:get("definitions")
        =>map:get($target-entity-type-name)    (: this function is only called with matching types/props :)
    let $source-properties :=
        if (exists($source-entity-type))
        then $source-entity-type=>map:get("properties")
        else ()
    let $is-missing-source :=
        (exists($source-properties) and not($target-property-name = map:keys($source-properties)))
    let $source-correlate :=
        if (exists($source-properties))
        then map:get($source-properties, $target-property-name)
        else ()
    let $target-is-array :=
        if (exists($target-property))
        then $target-property=>map:get("datatype") eq 'array'
        else false()
    let $source-is-array :=
        exists($source-correlate) and $source-correlate=>map:get("datatype") eq 'array'
    let $target-ref :=
        if (exists($target-property))
        then
            if ($target-is-array)
            then $target-property=>map:get("items")=>map:get("$ref")
            else $target-property=>map:get("$ref")
        else ()
    let $source-ref :=
        if ($source-is-array)
        then $source-correlate=>map:get("items")=>map:get("$ref")
        else
        if (exists($source-correlate))
        then $source-correlate=>map:get("$ref")
        else ()
    let $is-scalar-from-ref := empty($target-ref) and exists($source-ref)
    let $target-is-scalar-array := $target-is-array and empty($target-ref)
         and map:get($target-property, "items")=>map:contains("datatype")
    let $source-is-scalar-array := $source-is-array and empty($source-ref)
        and map:get($source-correlate, "items")=>map:contains("datatype")
    let $properties-correlate := not($target-is-scalar-array) and not($source-is-scalar-array)
    let $is-array-from-scalar := $target-is-scalar-array and not($source-is-scalar-array)
    let $is-array-from-array := $target-is-scalar-array and $source-is-scalar-array
    let $truncates-array := not($target-is-array) and $source-is-scalar-array
    let $is-scalar-from-array := empty($target-ref) and $truncates-array

    let $target-datatype := esi:resolve-datatype($target-model, $target-entity-type-name, $target-property-name)
    let $casting-function-name := es-codegen:casting-function-name($target-datatype)
    let $required-properties := if (empty(map:get($target-entity-type, "required")))
        then ()
        else json:array-values( map:get($target-entity-type, "required") )
    let $is-required := $target-property-name =
            ( map:get($target-entity-type, "primaryKey"), $required-properties )
    let $path-to-property := concat("$source-node/", $target-property-name)
    let $target-ref-name := functx:substring-after-last($target-ref, "/")
    let $source-ref-name := functx:substring-after-last($source-ref, "/")
    (: warning: property path override if source is ref and target is scalar :)
    let $path-to-property := if ($is-scalar-from-ref)
                            then $path-to-property || "/" || $source-ref-name
                            else $path-to-property
    let $wrap-if-array := function($str, $fn, $arrity-ok) {
            if ($target-is-array and $is-required)
            then concat("json:to-array(", $str, " ! ", $fn, "(.) )")
            else
            if ($target-is-array)
            then concat("es:extract-array(", $str, ", ", $fn, if ($arrity-ok) then "#1" else (), ")")
            else concat($fn, "(", $str, ")")
        }
    let $is-reference-from-scalar-or-array :=
        exists($target-ref) and empty($source-ref)

    let $extract-reference-fn :=
            concat("function($path) { json:object()",
                      "=>map:with('$type', '", $target-ref-name, "')",
                      "=>map:with('$ref', $path/", $target-ref-name, "/text() ) }")
    let $extract-scalar-fn :=
            concat("function($path) { json:object()",
                      "=>map:with('$type', '", $target-ref-name, "')",
                      "=>map:with('$ref', ", $casting-function-name, "(($path)[1]) ) }")

    let $comment :=
        if ($is-missing-source)
        then es-codegen:comment((
                "The following property was missing from the source type.",
                "The XPath will not up-convert without intervention."))
        else if ($truncates-array)
        then es-codegen:comment("Warning: potential data loss, truncated array.")
        else ""
    let $function-call-string :=
        if ($is-required)
        then "    =>   map:with("
        else "    =>es:optional("
    let $property-padding :=
        functx:pad-string-to-length("'" || $display-property-name || "',", " ", max((  (string-length($target-property-name)+4), 25) )+1 )
    let $value :=
        if ($is-scalar-from-array)
        then concat($casting-function-name, "( fn:head(", $path-to-property, ") )")
        else if (empty($target-ref))
        then $wrap-if-array($path-to-property, $casting-function-name, true())
        else if ($is-reference-from-scalar-or-array)
        then $wrap-if-array($path-to-property, $extract-scalar-fn, true())
        else $wrap-if-array($path-to-property, $extract-reference-fn, false())

    return
        fn:string-join( ($comment,
            $function-call-string,
            $property-padding,
            $value,
            ")
")
          )
};

declare function es-codegen:version-translator-generate(
    $source-model as map:map,
    $target-model as map:map
) as document-node()
{
    let $target-info := map:get($target-model, "info")
    let $target-title := map:get($target-info, "title")
    let $target-prefix := lower-case(substring($target-title,1,1)) || substring($target-title,2)
    let $target-version:= map:get($target-info, "version")
    let $target-definitions := map:get($target-model, "definitions")
    let $target-entity-type-names := map:keys($target-definitions)
    let $target-base-uri := esi:resolve-base-uri($target-info)

    let $source-info := map:get($source-model, "info")
    let $source-title := map:get($source-info, "title")
    let $source-prefix := lower-case(substring($source-title,1,1)) || substring($source-title,2)
    let $source-version:= map:get($source-info, "version")
    let $source-definitions := map:get($source-model, "definitions")
    let $source-base-uri := esi:resolve-base-uri($source-info)

    let $module-prefix := $target-prefix || "-from-" || $source-prefix
    let $module-namespace := concat(
        $target-base-uri,
        $target-title,
        "-" ,
        $target-version,
        "-from-",
        $source-title,
        "-",
        $source-version)
    let $target-info := json:object()

(: BEGIN convert instance block :)
    let $convert-instance :=
        for $entity-type-name in $target-entity-type-names
        let $info-map := json:object()
        let $_ := map:put($target-info, $entity-type-name, $info-map)
        return
    <convert-instance>
(:~
 : Creates a map:map instance representation of the target
 : entity type {$entity-type-name} from a document that
 : contains the source entity instance.
 : @param $source-node  A document or node that contains data conforming to the
 : source entity type
 : @return A map:map instance that holds the data for this entity type.
 :)
{ if (not($entity-type-name = map:keys($source-definitions)))
    then "
(: Type " || $entity-type-name || " is not in the source model.
 : XPath expressions are created as though there were no change between source and target type.
 :)"
    else () }
declare function {$module-prefix}:convert-instance-{$entity-type-name}(
    $source as node()
) as map:map
{{
    let $source-node := {$module-prefix}:init-source($source, '{$entity-type-name}')

    return
    json:object()
    =>{$module-prefix}:copy-attachments($source-node)
    (: The following line identifies the type of this instance.  Do not change it. :)
    =>map:with('$type', '{ $entity-type-name }')
    (: The following lines are generated from the '{$entity-type-name}' entity type. :)
{
    (: Begin code generation block :)
    let $entity-type := map:get($target-definitions, $entity-type-name)
    let $source-entity-type :=
            if (map:contains($source-definitions, $entity-type-name))
            then map:get($source-definitions, $entity-type-name)
            else map:put($info-map, "missing from source model.", ())
    let $ts-func :=
        function($value) {
            typeswitch($value)
            case empty-sequence() return "None"
            case json:array return fn:string-join(json:array-values($value), ", ")
            default return $value
        }
    let $compare :=
        function($property) {
            (
            $ts-func(map:get($entity-type, $property)),
            if (exists($source-entity-type) and map:contains($source-entity-type, $property))
            then "( in source: " || $ts-func(map:get($source-entity-type, $property)) || " )"
            else "( in source: None )"
            )
        }
    let $_ :=
            (
                map:put($info-map, "primaryKey: ", $compare("primaryKey")),
                map:put($info-map, "required: ", $compare("required")),
                map:put($info-map, "range indexes: ", $compare("rangeIndex")),
                map:put($info-map, "word lexicons: ", $compare("wordLexicon"))
            )
    let $properties := map:get($entity-type, "properties")
    let $values :=
        for $property-name in map:keys($properties)
        return
            es-codegen:value-for-conversion($source-model, $target-model, $entity-type-name, $property-name, $property-name)
    let $missing-properties :=
        if (exists($source-entity-type))
        then
            for $property-name in map:keys(map:get($source-entity-type, "properties"))
            where not($property-name = map:keys(map:get($entity-type, "properties")))
                return es-codegen:value-for-conversion($source-model, $target-model, $entity-type-name, $property-name, "NO TARGET")
        else ()
    return
        fn:concat(
            fn:string-join($values),
            if (exists($missing-properties))
            then
                es-codegen:comment(fn:string-join(
                    ("The following properties are in the source, but not the target:
",
                    $missing-properties)))
            else ()
            )
    (: end code generation block :)
    }
}};
    </convert-instance>
(: END convert instance block :)

    let $removed-type :=
        (: Make comments for removed ET types :)
        for $removed-entity-type-name in map:keys($source-definitions)
        let $removed-entity-type := map:get($source-definitions, $removed-entity-type-name)
        where not( $removed-entity-type-name = $target-entity-type-names)
        return
<removed-type>
(:
 Entity type {$removed-entity-type-name} is in source document
 but not in target document.
 The following XPath expressions should get values from the source
 instances but there is no specified target.
 This comment can be as a starting point for writing a custom
 version converter.

declare function {$module-prefix}:convert-instance-{$removed-entity-type-name}(
    $source-node as node()
) as map:map
{{
    json:object()
    =>map:with('$type', '{ $removed-entity-type-name }')
{

map:put($target-info, $removed-entity-type-name, map:entry("", "Removed Type")),
let $values :=
    for $removed-property-name in $removed-entity-type=>map:get("properties")=>map:keys()
    return es-codegen:extraction-for($module-prefix, $source-model, $removed-entity-type-name, $removed-property-name)
return fn:string-join($values, "
")
}
:)
</removed-type>

    return document {
<module>xquery version "1.0-ml";
module namespace {$module-prefix}
    = "{$module-namespace}";

import module namespace es = "http://marklogic.com/entity-services"
    at "/MarkLogic/entity-services/entity-services.xqy";

declare option xdmp:mapping "false";

(:
 This module was generated by MarkLogic Entity Services.
 Its purpose is to create instances of entity types
 defined in
 {$target-title || ", version " || $target-version}
 from documents that were persisted according to model
 {$source-title || ", version " || $source-version}

 Modification History:
 Generated at timestamp: {fn:current-dateTime()}
 Persisted by AUTHOR
 Date: DATE

 Target Model {$target-title || "-" || $target-version} Info:

 {
map:keys($target-info) !
    function($et) {
        let $et-report := map:get($target-info, $et)
        return
        fn:concat("Type ", $et, ":
",
            fn:string-join(
               map:keys($et-report) !
                    function($report-key) {
                        "    ",
                        $report-key,
                        fn:string-join(map:get($et-report, $report-key), ", "),
                        "
"
                    }(.)
            )
        ),
        "
"
   }(.)
}:)

{
(: this flowr makes order deterministic, which is better for dev UX. :)
for $c in $convert-instance order by $c return $c/text()}

{for $r in $removed-type order by $r return $r/text()}

declare private function {$module-prefix}:init-source(
    $source as node()*,
    $entity-type-name as xs:string
) as node()*
{{
    if ( ($source//es:instance/element()[node-name(.) eq xs:QName($entity-type-name)]))
    then $source//es:instance/element()[node-name(.) eq xs:QName($entity-type-name)]
    else $source
}};

declare private function {$module-prefix}:copy-attachments(
    $instance as json:object,
    $source as node()*
) as json:object
{{
    $instance
    =>es:optional('$attachments',
        $source ! fn:root(.)/es:envelope/es:attachments/node())
}};

</module>/text()
    }
};

(:
 Copyright 2002-2016 MarkLogic Corporation.  All Rights Reserved.

 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";

(: This module supports code generation for entity services.
 : This module has no public API.  See entity-services.xqy for
 : the public interface.
 :)
module namespace es-codegen = "http://marklogic.com/entity-services-codegen";

import module namespace esi = "http://marklogic.com/entity-services-impl"
    at "entity-services-impl.xqy";

import module namespace functx   = "http://www.functx.com" at "/MarkLogic/functx/functx-1.0-nodoc-2007-01.xqy";

declare namespace es = "http://marklogic.com/entity-services";
declare namespace tde = "http://marklogic.com/xdmp/tde";
declare namespace xq = "http://www.w3.org/2012/xquery";

declare option xdmp:mapping "false";
declare option xq:require-feature "xdmp:three-one";

declare private function es-codegen:casting-function-name(
    $datatype as xs:string
) as xs:string
{
    if ($datatype eq "iri")
    then "sem:iri"
    else "xs:" || $datatype
};

declare private function es-codegen:comment(
    $comment-text as xs:string*
) as xs:string
{
    concat('    (: ', string-join($comment-text, "
       "), '  :)
')
};

declare private function es-codegen:extraction-for(
    $prefix as xs:string,
    $model as map:map,
    $entity-type-name as xs:string,
    $property-name as xs:string
) as xs:string
{
    let $entity-type := $model=>map:get("definitions")=>map:get($entity-type-name)
    let $properties := map:get($entity-type, "properties")
    let $required-properties := if (empty(map:get($entity-type, "required")))
        then ()
        else json:array-values( map:get($entity-type, "required") )
    let $is-required := $property-name =
            ( map:get($entity-type, "primaryKey"), $required-properties )
    let $is-array :=
            map:get(map:get($properties, $property-name), "datatype")
            eq "array"
    let $property-datatype := esi:resolve-datatype($model, $entity-type-name, $property-name)
    let $casting-function-name := es-codegen:casting-function-name($property-datatype)
    let $wrap-if-array := function($str, $fn, $arrity-ok) {
            if ($is-array and $is-required)
            then concat("json:to-array(", $str, " ! ", $fn, "(.) )")
            else
            if ($is-array)
            then concat("es:extract-array(", $str, ", ", $fn, if ($arrity-ok) then "#1" else (), ")")
            else concat($fn, "(", $str, ")")
        }
    let $ref :=
        if ($is-array)
        then $properties=>map:get($property-name)=>map:get("items")=>map:get("$ref")
        else $properties=>map:get($property-name)=>map:get("$ref")
    let $path-to-property := concat("$source-node/", $property-name)
    let $property-comment :=
        if (empty($ref))
        then ""
        else if (contains($ref, "#/definitions"))
        then es-codegen:comment("The following property is a local reference.")
        else
        es-codegen:comment((
            'The following property assigment comes from an external reference.',
            'Its generated value probably requires developer attention.'))
    let $ref-name := functx:substring-after-last($ref, "/")
    let $extract-reference-fn :=
            concat("function($path) { json:object()",
                      "    =>map:with('$type', '", $ref-name, "')",
                      "    =>map:with('$ref', $path/", $ref-name, "/text() ) }")
    let $value :=
        if (empty($ref))
        then
            $wrap-if-array($path-to-property, $casting-function-name, true())
        else
            if (contains($ref, "#/definitions"))
            then
                $wrap-if-array($path-to-property, concat($prefix, ":extract-instance-", $ref-name), true())
            else
                $wrap-if-array($path-to-property, $extract-reference-fn, false())
    (: if a property is required, use map:with to force inclusion :)
    let $function-call-string :=
        if ($is-required)
        then "    =>   map:with("
        else "    =>es:optional("
    return
        fn:concat( $property-comment,
               $function-call-string,
               functx:pad-string-to-length("'" || $property-name || "',", " ", max((  (string-length($property-name)+4), 25) )+1 ),
               $value,
               ")")
};

declare function es-codegen:instance-converter-generate(
    $model as map:map
) as document-node()
{
    let $info := map:get($model, "info")
    let $title := map:get($info, "title")
    let $prefix := lower-case(substring($title,1,1)) || substring($title,2)
    let $version:= map:get($info, "version")
    let $base-uri := esi:resolve-base-uri($info)
    return
document {
<module>xquery version "1.0-ml";

(:
 This module was generated by MarkLogic Entity Services.
 The source entity type document was {$title}-{$version}
 To use this module, examine how you wish to extract data from sources,
 and modify the various extract-instance-{{X}} functions to
 create the instances you wish.

 You may wish to/need to alter
 1.  values.  For example, creating duration values from decimal months.
 2.  references.  This conversion module assumes you want to denormalize
     instances when storing them in documents.  You may choose to remove
     code that denormalizes, and just include reference values in your
     instances instead.
 3.  Source XPath expressions.  The data coming into the extract-instance-{{X}}
     functions will probably not be exactly what this module predicts.

 After modifying this file, put it in your project for deployment to the modules
 database of your application, and check it into your source control system.

 Modification History:
 Generated at timestamp: {fn:current-dateTime()}
 Persisted by AUTHOR
 Date: DATE
 :)

module namespace {$prefix}
    = "{$base-uri}{$title}-{$version}";

import module namespace es = "http://marklogic.com/entity-services"
    at "/MarkLogic/entity-services/entity-services.xqy";

declare option xdmp:mapping "false";

(:
  extract-instance-{{entity-type}} functions

  These functions together take a source document and create a nested
  map structure from it.
  The resulting map is used by instance-to-canonical-xml to create documents
  in the database.

  It is expected that an implementer will edit at least XPath expressions in
  the extraction functions.  It is less likely that you will want to edit
  the instance-to-canonical-xml or envelope functions.
 :)

{
    for $entity-type-name in map:keys(map:get($model, "definitions"))
    return
    <extract-instance>
(:~
 : Creates a map:map instance from some source document.
 : @param $source-node  A document or node that contains
 :   data for populating a {$entity-type-name}
 : @return A map:map instance with extracted data and
 :   metadata about the instance.
 :)
declare function {$prefix}:extract-instance-{$entity-type-name}(
    $source as node()?
) as map:map
{{
    let $source-node := {$prefix}:init-source($source, '{$entity-type-name}')

    let $instance := json:object()
    (: If desired, add the original source document as an attachment. :)
        =>{$prefix}:add-attachments($source-node, $source)
    (: Add type information to the entity instance (required, do not change) :)
        =>map:with('$type', '{ $entity-type-name }')

    return

    (: The following logic may not be required for every extraction       :)
    (: if this $source-node has no child elements, it has a reference key :)
    if (empty($source-node/*))
    then $instance=>map:with('$ref', $source-node/text())
    (: Otherwise, this source node contains instance data. Populate it. :)
    else

    (:
    The following code populates the properties of the '{$entity-type-name}'
    entity type. Ensure that all of the property paths are correct for your
    source data.  The general pattern is
    =>map:with('keyName', casting-function($source-node/path/to/data))
    but you may also wish to convert values
    =>map:with('dateKeyName',
          xdmp:parse-dateTime("[Y0001]-[M01]-[D01]T[h01]:[m01]:[s01].[f1][Z]",
          $source-node/path/to/data/in/the/source))
    You can also implement lookup functions,
    =>map:with('lookupKey',
          cts:search( collection('customers'),
              string($source-node/path/to/lookup/key))/id
    or populate the instance with constants.
    =>map:with('constantValue', 10)
    The output of this function should structurally match the output of
    es:model-get-test-instances($model)
    :)

    $instance
{
    (: Begin code generation block :)
    let $entity-type := $model=>map:get("definitions")=>map:get($entity-type-name)
    let $properties := map:get($entity-type, "properties")
    let $value-lines :=
        for $property-name in map:keys($properties)
        return es-codegen:extraction-for($prefix, $model, $entity-type-name, $property-name)
    return fn:string-join($value-lines, "
")
        (: end code generation block :)
    }
}};
</extract-instance>/text()
}

(:~
 : Turns an entity instance into an XML structure.
 : This out-of-the box implementation traverses a map structure
 : and turns it deterministically into an XML tree.
 : Using this function as-is should be sufficient for most use
 : cases, and will play well with other generated artifacts.
 : @param $entity-instance A map:map instance returned from one of the extract-instance
 :    functions.
 : @return An XML element that encodes the instance.
 :)
declare function {$prefix}:instance-to-canonical-xml(
    $entity-instance as map:map
) as element()
{{
    (: Construct an element that is named the same as the Entity Type :)
    element {{ map:get($entity-instance, "$type") }}  {{
        if ( map:contains($entity-instance, "$ref") )
        then map:get($entity-instance, "$ref")
        else
            for $key in map:keys($entity-instance)
            let $instance-property := map:get($entity-instance, $key)
            where ($key castable as xs:NCName)
            return
                typeswitch ($instance-property)
                (: This branch handles embedded objects.  You can choose to prune
                   an entity's representation of extend it with lookups here. :)
                case json:object+
                    return
                        for $prop in $instance-property
                        return element {{ $key }} {{ {$prefix}:instance-to-canonical-xml($prop) }}
                (: An array can also treated as multiple elements :)
                case json:array
                    return
                        for $val in json:array-values($instance-property)
                        return
                            if ($val instance of json:object)
                            then element {{ $key }} {{
                                attribute datatype {{ "array" }},
                                {$prefix}:instance-to-canonical-xml($val) }}
                            else element {{ $key }} {{
                                attribute datatype {{ "array" }},
                                $val }}
                (: A sequence of values should be simply treated as multiple elements :)
                case item()+
                    return
                        for $val in $instance-property
                        return element {{ $key }} {{ $val }}
                default return element {{ $key }} {{ $instance-property }}
    }}
}};

(:
 : Wraps a canonical instance (returned by instance-to-canonical-xml())
 : within an envelope patterned document, along with the source
 : document, which is stored in an attachments section.
 : @param $entity-instance an instance, as returned by an extract-instance
 : function
 : @return A document which wraps both the canonical instance and source docs.
 :)
declare function {$prefix}:instance-to-envelope(
    $entity-instance as map:map
) as document-node()
{{
    document {{
        element es:envelope {{
            element es:instance {{
                element es:info {{
                    element es:title {{ map:get($entity-instance,'$type') }},
                    element es:version {{ "{$version}" }}
                }},
                {$prefix}:instance-to-canonical-xml($entity-instance)
            }},
            element es:attachments {{
                map:get($entity-instance, "$attachments")
            }}
        }}
    }}
}};

declare private function {$prefix}:init-source(
    $source as node()*,
    $entity-type-name as xs:string
) as node()*
{{
    if ( ($source instance of document-node())
        or (exists
            ($source/element()[fn:node-name(.) eq xs:QName($entity-type-name)] )))
    then $source/node()
    else $source
}};

declare private function {$prefix}:add-attachments(
    $instance as json:object,
    $source-node as node()*,
    $source as node()*
) as json:object
{{
    $instance
    =>map:with('$attachments',
        typeswitch($source-node)
        case object-node() return xdmp:quote($source)
        case array-node() return xdmp:quote($source)
        default return $source)
}};

</module>/text()
}

};

declare private function es-codegen:value-for-conversion(
    $source-model as map:map,
    $target-model as map:map,
    $target-entity-type-name as xs:string,
    $target-property-name as xs:string,
    $display-property-name as xs:string
) as xs:string
{
    let $target-entity-type := $target-model
        =>map:get("definitions")
        =>map:get($target-entity-type-name)
    let $target-property := $target-entity-type
        =>map:get("properties")
        =>map:get($target-property-name)
    let $source-entity-type := $source-model
        =>map:get("definitions")
        =>map:get($target-entity-type-name)    (: this function is only called with matching types/props :)
    let $source-properties :=
        if (exists($source-entity-type))
        then $source-entity-type=>map:get("properties")
        else ()
    let $is-missing-source :=
        (exists($source-properties) and ($target-property-name = map:keys($source-properties)))
    let $source-correlate :=
        if (exists($source-properties))
        then map:get($source-properties, $target-property-name)
        else ()
    let $target-is-array :=
        if (exists($target-property))
        then $target-property=>map:get("datatype") eq 'array'
        else false()
    let $source-is-array :=
        exists($source-correlate) and $source-correlate=>map:get("datatype") eq 'array'
    let $target-ref :=
        if (exists($target-property))
        then
            if ($target-is-array)
            then $target-property=>map:get("items")=>map:get("$ref")
            else $target-property=>map:get("$ref")
        else ()
    let $source-ref :=
        if ($source-is-array)
        then $source-correlate=>map:get("items")=>map:get("$ref")
        else
        if (exists($source-correlate))
        then $source-correlate=>map:get("$ref")
        else ()
    let $is-scalar-from-ref := empty($target-ref) and exists($source-ref)
    let $target-is-scalar-array := $target-is-array and empty($target-ref)
         and map:get($target-property, "items")=>map:contains("datatype")
    let $source-is-scalar-array := $source-is-array and empty($source-ref)
        and map:get($source-correlate, "items")=>map:contains("datatype")
    let $properties-correlate := not($target-is-scalar-array) and not($source-is-scalar-array)
    let $is-array-from-scalar := $target-is-scalar-array and not($source-is-scalar-array)
    let $is-array-from-array := $target-is-scalar-array and $source-is-scalar-array
    let $truncates-array := not($target-is-array) and $source-is-scalar-array
    let $is-scalar-from-array := empty($target-ref) and $truncates-array

    let $target-datatype := esi:resolve-datatype($target-model, $target-entity-type-name, $target-property-name)
    let $casting-function-name := es-codegen:casting-function-name($target-datatype)
    let $required-properties := if (empty(map:get($target-entity-type, "required")))
        then ()
        else json:array-values( map:get($target-entity-type, "required") )
    let $is-required := $target-property-name =
            ( map:get($target-entity-type, "primaryKey"), $required-properties )
    let $path-to-property := concat("$source-node/", $target-property-name)
    let $target-ref-name := functx:substring-after-last($target-ref, "/")
    let $source-ref-name := functx:substring-after-last($source-ref, "/")
    (: warning: property path override if source is ref and target is scalar :)
    let $path-to-property := if ($is-scalar-from-ref)
                            then $path-to-property || "/" || $source-ref-name
                            else $path-to-property
    let $wrap-if-array := function($str, $fn, $arrity-ok) {
            if ($target-is-array and $is-required)
            then concat("json:to-array(", $str, " ! ", $fn, "(.) )")
            else
            if ($target-is-array)
            then concat("es:extract-array(", $str, ", ", $fn, if ($arrity-ok) then "#1" else (), ")")
            else concat($fn, "(", $str, ")")
        }
    let $is-reference-from-scalar-or-array :=
        exists($target-ref) and empty($source-ref)

    let $extract-reference-fn :=
            concat("function($path) { json:object()",
                      "=>map:with('$type', '", $target-ref-name, "')",
                      "=>map:with('$ref', $path/", $target-ref-name, "/text() ) }")
    let $extract-scalar-fn :=
            concat("function($path) { json:object()",
                      "=>map:with('$type', '", $target-ref-name, "')",
                      "=>map:with('$ref', ", $casting-function-name, "(($path)[1]) ) }")

    let $comment :=
        if ($is-missing-source)
        then es-codegen:comment((
                "The following property was missing from the source type.",
                "The XPath will not up-convert without intervention."))
        else if ($truncates-array)
        then es-codegen:comment("Warning: potential data loss, truncated array.")
        else ""
    let $function-call-string :=
        if ($is-required)
        then "    =>   map:with("
        else "    =>es:optional("
    let $property-padding :=
        functx:pad-string-to-length("'" || $display-property-name || "',", " ", max((  (string-length($target-property-name)+4), 25) )+1 )
    let $value :=
        if ($is-scalar-from-array)
        then concat($casting-function-name, "( fn:head(", $path-to-property, ") )")
        else if (empty($target-ref))
        then $wrap-if-array($path-to-property, $casting-function-name, true())
        else if ($is-reference-from-scalar-or-array)
        then $wrap-if-array($path-to-property, $extract-scalar-fn, true())
        else $wrap-if-array($path-to-property, $extract-reference-fn, false())

    return
        fn:string-join( ($comment,
            $function-call-string,
            $property-padding,
            $value,
            ")
")
          )
};

declare function es-codegen:version-translator-generate(
    $source-model as map:map,
    $target-model as map:map
) as document-node()
{
    let $target-info := map:get($target-model, "info")
    let $target-title := map:get($target-info, "title")
    let $target-prefix := lower-case(substring($target-title,1,1)) || substring($target-title,2)
    let $target-version:= map:get($target-info, "version")
    let $target-definitions := map:get($target-model, "definitions")
    let $target-entity-type-names := map:keys($target-definitions)
    let $target-base-uri := esi:resolve-base-uri($target-info)

    let $source-info := map:get($source-model, "info")
    let $source-title := map:get($source-info, "title")
    let $source-prefix := lower-case(substring($source-title,1,1)) || substring($source-title,2)
    let $source-version:= map:get($source-info, "version")
    let $source-definitions := map:get($source-model, "definitions")
    let $source-base-uri := esi:resolve-base-uri($source-info)

    let $module-prefix := $target-prefix || "-from-" || $source-prefix
    let $module-namespace := concat(
        $target-base-uri,
        $target-title,
        "-" ,
        $target-version,
        "-from-",
        $source-title,
        "-",
        $source-version)
    let $target-info := json:object()

(: BEGIN convert instance block :)
    let $convert-instance :=
        for $entity-type-name in $target-entity-type-names
        let $info-map := json:object()
        let $_ := map:put($target-info, $entity-type-name, $info-map)
        return
    <convert-instance>
(:~
 : Creates a map:map instance representation of the target
 : entity type {$entity-type-name} from a document that
 : contains the source entity instance.
 : @param $source-node  A document or node that contains data conforming to the
 : source entity type
 : @return A map:map instance that holds the data for this entity type.
 :)
{ if (not($entity-type-name = map:keys($source-definitions)))
    then "
(: Type " || $entity-type-name || "is not in the source model.
 : XPath expressions are created as though there were no change between source and target type.
 :)"
    else () }
declare function {$module-prefix}:convert-instance-{$entity-type-name}(
    $source as node()
) as map:map
{{
    let $source-node := {$module-prefix}:init-source($source, '{$entity-type-name}')

    return
    json:object()
    =>{$module-prefix}:copy-attachments($source-node)
    (: The following line identifies the type of this instance.  Do not change it. :)
    =>map:with('$type', '{ $entity-type-name }')
    (: The following lines are generated from the '{$entity-type-name}' entity type. :)
{
    (: Begin code generation block :)
    let $entity-type := map:get($target-definitions, $entity-type-name)
    let $source-entity-type :=
            if (map:contains($source-definitions, $entity-type-name))
            then map:get($source-definitions, $entity-type-name)
            else map:put($info-map, "missing from source model.", ())
    let $ts-func :=
        function($value) {
            typeswitch($value)
            case empty-sequence() return "None"
            case json:array return fn:string-join(json:array-values($value), ", ")
            default return $value
        }
    let $compare :=
        function($property) {
            (
            $ts-func(map:get($entity-type, $property)),
            if (exists($source-entity-type) and map:contains($source-entity-type, $property))
            then "( in source: " || $ts-func(map:get($source-entity-type, $property)) || " )"
            else "( in source: None )"
            )
        }
    let $_ :=
            (
                map:put($info-map, "primaryKey: ", $compare("primaryKey")),
                map:put($info-map, "required: ", $compare("required")),
                map:put($info-map, "range indexes: ", $compare("rangeIndex")),
                map:put($info-map, "word lexicons: ", $compare("wordLexicon"))
            )
    let $properties := map:get($entity-type, "properties")
    let $values :=
        for $property-name in map:keys($properties)
        return
            es-codegen:value-for-conversion($source-model, $target-model, $entity-type-name, $property-name, $property-name)
    let $missing-properties :=
        if (exists($source-entity-type))
        then
            for $property-name in map:keys(map:get($source-entity-type, "properties"))
            where not($property-name = map:keys(map:get($entity-type, "properties")))
                return es-codegen:value-for-conversion($source-model, $target-model, $entity-type-name, $property-name, "NO TARGET")
        else ()
    return
        fn:concat(
            fn:string-join($values),
            if (exists($missing-properties))
            then
                es-codegen:comment(fn:string-join(
                    ("The following properties are in the source, but not the target:
",
                    $missing-properties)))
            else ()
            )
    (: end code generation block :)
    }
}};
    </convert-instance>
(: END convert instance block :)

    let $removed-type :=
        (: Make comments for removed ET types :)
        for $removed-entity-type-name in map:keys($source-definitions)
        let $removed-entity-type := map:get($source-definitions, $removed-entity-type-name)
        where not( $removed-entity-type-name = $target-entity-type-names)
        return
<removed-type>
(:
 Entity type {$removed-entity-type-name} is in source document
 but not in target document.
 The following XPath expressions should get values from the source
 instances but there is no specified target.
 This comment can be as a starting point for writing a custom
 version converter.

declare function {$module-prefix}:convert-instance-{$removed-entity-type-name}(
    $source-node as node()
) as map:map
{{
    json:object()
    =>map:with('$type', '{ $removed-entity-type-name }')
{

map:put($target-info, $removed-entity-type-name, map:entry("", "Removed Type")),
let $values :=
    for $removed-property-name in $removed-entity-type=>map:get("properties")=>map:keys()
    return es-codegen:extraction-for($module-prefix, $source-model, $removed-entity-type-name, $removed-property-name)
return fn:string-join($values, "
")
}
:)
</removed-type>

    return document {
<module>xquery version "1.0-ml";
module namespace {$module-prefix}
    = "{$module-namespace}";

import module namespace es = "http://marklogic.com/entity-services"
    at "/MarkLogic/entity-services/entity-services.xqy";

declare option xdmp:mapping "false";

(:
 This module was generated by MarkLogic Entity Services.
 Its purpose is to create instances of entity types
 defined in
 {$target-title || ", version " || $target-version}
 from documents that were persisted according to model
 {$source-title || ", version " || $source-version}

 Modification History:
 Generated at timestamp: {fn:current-dateTime()}
 Persisted by AUTHOR
 Date: DATE

 Target Model {$target-title || "-" || $target-version} Info:

 {
map:keys($target-info) !
    function($et) {
        let $et-report := map:get($target-info, $et)
        return
        fn:concat("Type ", $et, ":
",
            fn:string-join(
               map:keys($et-report) !
                    function($report-key) {
                        "    ",
                        $report-key,
                        fn:string-join(map:get($et-report, $report-key), ", "),
                        "
"
                    }(.)
            )
        ),
        "
"
   }(.)
}:)

{
(: this flowr makes order deterministic, which is better for dev UX. :)
for $c in $convert-instance order by $c return $c/text()}

{for $r in $removed-type order by $r return $r/text()}

declare private function {$module-prefix}:init-source(
    $source as node()*,
    $entity-type-name as xs:string
) as node()*
{{
    if ( ($source//es:instance/element()[node-name(.) eq xs:QName($entity-type-name)]))
    then $source//es:instance/element()[node-name(.) eq xs:QName($entity-type-name)]
    else $source
}};

declare private function {$module-prefix}:copy-attachments(
    $instance as json:object,
    $source as node()*
) as json:object
{{
    $instance
    =>es:optional('$attachments',
        $source ! fn:root(.)/es:envelope/es:attachments/node())
}};

</module>/text()
    }
};


[FIX] DBA: Database list, backups. Closes #1956

(:~
 : Main page.
 :
 : @author Christian Grün, BaseX Team 2005-20, BSD License
 :)
module namespace dba = 'dba/databases';

import module namespace html = 'dba/html' at '../lib/html.xqm';
import module namespace util = 'dba/util' at '../lib/util.xqm';

(:~ Top category :)
declare variable $dba:CAT := 'databases';

(:~ Regular expression for backups. :)
declare variable $dba:BACKUP-REGEX := '^(.*)-(\d{4}-\d\d-\d\d)-(\d\d)-(\d\d)-(\d\d)$';

(:~
 : Main page.
 : @param  $sort   table sort key
 : @param  $error  error string
 : @param  $info   info string
 : @param  $page   current page
 : @return page
 :)
declare
  %rest:GET
  %rest:path('/dba/databases')
  %rest:query-param('sort',  '{$sort}', '')
  %rest:query-param('page',  '{$page}', 1)
  %rest:query-param('info',  '{$info}')
  %rest:query-param('error', '{$error}')
  %output:method('html')
function dba:databases(
  $sort   as xs:string,
  $page   as xs:integer,
  $info   as xs:string?,
  $error  as xs:string?
) as element(html) {
  let $names := map:merge(db:list() ! map:entry(., true()))
  let $databases :=
    let $start := util:start($page, $sort)
    let $end := util:end($page, $sort)
    for $db in db:list-details()[position() = $start to $end]
    return map {
      'name': $db,
      'resources': $db/@resources,
      'size': $db/@size,
      'date': $db/@modified-date
    }
  let $backups :=
    for $backup in db:backups()
    where matches($backup, $dba:BACKUP-REGEX)
    group by $name := replace($backup, $dba:BACKUP-REGEX, '$1')
    where not($names($name))
    let $date := replace(sort($backup)[last()], $dba:BACKUP-REGEX, '$2T$3:$4:$5Z')
    return map {
      'name': $name,
      'size': (),
      'date': $date
    }

  return html:wrap(map { 'header': $dba:CAT, 'info': $info, 'error': $error },

        <form action='{ $dba:CAT }' method='post' class='update'>

Databases

          {
            let $headers := (
              map { 'key': 'name', 'label': 'Name' },
              map { 'key': 'resources', 'label': 'Count', 'type': 'number', 'order': 'desc' },
              map { 'key': 'size', 'label': 'Bytes', 'type': 'bytes', 'order': 'desc' },
              map { 'key': 'date', 'label': 'Last Modified', 'type': 'dateTime', 'order': 'desc' }
            )
            let $entries := ($databases, $backups)
            let $buttons := (
              html:button('db-create', 'Create…'),
              html:button('db-optimize-all', 'Optimize'),
              html:button('db-drop', 'Drop', true())
            )
            let $count := map:size($names) + count($backups)
            let $options := map {
              'sort': $sort,
              'link': 'database',
              'page': $page,
              'count': $count
            }
            return html:table($headers, $entries, $buttons, map { }, $options)
          }
        </form>

  )
};

(:~
 : Redirects to the specified action.
 : @param  $action  action to perform
 : @param  $names   names of selected databases
 : @return redirection
 :)
declare
  %rest:POST
  %rest:path('/dba/databases')
  %rest:query-param('action', '{$action}')
  %rest:query-param('name',   '{$names}')
function dba:databases-redirect(
  $action  as xs:string,
  $names   as xs:string*
) as element(rest:response) {
  web:redirect($action,
    if($action = 'create-db') then map { }
    else map { 'name': $names, 'redirect': $dba:CAT }
  )
};

(:~
 : Main page.
 :
 : @author Christian Grün, BaseX Team 2005-20, BSD License
 :)
module namespace dba = 'dba/databases';

import module namespace html = 'dba/html' at '../lib/html.xqm';
import module namespace util = 'dba/util' at '../lib/util.xqm';

(:~ Top category :)
declare variable $dba:CAT := 'databases';

(:~ Regular expression for backups. :)
declare variable $dba:BACKUP-REGEX := '^(.*)-(\d{4}-\d\d-\d\d)-(\d\d)-(\d\d)-(\d\d)$';

(:~
 : Main page.
 : @param  $sort   table sort key
 : @param  $error  error string
 : @param  $info   info string
 : @param  $page   current page
 : @return page
 :)
declare
  %rest:GET
  %rest:path('/dba/databases')
  %rest:query-param('sort',  '{$sort}', '')
  %rest:query-param('page',  '{$page}', 1)
  %rest:query-param('info',  '{$info}')
  %rest:query-param('error', '{$error}')
  %output:method('html')
function dba:databases(
  $sort   as xs:string,
  $page   as xs:integer,
  $info   as xs:string?,
  $error  as xs:string?
) as element(html) {
  let $names := map:merge(db:list() ! map:entry(., true()))
  let $databases :=
    let $start := util:start($page, $sort)
    let $end := util:end($page, $sort)
    for $db in db:list-details()[position() = $start to $end]
    return map {
      'name': $db,
      'resources': $db/@resources,
      'size': $db/@size,
      'date': $db/@modified-date
    }
  let $backups :=
    for $backup in db:backups()
    where matches($backup, $dba:BACKUP-REGEX)
    group by $name := replace($backup, $dba:BACKUP-REGEX, '$1')
    where not($names($name))
    let $date := replace(sort($backup)[last()], $dba:BACKUP-REGEX, '$2T$3:$4:$5Z')
    return map {
      'name': $name,
      'size': '(backup)',
      'date': $date
    }

  return html:wrap(map { 'header': $dba:CAT, 'info': $info, 'error': $error },

        <form action='{ $dba:CAT }' method='post' class='update'>

Databases

          {
            let $headers := (
              map { 'key': 'name', 'label': 'Name' },
              map { 'key': 'resources', 'label': 'Count', 'type': 'number', 'order': 'desc' },
              map { 'key': 'size', 'label': 'Bytes', 'type': 'bytes', 'order': 'desc' },
              map { 'key': 'date', 'label': 'Last Modified', 'type': 'dateTime', 'order': 'desc' }
            )
            let $entries := ($databases, $backups)
            let $buttons := (
              html:button('db-create', 'Create…'),
              html:button('db-optimize-all', 'Optimize'),
              html:button('db-drop', 'Drop', true())
            )
            let $count := map:size($names) + count($backups)
            let $options := map {
              'sort': $sort,
              'link': 'database',
              'page': $page,
              'count': $count
            }
            return html:table($headers, $entries, $buttons, map { }, $options)
          }
        </form>

  )
};

(:~
 : Redirects to the specified action.
 : @param  $action  action to perform
 : @param  $names   names of selected databases
 : @return redirection
 :)
declare
  %rest:POST
  %rest:path('/dba/databases')
  %rest:query-param('action', '{$action}')
  %rest:query-param('name',   '{$names}')
function dba:databases-redirect(
  $action  as xs:string,
  $names   as xs:string*
) as element(rest:response) {
  web:redirect($action,
    if($action = 'create-db') then map { }
    else map { 'name': $names, 'redirect': $dba:CAT }
  )
};


[ignore] Changing the news feed box on the front page to retrieve the news from the new wiki.

svn path=/trunk/eXist/; revision=16258

xquery version "1.0";

declare namespace atom="http://www.w3.org/2005/Atom";
declare namespace wiki="http://exist-db.org/xquery/wiki";

declare option exist:serialize "method=text media-type=application/json";

declare variable $atom:server := "http://exist-db.org";
declare variable $atom:uri := concat($atom:server, "/apps/wiki/atom/blogs/eXist/");
declare variable $atom:params := "?count=4";
declare variable $atom:entry := concat($atom:server, "/apps/wiki/blogs/eXist/");

declare function atom:format-entry($feed as element()) {

    {
        for $entry in subsequence($feed//atom:entry, 1, 4)
        let $link := concat($atom:entry, $entry/wiki:id)
        return

                    {substring($entry/atom:published, 1, 10)}

                    {$entry/atom:title/text()}

    }

};

let $uri := xs:anyURI(concat($atom:uri, $atom:params))
let $response := doc($uri)
let $output :=
    util:serialize(atom:format-entry($response/atom:feed), "indent=no")
return
	concat("atomCallback('", $output, "');")

xquery version "1.0";

declare namespace atom="http://www.w3.org/2005/Atom";

declare option exist:serialize "method=text media-type=application/json";

declare variable $atom:server := "http://atomic.exist-db.org";
declare variable $atom:uri := concat($atom:server, "/atom/summary/wiki/blogs/eXist/");

declare function atom:format-entry($feed as element()) {

    {
        for $entry in subsequence($feed//atom:entry, 1, 4)
        let $link := $entry/atom:link[@rel = 'alternate'][@type = 'text/html']/@href
        return

                    {substring($entry/atom:published, 1, 10)}

                    {$entry/atom:title/text()}

    }

};

let $uri := xs:anyURI($atom:uri)
let $response := doc($uri)
let $output :=
    util:serialize(atom:format-entry($response/atom:feed), "indent=no")
return
	concat("atomCallback('", $output, "');")


Changed additional uses of "XQuery" and "program"; a bit of doc cleanup.

xquery version "3.0";
(:
 : Copyright 2011 The FLWOR Foundation.
 :
 : 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.
:)

(:~
 : This module contains functions to compile and evaluate queries
 : written in either JSONiq or XQuery. Also, it contains function that
 : allow to parameterize the static or dynamic evaluation phase.
 :
 : @author Juan Zacarias
 : @project Zorba/Programming Languages/ZQ
 :)
module namespace zq = 'http://zorba.io/modules/zorba-query';

declare namespace an = "http://zorba.io/annotations";
declare namespace ver = "http://zorba.io/options/versioning";
declare namespace op = "http://zorba.io/options/features";
declare namespace f = "http://zorba.io/features";

declare option ver:module-version "1.0";

(:~
 :
The function prepares a given a query for execution.

 :
If the query was successfully compiled, the function returns an
 : identifier as xs:anyURI. This URI can be passed to other functions
 : of this module (e.g. to actually evaluate the query). The URI
 : is opaque and its lifetime is bound by the lifetime of the query
 : that invoked this function. Further reference or uses
 : of the identifier lead to unexpected results.

 :

 :
Successfully prepared queries need to be deleted by passing the resulting
 : identifier to the zq:delete-query function of this module.

 :
 : @param $main-module-text the query that should be prepared.
 :   The query needs to be a XQuery or JSONiq main module.
 :
 : @return an identifier for the compiled query that can be passed
 :   as arguments to other functions of this module.
 :
 : @error any (static or type) error that may be raised during the compilation
 : of the query. For example, err:XPST0003 if the given query could
 : not be parsed.
 :)
declare %an:sequential function zq:prepare-main-module($main-module-text as xs:string) as
  xs:anyURI external;

(:~
 :
The function prepares a given query for execution.

 :
If the query was successfully compiled, the function returns an
 : identifier as xs:anyURI. This URI can be passed to other functions
 : of this module (e.g. to actually evaluate the query). The URI
 : is opaque and its lifetime is bound by the lifetime of the query
 : that invoked this function. Further reference or uses
 : of the identifier lead to unexpected results.

 :

 :
Important notes regarding the second and third parameters of the function:

 :
--------------------------------------------------------------------------

 :

 :
These parameters allow you to specify a URL resolver and a URI mapper
 : for Zorba to use when executing this query. See
 : here

 :

 :
The second parameter is a function item for a URL
 : resolver. The URL resolver function must recive 2 parameters:
 :
A $namespace as xs:string that will contain the url to be resolved.

 :
A $entity as xs:string that will contain the type of resolving needed.
 :   This can be one of two values: "module" or "schema".

 :

 :
The function must return the empty sequence when the specified $namespace
 : or $entity are not the ones to be resolved.

 :

 :
Example:

 :

 : declare function mymod:url-resolver($namespace as xs:string, $entity as xs:string) as item()?
 : {
 :  if($namespace = 'http://test.xq')
 :  then "module namespace test = 'http://test'; declare function test:foo(){'foo'};"
 :  else ()
 : };
 :

 :
The URL resolver function's namespace, name, and parameter naming are
 : not restricted by ZQ.

 :

 :
The URL resolver function's return type is not restricted, it could be a string, a sequence,
 : a node, etc. All the outputs types are to be serialized as a string.

 :

 :
The third parameter is a function item for a URI mapper.

 :
The URI mapper function, just like the URL resolver, receives 2 parameters:
 :
A $namespace as xs:string that will contain the URI to be mapped.

 :
A $entity as xs:string that will contain the type of resolving needed.
 :   This can be one of two values: "module" or "schema".

 :

 :
The URI mapper must return an empty sequence when the specified $namesapce or $entity
 : are not to be mapped. Unlike the URL resolver this function must return a sequence of strings.

 :

 :
Example:

 :

 : declare function mymod:uri-mapper($namespace as xs:string, $entity as xs:string)
 : {
 :  if($namespace = 'http://test')
 :  then ("http://zorba.io/test", "http://foo.com/schema/test")
 :  else ()
 : };
 :

 :
The URI mapper function's namespace, name, and parameter naming are
 : not restricted by ZQ.

 :

 :
In order to pass the above URL resolver and URI mapper to this function,
 : use the following syntax:

 :

 :   variable $queryID := zq:prepare-main-module("..query text..",
 :      mymod:url-resolver#2, mymod:uri-mapper#2);
 :

 :
That is, the QName of the function followed by "#2". This is XQuery
 : "higher-order function" syntax, meaning the function with the specified
 : QName which takes two arguments. Since URL resolvers and URI mappers
 : must take two arguments, both will always be specified with "#2".

 :

 :
Both the URL resolver and URI mapper functions are optional, meaning you
 : may pass the empty-sequence () for either.

 :

 :
Successfully prepared queries need to be deleted by passing the resulting
 : identifier to the zq:delete-query function of this module.

 :
 : @param $main-module-text the query that should be prepared.
 :   The query needs to be a XQuery or JSONiq main module.
 :
 : @param $resolver the URL resolver function.
 :
 : @param $mapper the URI mapper function.
 :
 : @return an identifier for the compiled query that can be passed
 :   as arguments to other functions of this module.
 :
 : @error any (static or type) error that may be raised during the compilation
 : of the query. For example, err:XPST0003 if the given query could
 : not be parsed.
 :)
declare %an:sequential function zq:prepare-main-module(
  $main-module-text as xs:string,
  $resolver as ( function(xs:string, xs:string) as item()? )?,
  $mapper as ( function(xs:string, xs:string) as xs:string* )? ) as
    xs:anyURI external;

(:~
 :
This function compiles a given XQuery or JSONiq library module.
 : It can be used to compile-check a module.

 :
 : @param $library-module-text the library module that should
 :  be prepared.
 :
 : @return the empty-sequence.
 :
 : @error any (static or type) error that may be raised during the compilation
 : of the library module. For example, err:XPST0003 if the given library
 : module could not be parsed.
 :)
declare %an:sequential function zq:prepare-library-module($library-module-text as xs:string) as
  empty-sequence() external ;

(:~
 :
The function tests if the context-item is bound for the
 : execution of the query referred to by the given query identifier.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return true if the context-item is bound, false otherwise.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :)
declare function zq:is-bound-context-item($query-key as xs:anyURI)
  as xs:boolean  external;

(:~
 :
The function tests if the given variable is bound for the
 : execution of the query referred to by the given query identifier.

 :
 : @param $query-key the identifier for a compiled query
 : @param $var-name the name of the variable
 :
 : @return true if the variable is bound, false otherwise.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 : @error zq:UNDECLARED_VARIABLE if the given variable is not declared
 :   in the query.
 :)
declare function zq:is-bound-variable($query-key as xs:anyURI, $var-name as
  xs:QName) as xs:boolean  external;

(:~
 :
The function returns the names of the external variables that
 : are declared in the given query (either in the main module or
 : in any of the imported library modules).

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return the sequence of names of the said external variables.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :)
declare function zq:external-variables($query-key as xs:anyURI) as
  xs:QName* external ;

(:~
 :
The function tests if the query identified by the given key
 : is an updating query.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return true if the query is an updating query, false otherwise.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :)
declare function zq:is-updating($query-key as xs:anyURI) as
  xs:boolean external;

(:~
 :
The function tests if the query identified by the given key
 : is sequential query.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return true if the query is a sequential, false otherwise.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :)
declare function zq:is-sequential($query-key as xs:anyURI) as
  xs:boolean external;

(:~
 :
This function binds the context-item of the prepared query
 : identified by the given key to the $dot argument.

 :
 : @param $query-key the identifier for a compiled query
 : @param $dot the context item to bind
 :
 : @return the function has side effects and returns the empty
 :   sequence.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :)
declare %an:sequential function zq:bind-context-item($query-key as xs:anyURI,
  $dot as item()) as empty-sequence() external ;

(:~
 :
This function binds the variable with name $name of
 : the prepared query identified by $query-key to the given sequence.

 :
 : @param $query-key the identifier for a compiled query
 : @param $name the name of the external variable to bind
 : @param $value the sequence to which the external variable $name
 :  should be bound
 :
 : @return the function has side effects and returns the empty
 :   sequence.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 : @error zq:UNDECLARED_VARIABLE if the given variable is not declared
 :   in the query.
 :)
declare %an:sequential function zq:bind-variable($query-key as xs:anyURI,
  $var as xs:QName, $value as item()*) as empty-sequence() external ;

(:~
 :
Evaluates the given prepared query and returns the result
 : of the evaluation. The query must not be sequential or
 : updating.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return the result of evaluating the given query
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :
 : @error zq:QUERY_IS_UPDATING if the query is an updating query.
 :
 : @error zq:QUERY_IS_SEQUENTIAL if the query is sequential.
 :
 : @error any dynamic error that is raised by evaluating the
 :   given query.
 :
 :)
declare function zq:evaluate($query-key as xs:anyURI) as item()* external;

(:~
 :
Evaluates the given prepared query and applies the updates
 : computed by this query. The query must be an updating query.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return the function has side effects because it applies
 :  the updates of the query. It returns the empty sequence.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :
 : @error zq:QUERY_NOT_UPDATING if the query is not an updating query.
 :
 : @error zq:QUERY_IS_SEQUENTIAL if the query is sequential.
 :
 : @error any dynamic error that is raised by evaluating the
 :   given query or applying its updates.
 :
 :)
declare updating function zq:evaluate-updating($query-key as xs:anyURI) external;

(:~
 :
Evaluates the given prepared query and returns the result
 : of the evaluation. The query must be sequential.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return the result of evaluating the query.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :
 : @error zq:QUERY_NOT_SEQUENTIAL if the query is not sequential.
 :
 : @error zq:QUERY_IS_UPDATING if the query is an updating query.
 :
 : @error any dynamic error that is raised by evaluating the
 :   given query.
 :
 :)
declare %an:sequential function zq:evaluate-sequential($query-key as
  xs:string) as item()* external;

(:~
 :
Deletes the prepared query associated with the given identifier.

 :
After the query is deleted, the corresponding identifier should
 : not be used as argument to any of the functions of this module.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return the function has side effects and returns the empty sequence.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :
 :)
declare %an:sequential function zq:delete-query($query-key as xs:anyURI) as
  empty-sequence() external;

(:~
 :
This function returns the value of a variable that is bound in the
 : given query.

 :
 : @param $query-key the identifier of a compiled query.
 : @param $var-name the name of the variable whose value should be returned.
 :
 : @return the value bound to the given variable.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 : @error zq:UNDECLARED_VARIABLE if the given variable is not declared
 :   in the query.
 : @error zq:UNBOUND_VARIABLE if the given variable doesn't have a value.
 :)
declare function zq:variable-value($query-key as xs:anyURI, $var-name as
  xs:QName) as item()* external;

(:~
 :
Returns the compiled query identified by the given query-key
 : as binary data.

 :
 : @param $query-key the identifier of a compiled query.
 :
 : @return the query as xs:base64Binary.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 : @error zq:NO_QUERY_PLAN if there is an error serializing the query.
 :)
declare function zq:query-plan($query-key as xs:anyURI)
  as xs:base64Binary external;

(:~
 :
The function loads a given query for execution from a
 : xs:base64Binary query plan, obtained through the zq:query-plan function.

 :
If the query was successfully loaded, the function returns an
 : identifier as xs:anyURI. This URI can be passed to other functions
 : of this module (e.g. to actually evaluate the query). The URI
 : is opaque and its lifetime is bound by the lifetime of the query
 : that invoked this function. Further reference or uses
 : of the identifier lead to unexpected results.

 :

 :
Successfully prepared queries need to be deleted by passing the resulting
 : identifier to the zq:delete-query function of this module.

 :
 : @param $plan the binary query plan.
 :
 : @return an identifier for the compiled query that can be passed
 :   as arguments to other functions of this module.
 :
 : @error any (static or type) error that may be raised during the compilation
 : of the query. For example, err:XPST0003 if the given query could
 : not be parsed.
 :)
declare function zq:load-from-query-plan($plan as xs:base64Binary)
  as xs:anyURI external;

(:~
 :
The function loads a given query for execution from a
 : xs:base64Binary query plan, obtained through the zq:query-plan function.

 :
If the query was successfully loaded, the function returns an
 : identifier as xs:anyURI. This URI can be passed to other functions
 : of this module (e.g. to actually evaluate the query). The URI
 : is opaque and its lilfetime is bound by the lifetime of the query
 : that invoked this function. Further reference or uses
 : of the identifier lead to unexpected results.

 :

 :
For important notes regarding the second and third parameters of the
 : function, review the comments in zq:prepare-main-module#3.

 :

 :
Successfully prepared queries need to be deleted by passing the resulting
 : identifier to the zq:delete-query function of this module.

 :
 : @param $plan the binary query plan.
 :
 : @param $resolver the URL resolver function.
 :
 : @param $mapper the URI mapper function.
 :
 : @return an identifier for the compiled query that can be passed
 :   as arguments to other functions of this module.
 :
 : @error any (static or type) error that may be raised during the compilation
 : of the query. For example, err:XPST0003 if the given query could
 : not be parsed.
 :)
declare function zq:load-from-query-plan($plan as xs:base64Binary,
  $resolver as item()?, $mapper as item()?) as xs:anyURI external;

(:~
 :
Internal helper function. Only necessary because of incomplete HOF
 : support in Zorba.

 :)
declare %private function zq:hof-invoker($hof as item(),
  $ns as xs:string, $entity as xs:string) as item()*
{
   $hof($ns, $entity)
};

xquery version "3.0";
(:
 : Copyright 2011 The FLWOR Foundation.
 :
 : 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.
:)

(:~
 : This module contains functions to compile and evaluate XQuery
 : programs. Also, it contains function that allow to parameterize
 : the static or dynamic evaluation phase.
 :
 : @author Juan Zacarias
 : @project Zorba/Programming Languages/ZQ
 :)
module namespace zq = 'http://zorba.io/modules/zorba-query';

declare namespace an = "http://zorba.io/annotations";
declare namespace ver = "http://zorba.io/options/versioning";
declare namespace op = "http://zorba.io/options/features";
declare namespace f = "http://zorba.io/features";

declare option ver:module-version "1.0";

(:~
 :
The function prepares a given a query for execution.

 :
If the program was successfully compiled, the function returns an
 : identifier as xs:anyURI. This URI can be passed to other functions
 : of this module (e.g. to actually evaluate the program). The URI
 : is opaque and its lilfetime is bound by the lifetime of the query
 : program that invoked this function. Further reference or uses
 : of the identifier lead to unexpected results.

 :

 :
Successfully prepared queries need to be deleted by passing the resulting
 : identifier to the zq:delete-query function of this module.

 :
 : @param $main-module-text the query program that should be prepared.
 :   The program needs to be a XQuery or JSONiq main module.
 :
 : @return an identifier for the compiled program that can be passed
 :   as arguments to other functions of this module.
 :
 : @error any (static or type) error that may be raised during the compilation
 : of the query. For example, err:XPST0003 if the given query program could
 : not be parsed.
 :)
declare %an:sequential function zq:prepare-main-module($main-module-text as xs:string) as
  xs:anyURI external;

(:~
 :
The function prepares a given query for execution.

 :
If the program was successfully compiled, the function returns an
 : identifier as xs:anyURI. This URI can be passed to other functions
 : of this module (e.g. to actually evaluate the program). The URI
 : is opaque and its lilfetime is bound by the lifetime of the XQuery
 : program that invoked this function. Further reference or uses
 : of the identifier lead to unexpected results.

 :

 :
Important notes regarding the second and third parameters of the function:

 :
--------------------------------------------------------------------------

 :

 :
These parameters allow you to specify a URL resolver and a URI mapper
 : for Zorba to use when executing this query. See
 : here

 :

 :
The second parameter is a function item for a URL
 : resolver. The URL resolver function must recive 2 parameters:
 :
A $namespace as xs:string that will contain the url to be resolved.

 :
A $entity as xs:string that will contain the type of resolving needed.
 :   This can be one of two values: "module" or "schema".

 :

 :
The function must return the empty sequence when the specified $namespace
 : or $entity are not the ones to be resolved.

 :

 :
Example:

 :

 : declare function mymod:url-resolver($namespace as xs:string, $entity as xs:string) as item()?
 : {
 :  if($namespace = 'http://test.xq')
 :  then "module namespace test = 'http://test'; declare function test:foo(){'foo'};"
 :  else ()
 : };
 :

 :
The URL resolver function's namespace, name, and parameter naming are
 : not restricted by ZQ.

 :

 :
The URL resolver function's return type is not restricted, it could be a string, a sequence,
 : a node, etc. All the outputs types are to be serialized as a string.

 :

 :
The third parameter is a function item for a URI mapper.

 :
The URI mapper function, just like the URL resolver, receives 2 parameters:
 :
A $namespace as xs:string that will contain the URI to be mapped.

 :
A $entity as xs:string that will contain the type of resolving needed.
 :   This can be one of two values: "module" or "schema".

 :

 :
The URI mapper must return an empty sequence when the specified $namesapce or $entity
 : are not to be mapped. Unlike the URL resolver this function must return a sequence of strings.

 :

 :
Example:

 :

 : declare function mymod:uri-mapper($namespace as xs:string, $entity as xs:string)
 : {
 :  if($namespace = 'http://test')
 :  then ("http://www.zorba-xquery.com/test", "http://foo.com/schema/test")
 :  else ()
 : };
 :

 :
The URI mapper function's namespace, name, and parameter naming are
 : not restricted by ZQ.

 :

 :
In order to pass the above URL resolver and URI mapper to this function,
 : use the following syntax:

 :

 :   variable $queryID := zq:prepare-main-module("..query text..",
 :      mymod:url-resolver#2, mymod:uri-mapper#2);
 :

 :
That is, the QName of the function followed by "#2". This is XQuery
 : "higher-order function" syntax, meaning the function with the specified
 : QName which takes two arguments. Since URL resolvers and URI mappers
 : must take two arguments, both will always be specified with "#2".

 :

 :
Both the URL resolver and URI mapper functions are optional, meaning you
 : may pass the empty-sequence () for either.

 :

 :
Successfully prepared queries need to be deleted by passing the resulting
 : identifier to the zq:delete-query function of this module.

 :
 : @param $main-module-text the query that should be prepared.
 :   The program needs to be a XQuery main module.
 :
 : @param $resolver the URL resolver function.
 :
 : @param $mapper the URI mapper function.
 :
 : @return an identifier for the compiled program that can be passed
 :   as arguments to other functions of this module.
 :
 : @error any (static or type) error that may be raised during the compilation
 : of the query. For example, err:XPST0003 if the given query could
 : not be parsed.
 :)
declare %an:sequential function zq:prepare-main-module(
  $main-module-text as xs:string,
  $resolver as ( function(xs:string, xs:string) as item()? )?,
  $mapper as ( function(xs:string, xs:string) as xs:string* )? ) as
    xs:anyURI external;

(:~
 :
This function compiles a given XQuery library module. It can be used
 : to compile-check a module.

 :
 : @param $library-module-text the XQuery library module that should
 :  be prepared.
 :
 : @return the function is declared as sequential.It returns the
 :  empty-sequence.
 :
 : @error any (static or type) error that may be raised during the compilation
 : of the library module. For example, err:XPST0003 if the given XQuery library
 : module could not be parsed.
 :)
declare %an:sequential function zq:prepare-library-module($library-module-text as xs:string) as
  empty-sequence() external ;

(:~
 :
The function tests if the context-item is bound for the
 : execution of the query referred to by the given query identifier.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return true if the context-item is bound, false otherwise.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :)
declare function zq:is-bound-context-item($query-key as xs:anyURI)
  as xs:boolean  external;

(:~
 :
The function tests if the given variable is bound for the
 : execution of the query referred to by the given query identifier.

 :
 : @param $query-key the identifier for a compiled query
 : @param $var-name the name of the variable
 :
 : @return true if the variable is bound, false otherwise.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 : @error zq:UNDECLARED_VARIABLE if the given variable is not declared
 :   in the query.
 :)
declare function zq:is-bound-variable($query-key as xs:anyURI, $var-name as
  xs:QName) as xs:boolean  external;

(:~
 :
The function returns the names of the external variables that
 : are declared in the given query (either in the main module or
 : in any of the imported library modules).

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return the sequence of names of the said external variables.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :)
declare function zq:external-variables($query-key as xs:anyURI) as
  xs:QName* external ;

(:~
 :
The function tests if the query identified by the given key
 : is an updating query.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return true if the query is an updating query, false otherwise.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :)
declare function zq:is-updating($query-key as xs:anyURI) as
  xs:boolean external;

(:~
 :
The function tests if the query identified by the given key
 : is sequential query.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return true if the query is a sequential, false otherwise.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :)
declare function zq:is-sequential($query-key as xs:anyURI) as
  xs:boolean external;

(:~
 :
This function binds the context-item of the prepared query
 : identified by the given key to the $dot argument.

 :
 : @param $query-key the identifier for a compiled query
 : @param $dot the context item to bind
 :
 : @return the function has side effects and returns the empty
 :   sequence.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :)
declare %an:sequential function zq:bind-context-item($query-key as xs:anyURI,
  $dot as item()) as empty-sequence() external ;

(:~
 :
This function binds the variable with name $name of
 : the prepared query identified by $query-key to the given sequence.

 :
 : @param $query-key the identifier for a compiled query
 : @param $name the name of the external variable to bind
 : @param $value the sequence to which the external variable $name
 :  should be bound
 :
 : @return the function has side effects and returns the empty
 :   sequence.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 : @error zq:UNDECLARED_VARIABLE if the given variable is not declared
 :   in the query.
 :)
declare %an:sequential function zq:bind-variable($query-key as xs:anyURI,
  $var as xs:QName, $value as item()*) as empty-sequence() external ;

(:~
 :
Evaluates the given prepared query and returns the result
 : of the evaluation. The query must not be sequential or
 : updating.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return the result of evaluating the given query
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :
 : @error zq:QUERY_IS_UPDATING if the query is an updating query.
 :
 : @error zq:QUERY_IS_SEQUENTIAL if the query is sequential.
 :
 : @error any dynamic error that is raised by evaluating the
 :   given query.
 :
 :)
declare function zq:evaluate($query-key as xs:anyURI) as item()* external;

(:~
 :
Evaluates the given prepared query and applies the updates
 : computed by this query. The query must be an updating query.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return the function has side effects because it applies
 :  the updates of the query. It returns the empty sequence.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :
 : @error zq:QUERY_NOT_UPDATING if the query is not an updating query.
 :
 : @error zq:QUERY_IS_SEQUENTIAL if the query is sequential.
 :
 : @error any dynamic error that is raised by evaluating the
 :   given query or applying its updates.
 :
 :)
declare updating function zq:evaluate-updating($query-key as xs:anyURI) external;

(:~
 :
Evaluates the given prepared query and returns the result
 : of the evaluation. The query must be sequential.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return the result of evaluating the query.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :
 : @error zq:QUERY_NOT_SEQUENTIAL if the query is not sequential.
 :
 : @error zq:QUERY_IS_UPDATING if the query is an updating query.
 :
 : @error any dynamic error that is raised by evaluating the
 :   given query.
 :
 :)
declare %an:sequential function zq:evaluate-sequential($query-key as
  xs:string) as item()* external;

(:~
 :
Deletes the prepared query associated with the given identifier.

 :
After the query is deleted, the corresponding identifier should
 : not be used as argument to any of the functions of this module.

 :
 : @param $query-key the identifier for a compiled query
 :
 : @return the function has side effects and returns the empty sequence.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 :
 :)
declare %an:sequential function zq:delete-query($query-key as xs:anyURI) as
  empty-sequence() external;

(:~
 :
This function returns the value of a variable that is bound in the
 : given query.

 :
 : @param $query-key the identifier of a compiled query.
 : @param $var-name the name of the variable whose value should be returned.
 :
 : @return the value bound to the given variable.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 : @error zq:UNDECLARED_VARIABLE if the given variable is not declared
 :   in the query.
 : @error zq:UNBOUND_VARIABLE if the given variable doesn't have a value.
 :)
declare function zq:variable-value($query-key as xs:anyURI, $var-name as
  xs:QName) as item()* external;

(:~
 :
Returns the compiled query identified by the given query-key
 : as binary data.

 :
 : @param $query-key the identifier of a compiled query.
 :
 : @return the query as xs:base64Binary.
 :
 : @error zq:NO_QUERY_MATCH if no query with the given identifier
 :   was prepared.
 : @error zq:NO_QUERY_PLAN if there is an error serializing the query.
 :)
declare function zq:query-plan($query-key as xs:anyURI)
  as xs:base64Binary external;

(:~
 :
The function loads a given query for execution from a
 : xs:base64Binary query plan, obtained through the zq:query-plan function.

 :
If the program was successfully loaded, the function returns an
 : identifier as xs:anyURI. This URI can be passed to other functions
 : of this module (e.g. to actually evaluate the program). The URI
 : is opaque and its lifetime is bound by the lifetime of the XQuery
 : program that invoked this function. Further reference or uses
 : of the identifier lead to unexpected results.

 :

 :
Successfully prepared queries need to be deleted by passing the resulting
 : identifier to the zq:delete-query function of this module.

 :
 : @param $main-module-text the query that should be prepared.
 :   The program needs to be a XQuery main module.
 :
 : @return an identifier for the compiled program that can be passed
 :   as arguments to other functions of this module.
 :
 : @error any (static or type) error that may be raised during the compilation
 : of the query. For example, err:XPST0003 if the given query could
 : not be parsed.
 :)
declare function zq:load-from-query-plan($plan as xs:base64Binary)
  as xs:anyURI external;

(:~
 :
The function loads a given query for execution from a
 : xs:base64Binary query plan, obtained through the zq:query-plan function.

 :
If the program was successfully loaded, the function returns an
 : identifier as xs:anyURI. This URI can be passed to other functions
 : of this module (e.g. to actually evaluate the program). The URI
 : is opaque and its lilfetime is bound by the lifetime of the XQuery
 : program that invoked this function. Further reference or uses
 : of the identifier lead to unexpected results.

 :

 :
For important notes regarding the second and third parameters of the
 : function, review the comments in zq:prepare-main-module#3.

 :

 :
Successfully prepared queries need to be deleted by passing the resulting
 : identifier to the zq:delete-query function of this module.

 :
 : @param $main-module-text the query that should be prepared.
 :   The program needs to be a XQuery main module.
 :
 : @param $resolver the URL resolver function.
 :
 : @param $mapper the URI mapper function.
 :
 : @return an identifier for the compiled program that can be passed
 :   as arguments to other functions of this module.
 :
 : @error any (static or type) error that may be raised during the compilation
 : of the query. For example, err:XPST0003 if the given query could
 : not be parsed.
 :)
declare function zq:load-from-query-plan($plan as xs:base64Binary,
  $resolver as item()?, $mapper as item()?) as xs:anyURI external;

(:~
 :
Internal helper function. Only necessary because of incomplete HOF
 : support in Zorba.

 :)
declare %private function zq:hof-invoker($hof as item(),
  $ns as xs:string, $entity as xs:string) as item()*
{
   $hof($ns, $entity)
};