XQuery concat not outputting document XML in expected way - xquery

let $d := doc('foo.xml')
return concat('let $d := 

', $d)
returns
let $d :=
bar
I need it to return:
let $d :=
<foo>bar</foo>

Reading the function signature for fn:concat, there is no reason to expect it to output XML. From http://www.w3.org/TR/xpath-functions/#func-concat
fn:concat(
$arg1 as xs:anyAtomicType?,
$arg2 as xs:anyAtomicType?,
...)
as xs:string
That is, it takes a variable number of atomic items and returns a string. So if you pass it an XML node, it will attempt to atomize that and return a string result. If you haven't run into atomization yet, try string(doc($uri)) to see what happens.
Ignoring that, it looks like you're trying to build an XQuery expression using string manipulation - perhaps for use with xdmp:eval? That's fine, but don't pass in the XML using xdmp:quote. For correctness, performance, and security reasons, the right tool for this job is an external variable.
xdmp:eval('
declare variable $INPUT external ;
let $d := $INPUT
return xdmp:describe($d)',
(xs:QName('INPUT'), $d))
Better yet, write the string part as a separate XQuery module and xdmp:invoke it with the same external variable parameters.
Why do it this way? Correctness, efficiency and security. If you get into the habit of blindly evaluating strings, you are setting yourself up for problems. Passing a node reference is more efficient than quoting a large node. When you quote XML as a string you may end up with XQuery-significant characters, such as {. Then you have to escape them (or switch to using xdmp:unquote, but that makes it even less efficient). Any escaping will be error-prone. In SQL the classic way to handle this is with a bind variable, and with XQuery it is an external variable. As with bind variables in SQL, external variables handle escaping. This also makes injection attacks much more difficult.

Use pipes to concatenate.
Eg: $a||$b
If your variables are of atomic type, then you can use a string-join.
eg:, $a||fn:string-join(($b),",")

let $d := xdmp:quote(doc('foo.xml'))
return concat('let $d := 

', $d)

Related

XQuery cardinality error on outputting results to text file

Using XQuery 3.1 (under eXistDB 4.4), I have a function which returns a serialized output of 710 delimited rows like these:
MS609-0001~ok~0001~1r~Deposition~De_Manso_Sanctarum_Puellarum_1~self~1245-05-27~Arnald_Garnier_MSP-AU~self
MS609-0002~ok~0002~1r~Deposition~De_Manso_Sanctarum_Puellarum_1~MS609-0001~1245-05-27~Guilhem_de_Rosengue_MSP-AU~MS609-0001
MS609-0003~ok~0003~1r~Deposition~De_Manso_Sanctarum_Puellarum_1~MS609-0001~1245-05-27~Hugo_de_Mamiros_MSP-AU~MS609-0001
I get the above serialized results in another function that should store it in the directory /db/apps/deheresi/documents as a flatfile depositions.txt
let $x := schedule:deposition-textfile()
return xmldb:store(concat($globalvar:URIdb,"documents"), "deposition.txt", $x)
But when I execute the xmldb:store action, it returns an error:
Description: err:XPTY0004 checking function parameter 3
in call xmldb:store(untyped-value-check[xs:string,
concat("/db/apps/deheresi/", "documents")], "depositions.txt", $x):
XPTY0004: The actual cardinality for parameter 3 does not
match the cardinality declared in the function's signature:
xmldb:store($collection-uri as xs:string,
$resource-name as xs:string?,
$contents as item()) xs:string?.
Expected cardinality: exactly one, got 710.
How do I get these serialized results into the text file?
Thanks in advance.
UPDATE: I tried wrapping the serialized output in <result> and that fixes the problem of cardinality, BUT it writes the <result> element to the file as plain text:
<result>MS609-0001~ok~0001~1r~Deposition~De_Manso_Sanctarum_Puellarum_1~self~1245-05-27~Arnald_Garnier_MSP-AU~self
MS609-0002~ok~0002~1r~Deposition~De_Manso_Sanctarum_Puellarum_1~MS609-0001~1245-05-27~Guilhem_de_Rosengue_MSP-AU~MS609-0001
MS609-0003~ok~0003~1r~Deposition~De_Manso_Sanctarum_Puellarum_1~MS609-0001~1245-05-27~Hugo_de_Mamiros_MSP-AU~MS609-0001</result>
Even if I add:
declare option exist:serialize "method=text";
The error message seems quite clear. The return type of schedule:deposition-textfile() is not acceptable to xmldb:store(). You want a single string, not 710.
Either change the return type of your function, or search the xQuery function documentation for xmldb:store to find a suitable alternative for your returntype.
I don't know the function you are calling, but perhaps you should use string-join() to combine the 710 strings into one, with a newline separator.

How to iterate over script arguments?

In Unix shell scripts one can use something like this for parsing the arguments to the script:
while [ $1 ]; do
#
# do stuff
#
shift
done
How do I implement something similar in PowerShell?
For undeclared parameters, you can use the $Args automatic variable as #AnsgarWeichers suggests with either of the foreach methods (see below), but I would argue the Unix method you're describing is against PowerShell philosophy. PowerShell is a .Net environment, so everything is fundamentally an object instead of fundamentally a character string.
You should be defining your parameters, giving them a type, and, above all, giving them a name. See about_Parameters, about_Functions, about_Functions_Advanced, and about_Functions_Advanced_Parameters. A script can be written to function like a cmdlet or function, so that's why you look to those help docs.
If your parameter is legitimately an array, you can iterate through it with either the foreach statement:
foreach ($item in $MyParameterArray) {
[...]
}
Or using the ForEach-Object cmdlet:
$MyParameterArray | ForEach-Object {
[...]
}

Execute function in FLWOR without using 'let'

Let's say I create a map:
let $map := map:map()
How can I put something in that map without using let? Usually I have to do something like
let $map := map:map()
let $useless-var := map:put($map, $key, $value)
Seems strange that if I want to execute something and I don't care about the return value, I still have to store the result. What am I missing here?
Note: The important part is not map(), but the fact that I can't run a function without storing the result in some pointless variable.
One approach is to execute the functions as items in a sequence where only one item (typically, the first or last) in the sequence supplies the real value for an assignment or return, as in:
let $roundedX := (
math:floor($x),
local:function1(...),
local:function2(...)[false()],
...
)
...
return (
local:functionA(...),
local:functionB(...)[false()],
...,
$roundedX * 10
)
If the function returns a value that you want to throw away, just use a false predicate, as with two of the functions above.
Of course, this approach is only useful for functions with side effects.
Don't use a FLWOR unless you need it. In my opinion FLWOR expressions are somewhat overused. I often see expressions like:
let $a := current-time()
return $a
...when it would work just as well to write:
current-time()
See also: http://blakeley.com/blogofile/2012/03/19/let-free-style-and-streaming/
In MarkLogic 7 you can use the map constructor to generate maps recursively. I think this is probably what you want:
let $map := map:new(
(1 to 10) ! map:entry(., .)
)
Or you execute map:put as part of another sequence, or in the return statement before you return the map:
let $map := map:map()
let $not-useless-var := ...
return (map:put($map, string($not-useless-var), $not-useless-var), $map)
In plain XQuery (ignoring extensions like the XQuery scripting extension) there are no side-effects, so calling a function without using its return value is meaningless.
What you may be missing here is that map:put() returns a new map with an extra item added, it does not mutate the original map. So your $useless-var is not actually useless.
EDIT: Actually I'm not sure if MarkLogic's map:put() mutates the map. (If it does, that is really gross.) I was thinking of the proposed XQuery 3.1 maps (which I've used in BaseX) which definitely are immutable.
Since the focus of your question is about running a function without storing intermediate results, you might find the map operator (!) helpful:
local:build-sequence() ! local:do-something-to-each(.)
That's good for processing sequences. If you're thinking more about processing the result of something, the answer is likely in embracing the functional nature of XQuery:
local:produce-result(
local:build-parameter(),
local:retrieve-config()
)
Not sure exactly what you're looking for, but hopefully those help.

How to write a while loop in MarkLogic XQuery

Is there any recognized idiom for writing the equivalent of a while loop in MarkLogic XQuery? I know that I could write a tail-recursive routine, but MarkLogic XQuery does not optimize tail-recursion and I'm getting a stack overflow (I have to go around my loop ~20000 times).
Editorial note: as of MarkLogic 6, tail recursion is optimized in MarkLogic.
Recursion is the usual way. Another is to use a FLWOR with a try-catch and throw an exception with a known code to exit early.
try {
for $x in 1 to count($tokens)
return tok($x) }
catch ($ex) {
if ($x/error:code eq 'BREAK') then ()
else xdmp:rethrow() }
The tok function would call error((), 'BREAK') to exit the parent FLWOR expression. If needed you could multiply the token count by some factor, or use an arbitrary large number.
https://github.com/robwhitby/xray/blob/coverage/src/coverage.xqy has a more complex example, in the cover:actual-via-debug function.
The usual way in XQuery is by using a FLWOR expression. How to write this highly depends on your loop and data as it is not an excact mappoing to a while loop known from other languages.
So what you can do is e.g.
let $c := doc('my.doc')/root/get-all-my-elements-using-xpath
return process-result($c)
Please be more specific what you actually want to do if you need further advice.

how to build string iteratively in xquery

I need the xquery structure which is the same with java code
string temp
for(int i=0,i<string[].length,i++)
temp=temp+string[i]
for example, in xquery, I have string /a/b/c I need to something like
let $temp:=""
for $x in tokenize(string,'/')
$temp=concat($temp,$x)
return $temp
and it should return the following at each iterate
a
ab
abc
but somehow it seams that this statement $temp=concat($temp,$x) is not working. so what's the right syntax to do this? Thanks in advance
I think, you need to get the notion of declarative programming. You are trying to tell the processor what to do (like you would do in java) instead of describing the overall result. For example, if you don't use the scripting extension (which is only supported by some processors, e.g. zorba) you cannot use assignments the way you would use them in java. Think of it as the complete query describing one resulting document.
This stuff is hard to get in the beginning, but it brings huge benefits in the end (productivity, robustness, performance).
I would translate your imperative pseudo code into this one-liner:
string-join(tokenize("/a/b/c",'/'))
You can test it on try.zorba-xquery.com. I really hope this helps. Sorry, if this is not the answer you were looking for...
The $temp=conct($temp, $x) doesn't accumulate because in XQuery, that's a new variable each time through the loop. Try the following (tested in MarkLogic but uses all standard syntax):
declare function local:build($prefix, $tokens)
{
if (fn:exists($tokens)) then
let $str := fn:concat($prefix, $tokens[1])
return (
$str,
local:build($str, fn:subsequence($tokens, 2))
)
else ()
};
let $string := "/a/b/c"
return local:build("", fn:tokenize($string, "/"))

Resources