xquery4 1.6.7

dotnet tool install --global xquery4 --version 1.6.7
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local xquery4 --version 1.6.7
                    
This package contains a .NET tool you can call from the shell/command line.
#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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
Loading failed

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 `&#xD;` 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.