xquery4 1.6.7
dotnet tool install --global xquery4 --version 1.6.7
dotnet new tool-manifest
dotnet tool install --local xquery4 --version 1.6.7
#tool dotnet:?package=xquery4&version=1.6.7
nuke :add-package xquery4 --version 1.6.7
xquery
Command-line XQuery 3.1/4.0 processor for .NET. Query XML documents from the terminal using the PhoenixmlDb XQuery engine.
Installation
dotnet tool install -g xquery4
Usage
# Query an XML file
xquery '//book/title' library.xml
# Count elements
xquery 'count(//item)' catalog.xml
# Read from a query file
xquery -f transform.xq input.xml
# Query a directory of XML files
xquery 'collection()//product[price > 50]' ./data/
# JSON output
xquery -o json 'map { "count": count(//item) }' data.xml
# Read from stdin
cat data.xml | xquery '//item/@name'
# Show execution plan
xquery --plan 'for $x in 1 to 10 return $x * $x'
# Show timing breakdown
xquery --timing '//item' large-catalog.xml
Features
- XQuery 3.1/4.0 — FLWOR, maps/arrays, higher-order functions, string constructors
- Multiple output methods — adaptive, XML, text, JSON
- Context item — input XML is available as
.(standard XQuery) - Multiple sources — files, directories, URLs, stdin
- Full prolog support — namespaces, variable/function declarations, serialization options
- Execution plans — inspect how queries are compiled and optimized
- Timing — built-in performance profiling
Documentation
Full documentation at phoenixml.dev
License
Apache-2.0
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.6.7 | 49 | 8/24/2026 |
| 1.6.6 | 63 | 8/23/2026 |
| 1.6.5 | 60 | 8/21/2026 |
| 1.6.2 | 78 | 8/16/2026 |
| 1.6.1 | 72 | 7/31/2026 |
| 1.6.0 | 82 | 7/27/2026 |
| 1.5.5 | 90 | 7/12/2026 |
| 1.5.4 | 79 | 7/9/2026 |
| 1.5.3 | 81 | 7/8/2026 |
| 1.5.2 | 80 | 7/7/2026 |
| 1.5.1 | 94 | 6/30/2026 |
| 1.5.0 | 80 | 6/28/2026 |
| 1.4.7 | 87 | 6/25/2026 |
| 1.4.6 | 96 | 6/17/2026 |
| 1.4.5 | 91 | 6/17/2026 |
| 1.4.4 | 86 | 6/14/2026 |
| 1.4.3 | 88 | 6/10/2026 |
| 1.4.2 | 88 | 6/6/2026 |
| 1.4.1 | 96 | 6/4/2026 |
| 1.3.15 | 106 | 5/22/2026 |
Six fixes to XPath 4.0 function behaviour, one to numeric rounding, and one new parser
option. Four came from Martin Honnen's testing; the rest from an audit prompted by his
reports, and from the conformance work that took QT3 from 76.56% to 95.11%.
### `fn:highest` and `fn:lowest` take a collation second and a key third
XPath 4.0 §14.5 declares three arguments — `$input`, `$collation`, `$key`. This engine
declared arity 1-2 with the KEY second, so `highest#3` did not exist and
`highest($seq, $key)` bound a function into the collation slot, where it was read as a
string and discarded. Reported by Martin Honnen.
Two further faults his examples could not show. Every key went through `Convert.ToDouble`,
so any non-numeric input threw an unhandled `System.FormatException` and took the process
down — `highest(("apple","banana"))` was a .NET crash, not an XQuery error. And
`highest((1,5,3), "…/collation/codepoint")` returned the right answer by accident, having
ignored the collation entirely.
### Adaptive serialization of arrays, from the `xquery` tool
`[1,2]` printed as `12`, so `partition(…)` output looked wrong when the partitioning was
correct. Martin narrowed this himself to a sequence-of-arrays serialization fault, which is
exactly what it was.
The engine's own serializer had always been right; the CLI carried a SECOND copy in which
the two runtime representations were swapped — `object?[]` (a sequence) got array brackets
while `List<object?>` (an array) was flattened. That is why the same query was correct
through `xslt` and wrong through `xquery`.
### `fn:all-equal`, `fn:all-different`, `fn:duplicate-values`
These take an optional collation as their second argument; none of the three had it, and
all three compared the *lexical form* of the atomized value, so values of different types
compared equal whenever their strings matched — `all-equal((1, "1"))` was true. Numeric
promotion is unaffected: `1`, `1.0` and `1e0` remain equal.
### `fn:QName` accepts an `xs:anyURI` namespace
F&O function conversion includes URI promotion, honoured everywhere the conversion
machinery runs. `fn:QName` hand-rolled its type check and rejected `xs:anyURI` — in exactly
the case the function exists for, since a namespace URI is the natural thing to hold in one.
### XSD regex: character-class subtraction is no longer mistaken for POSIX syntax
`[\i-[:]]` — the canonical idiom for an NCName start character, and the most common use of
subtraction anywhere — was rejected as "POSIX character class syntax is not supported",
against a pattern using no POSIX syntax at all. Both forms begin `[:`; the only thing
separating them is whether an unescaped `-` precedes the bracket.
### Schema-defined simple types as `cast` / `castable` targets
import schema namespace s = "…" at "…xsd";
'IB123' castable as s:restrictedString
failed with `XPST0081: Unbound namespace prefix: s`. Two causes. `import schema namespace
p = "uri"` BINDS p in the statically known namespaces (§4.11) and the prefix was extracted
into the AST and never registered — so the prefix genuinely was unbound, and every use of
it failed, not only casts. And types were modelled as a fixed enum of built-in XSD types
with no representation for a schema-defined one.
Facet validation — pattern, enumeration, length, bounds, unions, lists — is delegated to
the schema provider rather than reimplemented. `instance of` and node tests against schema
types still need typed value annotations and are not supported.
### `fn:round-half-to-even` decides ties from the binary value
round-half-to-even(250.0250e0, 2) gave 250.02, must be 250.03
The nearest double to 250.025 is 250.0250000000000056…, strictly above the midpoint, so it
is not a tie. `Math.Round`'s double overload applies a decimal-style correction that
manufactures ties binary does not have. Rounding is now exact integer arithmetic over the
double's mantissa and exponent, so a spurious tie is unrepresentable.
### New: `XQueryParserFacade.NormalizeLineEndings`
Defaults true, which is correct for a query file. XSLT sets it false: an XPath expression
has already been through an XML parser, where `
` inside a string literal is DATA that
XML 1.0 §2.11 exempts from line-ending normalization. Applying XQuery's query-source rule
a second time rewrote it.
### Notes
`PhoenixmlDb.Core` moves to 1.6.7 (metadata prefix `phxm` → `dbxml`, prefix only). The
`xquery` tool embeds the XSLT engine from the previous release, 1.6.6 in this train — the
CLI needs `PhoenixmlDb.Xslt` for `fn:transform` and `PhoenixmlDb.Xslt` needs
`PhoenixmlDb.XQuery`, so it trails by one by construction. The library itself depends only
on Core.
### `fn:highest` and `fn:lowest` take a collation second and a key third
XPath 4.0 §14.5 declares three arguments:
fn:highest($input as item()*,
$collation as xs:string? := fn:default-collation(),
$key as (fn(item()) as xs:anyAtomicType*)? := fn:data#1) as item()*
This engine declared arity 1-2 with the KEY second, so `highest#3` did not exist and
`highest($seq, $key)` bound a function into the collation slot, where it was read as a string,
discarded, and quietly produced an unkeyed answer. Reported by Martin Honnen.
Two further faults, which his examples could not have shown:
- Every key went through `Convert.ToDouble`, so any non-numeric input threw an unhandled
`System.FormatException` and took the process down. `highest(("apple","banana"))` was a
.NET crash, not an XQuery error.
- `highest((1,5,3), "…/collation/codepoint")` returned `5` and looked right. It was right by
accident: the collation cast to a function, came back null, and was ignored.
Both now go through the machinery `fn:sort` already uses, so strings, dates and mixed numerics
order correctly and the collation is honoured. Ties are unchanged — every item at the extreme
is returned, in input order.
### `fn:all-equal`, `fn:all-different` and `fn:duplicate-values` compare values
These take an optional collation as their second argument. None of the three had it, and all
three compared the *lexical form* of the atomized value, so values of different types compared
equal whenever their strings matched:
all-equal((1, "1")) was true -> now false
all-different((1, "1")) was false -> now true
Numeric promotion is unaffected: `1`, `1.0` and `1e0` remain equal. Under a collation,
`fn:duplicate-values` reports the value **as first written** rather than the later occurrence
that revealed the duplication, matching `fn:distinct-values`.
Found by auditing every collation-taking function after the `fn:highest` report. Not reported.
### `fn:partition`'s result serializes as arrays from the `xquery` tool
Adaptive output printed an array as its bare members — `12` rather than `[1,2]` — so Martin
Honnen's `partition()` example produced `12/34/56/7` where Saxon produces `[1,2]` and so on.
The partitioning itself was fixed in 1.6.6; this is the other half of the same report, which
he had suspected: "not sure whether the result is wrong or the serialization fails". Both were.
The engine's own `XQueryResultSerializer` was correct and well covered. The CLI carries a
SECOND serializer, and in it the two runtime representations were swapped: `object?[]` (a
sequence) was given array brackets, while `List<object?>` (an array) fell through to the
sequence branch and was flattened. Both directions are now corrected, and a
`PhoenixmlDb.XQuery.Cli.Tests` project exists so the tool's serializer is covered at all — it
had no tests before.
Not addressed: with `indent="yes"` Saxon breaks array members across lines and this engine
keeps them on one. The values match; the whitespace does not.