3
votes

Typically when I work in XSLT I create a main file that is composed mostly of imports. I keep each of the import files small so that they are easier for me to maintain.

I'm trying to do the same thing in XQuery (in MarkLogic), but I'm not able to make it work the way I'd hoped it'd work.

Here's what I'd like to be able to do:

main.xqy:

xquery version "1.0-ml";
module namespace summit = "http://example.com/summit";
import module "http://example.com/summit" at "/ext/variables.xqy";
import module "http://example.com/summit" at "/ext/utils.xqy";

variables.xqy:

xquery version "1.0-ml";
module namespace summit = "http://example.com/summit";
declare variable $BASEURL as xs:string  := "https://example.com/v1";

utils.xqy:

xquery version "1.0-ml";
module namespace summit = "http://example.com/summit";
declare function summit:baseUrl() {
    let $url := $BASEURL
    return $url
};

and then use the following code to call it in the query console:

xquery version "1.0-ml";
import module namespace summit = "http://example.com/summit" at "/ext/main.xqy";
summit:baseUrl()

I get the following error:

[1.0-ml] XDMP-UNDVAR: (err:XPST0008) Undefined variable $BASEURL
Stack Trace
In /ext/utils.xqy on line 4
In xdmp:eval("xquery version &quot;1.0-ml&quot;;&#10;import module namespace s...", (), <options xmlns="xdmp:eval"><database>8148014817830251656</database>...</options>)

I get similar errors for functions defined in the same way (defined in one xquery file, used in a different one that's imported into main.xqy).

I can work around this by importing variables.xqy into utils.xqy, but I'd like to avoid that as it adds management overhead, not decreases it.

I'm sure there's something I'm doing obviously wrong here, but I'm not sure what.

How do you organize large xquery projects?

2

2 Answers

6
votes

Unlike XSLT, XQuery was designed so that modules could be independently compiled, and this means that a module cannot depend on variables or functions that aren't reachable by following its own module imports (transitively).

Although this can sometimes be inconvenient, it does have the benefit of making library modules more reusable, because they have no hidden dependencies that mean they can be imported into one main module and not into another.

Note there are some subtleties here. First, the spec notes that if you import the same module twice, you only get one copy of the variables:

If a query imports the same module via multiple paths, only one instance of the module is imported. Because only one instance of a module is imported, there is only one instance of each variable declared in a module's prolog.

But it also notes that the concept of "same module" is fuzzy around the edges:

When the same absolutized location URI is used more than once, either in the same "import module" declaration or in different "import module" declarations within the same query, a single copy of the resource containing the module is loaded. When different absolutized location URIs are used, each results in a single module being loaded, unless the implementation is able to determine that the different URIs are references to the same resource. No error due to duplicate variable or functions names should arise from the same module being imported more than once, so long as the absolute location URI is the same in each case.

5
votes

Your utils.xqy needs to import variables.xqy .

You can try this:

xquery version "1.0-ml";
module namespace summit = "http://example.com/summit";
import module namespace variables = "http://example.com/summit" at "/ext/variables.xqy";
declare function summit:baseUrl() {
    let $url := $variables:BASEURL
    return $url
};

Outside of correcting this, your code organization resembles my preferred technique as well.