XQuery cardinality error on outputting results to text file - xquery

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.

Related

How and why is a GNAT.Strings.String_List use clause disallowed? How can you use System.Strings.String_List."&" with infix notation?

Posting for two reasons: (1) I was stuck on unhelpful compiler errors for far too long for such a simple issue and I want the next person to google those messages to come upon my (or other) answers, and (2) I still don't understand disallowing a use clause, so my own answer is really incomplete.
In order to call a program in two places with mostly the same arguments, I want to use the '&' to append to a default list inline:
declare
Exit_Code : constant Integer := GNAT.OS_Lib.Spawn (Program_Name => "gprbuild", Args => (Default_GPR_Arguments & new String'(File_Name_Parameter)));
begin
if Exit_Code /= 0 then
raise Program_Error with "Exit code:" & Exit_Code'Image;
end if;
end;
However, the compiler complains that System.Strings.String_List needs a use clause:
operator for type "System.Strings.String_List" is not directly visible
use clause would make operation legal
But inserting use System.Strings.String_List yields:
"System.Strings.String_List" is not allowed in a use clause
I also got this warning:
warning: "System.Strings" is an internal GNAT unit
warning: use "GNAT.Strings" instead
So I substituted GNAT for System in the with and the use clause and got an extra error in addition to the original 'you need a use clause for System.Strings.String_List' one:
"GNAT.Strings.String_List" is not allowed in a use clause
Why is GNAT.Strings.String_List not allowed in a use clause? Section 8.5 on use clauses doesn't seem to state anything on disallowed packages, so is this a compiler bug? Is it possible to define a new package that cannot have a use clause?
In a use clause of the form
use Name;
Name must be a package name. GNAT.Strings.String_List is a subtype name, not a package name.
There are a number of ways to invoke "&" for String_List. The simplest is to use the full name:
GNAT.Strings."&" (Left, Right)
but presumably you want to be able to use it as an operator in infix notation, Left & Right. Ways to achieve this, in decreasing specificity:
function "&" (Left : GNAT.Strings.String_List; Right : GNAT.Strings.String_List) return GNAT.Strings.String_List renames GNAT.Strings."&"; This makes this specific function directly visible.
use type GNAT.Strings.String_List; This makes all primitive operators of the type directly visible.
use all type GNAT.Strings.String_List; This makes all primitive operations of the type (including non-operator operations) directly visible.
use GNAT.Strings; This makes everything in the package directly visible.
Looks like it is a design decision. And many other packages in System follows this rule. From the s-string.ads (package specification for System.String):
-- Note: this package is in the System hierarchy so that it can be directly
-- be used by other predefined packages. User access to this package is via
-- a renaming of this package in GNAT.String (file g-string.ads).
My guess why this is done in that way: because it isn't in the Ada specification, but extension from GNAT.

Is a string value "yes/no" allowed as value for the indent parameter of the serialize function?

Using BaseX 8.6 the following use of the serialize function with a map as the second argument works fine:
serialize(<root><foo><bar>test</bar></foo></root>, map { 'indent' : 'yes'})
and outputs the indented code
<root>
<foo>
<bar>test</bar>
</foo>
</root>
However, when I try to run the same code with Saxon 9.7 or AltovaXML Spy they don't compile the query and complain about map { 'indent' : 'yes'} not being a boolean value but a string. https://www.w3.org/TR/xpath-functions-31/#func-serialize defines
indent xs:boolean? true() means "yes", false() means "no"
so I am not quite sure whether that allows only a boolean and is meant to explain its meaning in relation to the serialization values of yes/no or whether it also means using yes or no is allowed.
In BaseX, the map argument was added before it was integrated in the XQFO 3.1 specification. Back then, the most obvious choice was to use the syntax for output declarations in the query prolog (in which only strings can be used for values of serialization parameters). – The new official syntax will be made available in a future version of BaseX.

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)

print out xquery sequence and exit

Is there a way to "die" in execution flow in an xquery file and output a nicely formatted printout of a sequence variable?
I'm trying something like:
return { fn:error(xs:QName("ERROR"), $xml) }
but that doesn't quite seem to work.
Thanks!
Based on your comment (you need it for debugging) I guess you are looking for the fn:trace function, described here http://www.xqueryfunctions.com/xq/fn_trace.html
If you want to abort the execution flow and output an error in your application you should in fact use the XQuery exception handling.
Try something like this, omitting the return if this isn't part of a FLWOR expression.
...
return fn:error((), "DEBUG", $xml)
There's no need for curly braces unless you're enclosing an expression, for example <x>{ current-time() }</x>. The return expression is not enclosed.
With MarkLogic it's best to leave the first parameter of fn:error empty. That way you don't have to worry about a QName, and anyway some folks believe that it's reserved for predefined errors. MarkLogic uses the second parameter to fill in error:code, and the third parameter for data.
For more on fn:error, see http://docs.marklogic.com/fn:error and https://github.com/robwhitby/xray/pull/11

Resources