how to build string iteratively in xquery - 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, "/"))

Related

xquery "last" function with other selectors

I am curious how to use the last() function in Xquery along with other "selectors" (not sure of the correct lingo). Here is what I have (and this works):
for $lastscoreplay in //bbgame/plays/period[last()]/play[#action="GOOD"]
return <datarow>
<lastscoreplay>{data($lastscoreplay/#uni)}</lastscoreplay>
</datarow>
But, I want to select the last play where the #action equals GOOD. If that makes sense. I would think I could do this but it does not work:
for $lastscoreplay in //bbgame/plays/period[last()]/play[last()][#action="GOOD"]
return <datarow>
<lastscoreplay>{data($lastscoreplay/#uni)}</lastscoreplay>
</datarow>
Sorry I am still rather new to Xquery and coding in general so sorry if this is easy and I just don't see the problem. Thanks for any help!
You query get's the last play, regardless of #action, and then filters that last play by [#action="GOOD"]. Instead, you should first filter by #action, then get the last one:
//bbgame/plays/period[last()]/play[#action="GOOD"][last()]
The full query:
for $lastscoreplay in //bbgame/plays/period[last()]/play[#action="GOOD"][last()]
return
<datarow>
<lastscoreplay>{ data($lastscoreplay/#uni) }</lastscoreplay>
</datarow>
In some cases the accepted answer won't work, if that happens try this alternative:
let $all := /bbgame/plays/period[last()]/play[#action="GOOD"]
return $all[last()]

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.

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 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.

Loops in SML/NJ

I'm very new to SNL/NJ and was wondering how I could accomplish the following:
foo(stuff,counter)
{
while(counter > 0)
{
bar(stuff);
counter-1;
}
return;
}
Something like this, but how do I decrement?:
foo(stuff,counter) =
while counter > 0 do bar(stuff) ??? // how do I decrement counter here?
I agree with the other contributors that you should generally use recursion instead of loops and mutation to do this in a functional language.
If you really wanted to use mutation and loops though, you would need to use a data structure called a reference which is a kind of "mutable cell". You allocate the reference with the ref function, passing it the initial contents. You access the contents using the ! operator. And you set new contents using the := operator. So the literal translation of your code above would be something like the following. As you can see, the syntax is really ugly and that is another reason why people avoid it.
fun foo (stuff, counter_start) =
let
val counter = ref counter_start
in
while !counter > 0 do (
bar stuff;
counter := !counter - 1
)
end;
In a functional program, a mutable variable turns into a parameter, typically to a nested helper function.
Since in your example, the thing being mutated is aleady parameter, no helper function is needed. Your code becomes
fun foo stuff counter =
if counter > 0 then
( bar stuff
; foo stuff (counter-1)
)
else
()
Of course this code is still terribly imperative... The call bar stuff is executed purely for side effect. Not very ML-ish.
Short answer: You don't. In functional programming, you generally never modify variables, which means a loop is impossible. Instead, you can implement the same using recursion. Similarly, since you don't, generally speaking, have side effects, function calls only make sense if they return data. So bar(stuff) is probably not very useful. It has no way of affecting the rest of the application. In a functional programming style, your bar() function should be called on different data each time, and return something that the rest of the application can act on.
(ML does allow side effects in certain cases, but to keep things simple, let's ignore that for now)
What exactly are you trying to achieve? (What do you need to loop over, what do the functions do?
If you provide a bit more detail, we can explain more specifically how you should write the program. But as it is, your program simply doesn't make sense in a functional style.
I don't know ML, but this is some ML-like pseudo code:
fun foo stuff 0 = return ()
| foo stuff counter = (bar stuff; foo stuff (counter - 1))
I don't know how to "chain" commands in ML; the semicolon is just a placeholder.
Generally, you wouldn't loop. I would rather expect the usual higher order functions. When you get used to those, manually writing a loop will feel like coding assembler.
edit: fixed code according to comment

Resources