XQuery: Count the arguments of a function - xquery

I have an XQuery function.
declare function local:helloWorld($param1 as xs:string, $param2 as xs:string) as element() {
Now, I want to say if is possible to count the parameters passed to my function.
Is possible?
Thanks in advance

In standard XQuery it is not possible.
However, the next Zorba release (Version 2.0) which will be available very soon will include a library for introspection which exactly does what you want. As an example, on the current svn version of Zorba the following query:
import module namespace sctx = "http://www.zorba-xquery.com/modules/introspection/sctx";
declare function local:helloWorld($param1 as xs:string, $param2 as xs:string) as xs:string {
fn:concat($param1, $param2)
};
sctx:function-arguments-count(xs:QName("local:helloWorld"))
will return "2" (as expected). There is also a module to do reflection, so you can build your function calls completely dynamically. But the price you pay is of course, that this code will not be portable to other XQuery engines anymore.

There's no function to count the number of parameters passed to a function. The number of parameters is fixed by the function declaration. It can't change depending on the input.
This article explains functions.

Related

How to get flowId in transform activity in Oracle SOA 12c

Oracle 12c we have flow id method to keep track of service request. In assign activity I'm able to get flow id using ora:getFlowId() method but in transform activity I don't see such method. So, my question is how can I get this flow id in transform activity ?.
Consider something like this. Pass the ora:getFlowId() as a parameter to the xquery then assign it inside where ever you want.
xquery version "1.0" encoding "utf-8";
(:: OracleAnnotationVersion "1.0" ::)
declare variable $flowId as xs:string external;
declare function local:func($flowId as xs:string)
as element() {
<result>
{$flowId}
</result>
};
local:func($flowId as xs:string)
This might not answer your question to get flowId directly. But it might be a workaround for your problem.
Hope it helps
Assign a hard code value to flow id in your transform. after transform just have an assign in which override the already populated flow id with the function. This should do the trick.
There is no specific function to get the same in the transformation.

How to populate a map in XQuery?

I have an XML file in the following link
I want to populate a map that with ids of movies and the average of their corresponding ratings. I am trying the following command from this site
for $doc in db:open("movies","movies.xml")/movies/movie
let $map:= map:map()
let $key := map:put($map, $doc/#id, avg($doc/ratings/child::node()))
return $map
However, it doesn't run. I get an exception, Expecting variable declaration. What am I doing wrong?
EDIT:
I am trying it the following command
let $map:=map{}
for $doc in db:open("movies","movies.xml")/movies/movie
return
map:put($map,
$doc/#id,avg($doc/ratings/child::node())
)
I get seperate maps
As outlined in my comment, the map syntax MarkLogic uses has nothing to do with the XQuery 3.1 term map. MarkLogic implemented this map structure before the official XQuery spec defined it (this structure is rather new). So you can use the map as documented in the MarkLogic docs only when you use MarkLogic, it is a proprietary extension.
On the other hand, BaseX supports the XQuery 3.1 construct map. Therefore you should follow what is documented at the BaseX documentation. And this makes it actually shorter and side-effect free. You could e.g. do:
map:merge(for $movie in db:open("movies","movies.xml")/movies/movie return map:entry($movie/#id, avg($movie/ratings/child::node())))
You could also use map:put, but it doesn't make much sense for this use case as you would create a new map each time you call the map:put() function.

Drop the "fn:" in MarkLogic functions?

Is there a way with MarkLogic to not have to prefix every single fn: function with that prefix? I've seen lots of codes on the Internet that show me that I don't need it.
Things can get rather verbose, you know? fn:not(fn:contains(...)), instead of not(contains(...))
Thoughts?
Thanks!
Like you, I prefer not to type fn: in front of all my fn:functions.
In normal XQuery main modules you don't need the fn: prefix because that's the default function namespace and used for all unprefixed functions. You do however need fn: in library modules because they change their default function namespace to that of the library module namespace. This means the library functions can call each other without any prefix.
But you can change it back! Here's the header code to do the switch back.
xquery version "1.0-ml";
module namespace util = "http://markmail.org/util";
declare default function namespace "http://www.w3.org/2005/xpath-functions";
Or if you're on the older 0.9-ml:
xquery version "0.9-ml"
module "http://markmail.org/util"
declare namespace util = "http://markmail.org/util"
default function namespace = "http://www.w3.org/2003/05/xpath-functions"
It puts the module in a given namespace, assigns util to that namespace, then assigns the default back to the normal fn: one.
After this switch, function calls and definitions without a prefix will default to the fn: prefix; that means all functions in the util library should explicitly use a util: prefix. (Personally, I think that's cleaner anyway.)

xquery call a maintenance function within another function

Using MarkLogic Xquery, I have a function (admin:add-collection-to-publication) which calls another maintenance function ( admin:check-collections-exists) which checks for an element's presence and if its not present then it creates that particular element.
The way I call the maintenance function is with a let. This seems like a weird way, to do this it requires creating an unused variable. Should I instead return a sequence with the call to admin:check-collections-exists being the first item in the sequence then the subsequent processing being the second element? Just looking for the standard elegant way to do this. My functions are:
declare function admin:add-collection-to-publication($pub-name, $collection-name)
{
(:does this publication have a COLLECTIONS element?:)
let $unnecessary-variable := admin:check-collections-exists($pub-name)
(:now go and do what this function does:)
return "do some other stuff then return"
};
declare function admin:check-collections-exists($pub-name)
{
if(fn:exists($pubs-node/pub:PUBLICATION[pub:NAME/text()=$pub-name]/pub:COLLECTIONS))
then
"exists"
else
xdmp:node-insert-child($pubs-node/pub:PUBLICATION[pub:NAME/text()=$pub-name],<pub:COLLECTIONS/>)
};
Using a sequence is not reliable. MarkLogic will most likely attempt to evaluate the sequence items in parallel, which could cause the creating to happen at 'same' time or even after the other work. The best approach is indeed to use a let. The let's are always evaluated before the return. Note though that let's can be evaluated in parallel as well, but the optimizer is smart enough to detect dependencies.
Personally, I often use unused variables. For example to insert logging statements, in which case I have one unused variable name that I reuse each time:
let $log := xdmp:log($before)
let $result := do:something($before)
let $log := xdmp:log($result)
You could also use a very short variable name like $_. Or you could reconsider actually giving the variable a sensible name, and use it after all, even though you know it never reaches the else
let $exists :=
if (collection-exists()) then
create()
else true()
return
if ($exists) then
"do stuff"
else () (: never reached!! :)
HTH!

element() vs. node() in XQuery

Can someone tell me the exact difference between node() and element() types in XQuery? The documentation states that element() is an element node, while node() is any node, so if I understand it correctly element() is a subset of node().
The thing is I have an XQuery function like this:
declare function local:myFunction($arg1 as element()) as element() {
let $value := data($arg1/subelement)
etc...
};
Now I want to call the function with a parameter which is obtained by another function, say functionX (which I have no control over):
let $parameter := someNamespace:functionX()
return local:myFunction($parameter)
The problem is, functionX returns an node() so it will not let me pass the $parameter directly. I tried changing the type of my function to take a node() instead of an element(), but then I can’t seem to read any data from it. $value is just empty.
Is there some way of either converting the node to an element or should am I just missing something?
EDIT: As far as I can tell the problem is in the part where I try to get the subelement using $arg1/subelement. Apparently you can do this if $arg1 is an element() but not if it is a node().
UPDATE: I have tested the example provided by Dimitre below, and it indeed works fine, both with Saxon and with eXist DB (which is what I am using as the XQuery engine). The problem actually occurs with the request:get-data() function from eXist DB. This function gets data provided by the POST request when using eXist through REST, parses it as XML and returns it as a node(). But for some reason when I pass the data to another function XQuery doesn’t acknowledge it as being a valid element(), even though it is. If I extract it manually (i.e. copy the output and paste it to my source code), assign it to a variable and pass it to my function all goes well. But if I pass it directly it gives me a runtime error (and indeed fails the instance of test).
I need to be able to either make it ignore this type-check or “typecast” the data to an element().
data() returning empty for an element just because the argument type is node() sounds like a bug to me. What XQuery processor are you using?
It sounds like you need to placate static type checking, which you can do using a treat as expression. I don't believe a dynamic test using instance of will suffice.
Try this:
let $parameter := someNamespace:functionX() treat as element()
return local:myFunction($parameter)
Quoting from the 4th edition of Michael Kay's magnum opus, "The treat as operator is essentially telling the system that you know what the runtime type is going to be, and you want any checking to be deferred until runtime, because you're confident that your code is correct." (p. 679)
UPDATE: I think the above is actually wrong, since treat as is just an assertion. It doesn't change the type annotation node(), which means it's also a wrong assertion and doesn't help you. Hmmm... What I really want is cast as, but that only works for atomic types. I guess I'm stumped. Maybe you should change XQuery engines. :-) I'll report back if I think of something else. Also, I'm curious to find out if Dimitre's solution works for you.
UPDATE #2: I had backpedaled here earlier. Can I backpedal again? ;-) Now my theory is that treat as will work based on the fact that node() is interpreted as a union of the various specific node type annotations, and not as a run-time type annotation itself (see the "Note" in the "Item types" section of the XQuery formal semantics.) At run time, the type annotation will be element(). Use treat as to guarantee to the type checker that this will be true. Now I wait on bated breath: does it work for you?
EXPLANATORY ADDENDUM: Assuming this works, here's why. node() is a union type. Actual items at run time are never annotated with node(). "An item type is either an atomic type, an element type, an attribute type, a document node type, a text node type, a comment node type, or a processing instruction type."1 Notice that node() is not in that list. Thus, your XQuery engine isn't complaining that an item has type node(); rather it's complaining that it doesn't know what the type is going to be (node() means it could end up being attribute(), element(), text(), comment(), processing-instruction(), or document-node()). Why does it have to know? Because you're telling it elsewhere that it's an element (in your function's signature). It's not enough to narrow it down to one of the above six possibilities. Static type checking means that you have to guarantee—at compile time—that the types will match up (element with element, in this case). treat as is used to narrow down the static type from a general type (node()) to a more specific type (element()). It doesn't change the dynamic type. cast as, on the other hand, is used to convert an item from one type to another, changing both the static and dynamic types (e.g., xs:string to xs:boolean). It makes sense that cast as can only be used with atomic values (and not nodes), because what would it mean to convert an attribute to an element (etc.)? And there's no such thing as converting a node() item to an element() item, because there's no such thing as a node() item. node() only exists as a static union type. Moral of the story? Avoid XQuery processors that use static type checking. (Sorry for the snarky conclusion; I feel I've earned the right. :-) )
NEW ANSWER BASED ON UPDATED INFORMATION: It sounds like static type checking is a red herring (a big fat one). I believe you are in fact not dealing with an element but a document node, which is the invisible root node that contains the top-level element (document element) in the XPath data model representation of a well-formed XML document.
The tree is thus modeled like this:
[document-node]
|
<docElement>
|
<subelement>
and not like this:
<docElement>
|
<subelement>
I had assumed you were passing the <docElement> node. But if I'm right, you were actually passing the document node (its parent). Since the document node is invisible, its serialization (what you copied and pasted) is indistinguishable from an element node, and the distinction was lost when you pasted what is now interpreted as a bare element constructor in your XQuery. (To construct a document node in XQuery, you have to wrap the element constructor with document{ ... }.)
The instance of test fails because the node is not an element but a document-node. (It's not a node() per se, because there's no such thing; see explanation above.)
Also, this would explain why data() returns empty when you tried to get the <subelement> child of the document node (after relaxing the function argument type to node()). The first tree representation above shows that <subelement> is not a child of the document node; thus it returns the empty sequence.
Now for the solution. Before passing the (document node) parameter, get its element child (the document element), by appending /* (or /element() which is equivalent) like this:
let $parameter := someNamespace:functionX()/*
return local:myFunction($parameter)
Alternatively, let your function take a document node and update the argument you pass to data():
declare function local:myFunction($arg1 as document-node()) as element() {
let $value := data($arg1/*/subelement)
etc...
};
Finally, it looks like the description of eXist's request:get-data() function is perfectly consistent with this explanation. It says: "If its not a binary document, we attempt to parse it as XML and return a document-node()." (emphasis added)
Thanks for the adventure. This turned out to be a common XPath gotcha (awareness of document nodes), but I learned a few things from our detour into static type checking.
This works perfectly using Saxon 9.3:
declare namespace my = "my:my";
declare namespace their = "their:their";
declare function my:fun($arg1 as element()) as element()
{
$arg1/a
};
declare function their:fun2($arg1 as node()) as node()
{
$arg1
};
my:fun(their:fun2(/*) )
when the code above is applied on the following XML document:
<t>
<a/>
</t>
the correct result is produced with no error messages:
<a/>
Update:
The following should work even with the most punctuential static type-checking XQuery implementation:
declare namespace my = "my:my";
declare namespace their = "their:their";
declare function my:fun($arg1 as element()) as element()
{
$arg1/a
};
declare function their:fun2($arg1 as node()) as node()
{
$arg1
};
let $vRes := their:fun2(/*)
(: this prevents our code from runtime crash :)
return if($vRes instance of element())
then
(: and this assures the static type-checker
that the type is element() :)
my:fun(their:fun2(/*) treat as element())
else()
node() is an element, attribute, processing instruction, text node, etc.
But data() converts the result to a string, which isn't any of those; it's a primitive type.
You might want to try item(), which should match either.
See 2.5.4.2 Matching an ItemType and an Item in the W3C XQuery spec.
Although it's not shown in your example code, I assume you are actually returning a value (like the $value you are working with) from the local:myFunction.

Resources