XQuery 3.0
This article provides a summary of the most important features of the XQuery 3.0 Recommendation.
Enhanced FLWOR Expressions
Most clauses of FLWOR expressions can be specified in an arbitrary order: additional let
and for
clauses can be put after a where
clause, and multiple where
, order by
and group by
statements can be used. This means that many nested loops can now be rewritten to a single FLWOR expression.
for $country in db:get('factbook')//country
where $country/@population > 100000000
for $city in $country//city> 1000000
group by $name := $country/name[1]
count $id
return <country id='{ $id }' name='{ $name }'>{ $city/name }</country>
group by
FLWOR expressions have been extended to include the group by clause, which is well-established in SQL. group by
can be used to apply value-based partitioning to query results:
for $ppl in doc('xmark')//people/person
let $ic := $ppl/profile/@income
let $income :=
if ($ic < 30000) then
"challenge"
else if ($ic >= 30000 and $ic < 100000) then
"standard"
else if ($ic >= 100000) then
"preferred"
else
"na"
group by $income
order by $income
return element { $income } { count($ppl) }
This query is a rewrite of Query 20 of the XMark Benchmark Suite to use group by
. The query partitions the customers based on their income.
<challenge>4731</challenge>
<na>12677</na>
<preferred>314</preferred>
<standard>7778</standard>
In contrast to the relational GROUP BY statement, the XQuery counterpart concatenates the values of all non-grouping variables that belong to a specific group. In the context of our example, all nodes in //people/person
that belong to the preferred
partition are concatenated in $ppl
after grouping has finished. You can see this effect by changing the return statement to:
...
return element { $income } { $ppl }
Result:
<challenge>
<person id="person0">
<name>Kasidit Treweek</name>
…
<person id="personX">
…
</challenge>
Moreover, a value can be assigned to the grouping variable. This is shown in the following example:
XQuery:let $data :=
<xml>
<person country='USA' name='John'/>
<person country='USA' name='Jack'/>
<person country='Germany' name='Johann'/>
</xml>
for $person in $data/person
group by $country := $person/@country
return element persons {
attribute country { $country },
for $name in $person/@name
return element name { data($name) }
}
Result:
<persons country="USA">
<name>John</name>
<name>Jack</name>
</persons>
<persons country="Germany">
<name>Johann</name>
</persons>
count
The count
clause enhances the FLWOR expression with a variable that enumerates the iterated tuples.
for $n in (1 to 10)[. mod 2 = 1]
count $c
return <number count="{ $c }" number="{ $n }"/>
allowing empty
The allowing empty
provides functionality similar to outer joins in SQL:
for $n allowing empty in ()
return 'empty? ' || empty($n)
window
Window clauses provide a rich set of variable declarations to process sub-sequences of iterated tuples. An example:
for tumbling window $w in (2, 4, 6, 8, 10, 12, 14)
start at $s when true()
only end at $e when $e - $s eq 2
return <window>{ $w }</window>
More information on window clauses can be found in the specification.
Functions
Function Items
One of the most distinguishing features added in XQuery 3.0 are function items, also known as lambdas or lambda functions. Function items act as functions, but can also be passed to and from other functions and expressions. This feature makes functions first-class citizens of the language.
ExamplesFunction items can be obtained in three different ways:
- Declaring a new inline function:
Result:let $f := function($x, $y) { $x + $y } return $f(17, 25)
42
- Getting the function item of an existing (built-in or user-defined) XQuery function. The arity (number of arguments) has to be specified as there can be more than one function with the same name:
Result:let $f := math:pow#2 return $f(5, 2)
25
- Partially applying another function or function item. This is done by supplying only some of the required arguments, writing the placeholder
?
in the positions of the arguments left out. The produced function item has one argument for every placeholder.
Result:let $f := substring(?, 1, 3) return ( $f('foo123'), $f('bar456') )
foo bar
Higher-Order Functions
A higher-order function is a function that takes other functions as arguments, or that returns functions as results. A simple example is fn:for-each
, which applies a function to each item of a sequence. In the following code, 10 integers are passed to a function that computes and returns the sqaure root:
for-each(
1 to 10,
function($n) { $n * $n }
)
Higher-order functions allow you to extract common patterns of behavior and abstract them into a library function.
See Higher-Order Functions for a list of most available higher-order standard functions.
Function Types
Like every XQuery item, function items have a sequence type. It can be used to specify the arity (number of arguments the function takes) and the argument and result types.
The most general function type is function(*)
. The following query for example goes through a list of XQuery items and, if it is a function item, prints its arity:
for $item in (1, 'foo', fn:concat#3, function($a) { 42 * $a })
where $item instance of function(*)
return fn:function-arity($item)
Result: 3 1
The notation for specifying argument and return types is quite intuitive, as it closely resembles the function declaration. The XQuery function
declare function local:char-at(
$string as xs:string,
$pos as xs:integer
) as xs:string {
substring($string, $pos, 1)
};
for example has the type function(xs:string, xs:integer) as xs:string
. It isn’t possible to specify only the argument and not the result type or the other way round. A good place-holder to use when no restriction is wanted is item()*
, as it matches any XQuery value.
Function types can also be nested. As an example we take local:on-sequences
, which takes a function defined on single items and makes it work on sequences as well:
declare function local:on-sequences(
$action as function(item()) as item()*
) as function(item()*) as item()* {
for-each($action, ?)
};
With fn:for-each
, a function is applied to each item of a sequence. The type of local:on-sequences
on the other hand is easily constructed, if a bit long:
function(function(item()) as item()*) as function(item()*) as item()*
Standard Functions
The following functions have been added to the Standard Functions of the language:
analyze-string
, available-environment-variables
, element-with-id
, environment-variable
, filter
, fold-left
, fold-right
, for-each
, for-each-pair
, format-date
, format-dateTime
, format-integer
, format-number
, format-time
, function-arity
, function-lookup
, function-name
, generate-id
, has-children
, head
, innermost
, outermost
, parse-xml
, parse-xml-fragment
, path
, serialize
, tail
, unparsed-text
, unparsed-text-available
, unparsed-text-lines
, uri-collection
Some functions have been updated:
document-uri
, string-join
, node-name
, round
, data
Simple Map Operator
The simple map operator !
provides a compact notation for applying the results of a first to a second expression: the resulting items of the first expression are bound to the context item one by one, and the second expression is evaluated for each item. The map operator may be used as replacement for FLWOR expressions:
(: Simple map notation :)
(1 to 10) ! element node { . },
(: FLWOR notation :)
for $i in 1 to 10
return element node { $i }
In contrast to path expressions, the results of the map operator will not be made duplicate-free and returned in document order.
Try/Catch
The try/catch construct can be used to handle errors at runtime:
Example:try {
1 + '2'
} catch err:XPTY0004 {
'Typing error: ' || $err:description
} catch * {
'Error [' || $err:code || ']: ' || $err:description
}
Result: Typing error: '+' operator: number expected, xs:string found.
Within the scope of the catch clause, a number of variables are implicitly declared, giving information about the error that occurred:
$err:code
error code$err:description
: error message$err:value
: value associated with the error (optional)$err:module
: URI of the module where the error occurred$err:line-number
: line number where the error occurred$err:column-number
: column number where the error occurred
Switch
The switch statement is available in many other programming languages. It chooses one of several expressions to evaluate based on its input value.
Example:for $fruit in ("Apple", "Pear", "Peach")
return switch ($fruit)
case "Apple" return "red"
case "Pear" return "green"
case "Peach" return "pink"
default return "unknown"
Result: red green pink
The expression to evaluate can correspond to multiple input values.
Example:for $fruit in ("Apple", "Cherry")
return switch ($fruit)
case "Apple"
case "Cherry"
return "red"
case "Pear"
return "green"
case "Peach"
return "pink"
default
return "unknown"
Result: red red
Expanded QNames
A QName can be prefixed with the letter Q
, the namespace URI wrapped in curly braces and the local name.
Q{http://www.w3.org/2005/xpath-functions/math}pi()
returns the number πQ{java:java.io.FileOutputStream}new("output.txt")
creates a new Java file output stream
Namespace Constructors
New namespaces can be created via so-called 'Computed Namespace Constructors'.
element node { namespace pref { 'http://url.org/' } }
String Concatenations
Two vertical bars ||
(also named pipe characters) can be used to concatenate strings. This operator is a shortcut for the concat()
function.
'Hello' || ' ' || 'Universe'
External Variables
Default values can be attached to external variable declarations. This way, an expression can also be evaluated if its external variables have not been bound to a new value.
declare variable $user external := "admin";
"User:", $user
Serialization
Serialization parameters can be defined within XQuery expressions. Parameters are placed in the query prolog and need to be specified as option declarations, using theoutput
prefix.
Example:
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:omit-xml-declaration "no";
declare option output:method "xhtml";
<html/>
Result: <?xml version="1.0" encoding="UTF-8"?><html></html>
In BaseX, the output
prefix is statically bound and can thus be omitted. Note that all namespaces need to be specified when using external APIs, such as XQJ.
Context Item
The context item can be specified in the prolog of an XQuery expression:
Example:declare context item := document {
<xml>
<text>Hello</text>
<text>World</text>
</xml>
};
for $t in .//text()
return string-length($t)
Result: 5 5
Annotations
XQuery 3.0 introduces annotations to declare properties associated with functions and variables. For instance, a function may be declared %public, %private, or %updating.
Example:declare %private function local:max($x1, $x2) {
if ($x1 > $x2) then $x1 else $x2
};
local:max(2, 3)
Changelog
Version 7.7- Added: Enhanced FLWOR Expressions
- Added: Simple Map Operator
- Added: Annotations
- Updated: Expanded QNames
- Added: String Concatenations