Can someone explain how the external variable works in XQuery? - xquery

I see an XML node when I log $variable, but I'm not sure how $variable received the node and assume it has something to do with external.
declare variable $variable as element() external;

Declaring an external variable in a query means that the calling application must supply a value for the variable when it invokes the query. The way in which this is done depends on the API offered by your chosen XQuery processor.

Related

XQuery: generate a variable name from string (attribute value)

I have a framework which runs my xquery and it includes an XML config file that defines the parameters I can use in the query as variables, like this:
<text name="myVar" label="My Var"/>
This means that the user will have a dialog with a field called "My Var", whose value I can then access as $myVar in my query. I don't know how the variable is declared and have no access to the XQuery code that runs my query.
But I want to access my params in a generic way, e.g. go through all of them, check if empty, create a table with labels and values in HTML. I do have access to the XML config file. That means I have to access the value of
$(/config/text/#name)
(The above does not work obviously).
How is it possible?
Well, variable names such as $myVar can only be defined statically, so this isn't going to be possible unless you actually generate the source code of your query programmatically from what you find in the config file.
I think what I would do here is to pass the parameters as a map:
declare variable $params as map(xs:string, item()*);
do-something-with($params('myVar'));
The entries in the map can be accessed either statically ($params?myVar) or dynamically ($params('myVar'))

How do I get the HTTP POST body in MarkLogic XQuery?

I'm trying to use the exist-db request:get-data() method to get the post data of a request. However, I'm getting the error:
XDMP-UNDFUN: (err:XPST0017) Undefined function request:get-data()
I did declare the namespace in my header. I don't understand why I still can't use request:get-data() or any of the other request: functions
declare namespace request="http://exist-db.org/xquery/request";
declare option exist:serialize "method=xml media-type=text/xml indent=yes";
let $post-data := request:get-data()
return $post-data
I think you're looking for xdmp:get-request-body.
Sam pointed you to the function you need, but I wanted to respond to another part of your question:
I did declare the namespace in my header. I don't understand why I still can't use request:get-data() or any of the other request: functions
Each XQuery processing engine implements standard functions, but there is other functionality needed that is not defined by the standard. For MarkLogic, you'll use standard functions with the fn: prefix.
Each XQuery engine then defines additional functions that will be needed. For Exist DB, some of those are in the "http://exist-db.org/xquery/request" namespace, while MarkLogic uses "http://marklogic.com/xdmp" for a lot of its extension functions.
When you're looking for the MarkLogic equivalent of an Exist DB-specific function, search on http://docs.marklogic.com -- start with the function name, and if that doesn't work, search for the terms that describe what you're trying to do.

Nested property expansion in SoapUI

Short version: How do I do nested property expansion in an XQuery in SoapUI Pro 5, where the outer property is a reference to the ResponseAsXML of a previous test step, and the inner property comes from a properties file?
Example:
My test steps look like this:
Call AddCustomer, which also adds an email address, and returns customerId.
Use property transfer to store the customerId in a properties file called EmailProperties.
Call AddEmailToCustomer, which adds a second email to the same customer and returns a new emailId.
Call GetEmailsForCustomer, which returns both emails for the customer.
The REST endpoints for step 3 and 4 look like this:
POST/GET http://myEndpoint.com/customers/{customerId}/emails
When trying to verify step 4, I wrote an XQuery that loops through the Response (since we can't guarantee what order the emails will be in the response) to find the email added in step 3. In my XQuery, if I hardcode the customerId in the namespace like this it works fine:
for $email in //emails
where $email/id/text()='${AddEmailToCustomer#ResponseAsXml#declare namespace ns1='http://myEndpoint.com/customers/1234/emails'; //ns1:Response[1]/ns1:id[1]}'
But if I try to use the customerId from the properties file like this:
for $email in //emails
where $email/id/text()='${AddEmailToCustomer#ResponseAsXml#declare namespace ns1='http://myEndpoint.com/customers/${#EmailProperties#customerId}/emails'; //ns1:Response[1]/ns1:id[1]}'
I get an error about how it can't find the expected child nodes:
...Exception:org.custommonkey.xmlunit.Diff[different] Expected presence of child nodes to be 'true' but was 'false'...
How can I get this to work?
You can use wildcards for namespaces to greatly simplify your XPaths:
where $email/id/text()='${AddEmailToCustomer#ResponseAsXml#//*:Response[1]/*:id[1]}'
As #SiKing recommends you can use wildcards for namespaces to simplify XPath, this is enough if you don't care about check the namespace (which is the most usual behavior). However I think that you put the namespace in a property in order to check possible different namespaces due you can try declaring your XQuery Match assertion as follows:
declare namespace ns1='http://myEndpoint.com/customers/${#EmailProperties#customerId}/emails';
<XQueryResult>
{
for $email in //ns1:emails
where $email/id/text()='${AddEmailToCustomer#Response#//ns1:Response[1]/ns1:id[1]}'
return $email
}
</XQueryResult>
Please note that if EmailProperties is a property testStep then you must use ${EmailProperties#customerId} instead of ${#EmailProperties#customerId}, also in this case I prefer to use #Response instead of #ResponseAsXml.
Hope this helps,

How to set the default / base collection of a XQJ Connection

I want to query the eXist db via the XQJ API. Therefore I have an XQuery file which I want to apply to several different documents inside different collections of the database.
My question is, how can I set the path to the document/collection? I want to apply the XQuery only on specific documents/collections and canĀ“t set the path directly in the XQuery document (with the doc('path/to/doc') function ), because I want to apply the same XQuery to several files with different paths.
I could bind a Java variable to an Xquery expression but there has to be a more elegant way to achieve this task with the XQJ API. Hope you can help me out a little bit.
If you know the specific document URIs up-front, you could bind a bunch of URIs as a xs:string sequence, where you can then iterate through them in your XQuery expression, performing operations against each URI as necessary.
declare variable $uris as xs:string* external;
for $uri in $uris
return (
(: perform operation against $uri :)
)
Or if you know the collection uri,
declare variable $collection-uri as xs:string external;
for $document-node in fn:collection($collection-uri)
return (
(: perform operation against document-node() :)
)
In a future version of eXist XQJ API, you will be able to set a collection as the default context, which goes against both the XQJ standard AND the XQuery 1.0 / 3.0 standards but other users appear to be wanting this functionality quite badly.

Can I declare a global variable in xquery in Marklogic Server?

I want a global variable which I can use in my different .xqy pages. Can I declare such a variable in xquery in Marklogic Server ?
You can declare a variable in any module. For instance, it is config.xqy.
declare variable $PRECISION as xs:integer := 4;
For using this variable you need to import this module in your work module.
import module namespace config = "http://your-namespace" at "config.xqy";
And refer to this variable:
$config:PRECISION
If your application is running on a single E-node, you can use server fields , which are sort of designed for this use case as well.
If you need values accessible across the server, there is a library in the Marklogic XQuery Commons for storing persistent key/value pairs:
https://github.com/marklogic/commons/blob/master/properties/properties.xqy
And you may have already considered this, but you could also just simply store the global data in a document on the database and access with doc() - or eval() if you need to get to it from a different database.
You have a few options. If you need a global constant variable, the config.xqy method mentions in #Andrew Orlov's answer is great because you avoid any locking from concurrent access to a properties.xml file.
If you need a variable that can be mutated across a cluster of nodes, the property.xqy example linked by #wst appears to use globally assigned namespaces to embed a retrievable key and value. Pretty clever. However, I'm not sure how much this is meant for heavy levels of change.
The E-node specific variable from #Eric Bloch is good, but please also be aware that it will not survive a system restart.
I'd be interested to know how these all compare performance-wise.

Resources