Execute function in FLWOR without using 'let' - xquery

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.

Related

How to include multiple function calls or assignments in an if-then statement

I am new to xquery and have a hard time finding more than just the basics online. I have existing code I want to change but I don't know how to include multiple function calls or variable assignments in a single if-then statement.
Say I have this block
if (fn:namespace-uri(.) = 'http://xmlns.oracle.com/communications/sce/dictionary/testNamespace'
and //tns:CreateSalesOrder/tns:ListOfSWIOrderIO/tns:SWIOrder/tns:OrderTypeCode/text() = 'Test Order'
and $isDefaultVersion)
then true()
else false()
How can I include a function call after true()?
Something like this but I dont know the exact syntax:
if (fn:namespace-uri(.) = 'http://xmlns.oracle.com/communications/sce/dictionary/testNamespace'
and //tns:CreateSalesOrder/tns:ListOfSWIOrderIO/tns:SWIOrder/tns:OrderTypeCode/text() = 'Test Order'
and $isDefaultVersion)
then true(), util:write-record('123','johndoe')
else false()
You can write:
if (...) then (true(), util:write-record('123','johndoe')) else ...
Note the parentheses.
I suspect from the name that util:write-record() is a function that has side-effects. Functions with side-effects are very tricky in XQuery and you need to understand how they are handled by your particular implementation. There is always a risk that the query optimizer will change the order of evaluation so side-effects happen in an unexpected order, or that particular calls will not happen at all because the optimizer decides the result is not needed.

Xquery result duplicated

I'm not getting the output I want. I don't understand why the result is duplicated. Can someone help me?
for $i in 1 to 2
let $rng:=random-number-generator()
let $rng1:=$rng('permute')(1 to 10)
let $rng:=$rng('next')()
let $rng2:=$rng('permute')(1 to 10)
let $rng:=$rng('next')()
let $rng3:=$rng('permute')(1 to 10)
return (string-join($rng1),string-join($rng2),string-join($rng3),",")
result:
23496815107
31018674529
31017684259
23496815107
31018674529
31017684259
The result is duplicated because of the initial for $i in 1 to 2, and because the variable $i is not actually used anywhere.
I edited the query based on your comment (getting 10 numbers). From what I understand, the difficulty here is to chain the calls (alternating between 'next' and 'permute'). Chaining calls can be done with a tail recursion.
declare function local:multiple-calls(
$rng as function(*),
$number-of-times as xs:integer) as item()* {
if($number-of-times le 0)
then ()
else
let $rng := $rng('next')
return ($rng('permute')(1 to 10),
local:multiple-calls($rng, $number-of-times - 1))
};
local:multiple-calls(random-number-generator(), 10)
Note: I am not sure if (1 to 10) is what needs to actually be passed to the call to $rng('permute'), or if it was an attempt to output ten numbers. In doubt, I haven't changed it.
The specification is here:
http://www.w3.org/TR/xpath-functions-31/#func-random-number-generator
It says:
Both forms of the function are ·deterministic·: calling the function
twice with the same arguments, within a single ·execution scope·,
produces the same results.
If you supply $i as the $seed argument to random-number-generator then the two sequences should be different.
I think I now understand what confuses you in this original query. One could indeed expect the random numbers to be generated differently for each iteration of $i.
However, XQuery is (to put it simply, with a few exceptions) deterministic. This means that the random generator probably gets initialized in each iteration with the same, default seed.
Thus, I have a second potential answer:
If you have a way to pass a different seed to $rng, you could slightly modify your initial query by constructing a seed based on $i and maybe current-dateTime() in each iteration before generating the numbers. But it will still be the same if you execute the query several times unless you involve the current date/time.

Returning multiple values in Ruby, to be used to call a function

Is it possible to return multiple values from a function?
I want to pass the return values into another function, and I wonder if I can avoid having to explode the array into multiple values
My problem?
I am upgrading Capybara for my project, and I realized, thanks to CSS 'contains' selector & upgrade of Capybara, that the statement below will no longer work
has_selector?(:css, "#rightCol:contains(\"#{page_name}\")")
I want to get it working with minimum effort (there are a lot of such cases), So I came up with the idea of using Nokogiri to convert the css to xpath. I wanted to write it so that the above function can become
has_selector? xpath(:css, "#rightCol:contains(\"#{page_name}\")")
But since xpath has to return an array, I need to actually write this
has_selector?(*xpath(:css, "#rightCol:contains(\"#{page_name}\")"))
Is there a way to get the former behavior?
It can be assumed that right now xpath func is like the below, for brevity.
def xpath(*a)
[1, 2]
end
You cannot let a method return multiple values. In order to do what you want, you have to change has_selector?, maybe something like this:
alias old_has_selector? :has_selector?
def has_selector? arg
case arg
when Array then old_has_selector?(*arg)
else old_has_selector?(arg)
end
end
Ruby has limited support for returning multiple values from a function. In particular a returned Array will get "destructured" when assigning to multiple variables:
def foo
[1, 2]
end
a, b = foo
a #=> 1
b #=> 2
However in your case you need the splat (*) to make it clear you're not just passing the array as the first argument.
If you want a cleaner syntax, why not just write your own wrapper:
def has_xpath?(xp)
has_selector?(*xpath(:css, xp))
end

XQuery concat not outputting document XML in expected way

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)

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