Skip to content

Fix XQuery 3.1 casting and numeric comparison compliance#6165

Closed
joewiz wants to merge 8 commits intoeXist-db:developfrom
joewiz:feature/xq31-compliance-push
Closed

Fix XQuery 3.1 casting and numeric comparison compliance#6165
joewiz wants to merge 8 commits intoeXist-db:developfrom
joewiz:feature/xq31-compliance-push

Conversation

@joewiz
Copy link
Copy Markdown
Member

@joewiz joewiz commented Mar 22, 2026

Summary

Push XQuery 3.1 XQTS compliance (HEAD test suite) from 85.3% to 88.8% (+652 tests) by fixing error codes, URI resolution, and parser edge cases.

Casting & type fixes

  • Implement XQuery 3.1 casting table (Type.isCastable): Pre-validates cast operations. Impossible casts now correctly raise XPTY0004 instead of FORG0001. Fixes ~580 tests.
  • Fix fn:min/fn:max mixed numeric comparisons: Allow cross-numeric-family comparisons by checking numeric union membership. Fixes ~60 tests.
  • Fix FOCA0002 for NaN/INF → integer/decimal: NaN/INF casts now correctly raise FOCA0002 instead of FORG0001. Fixes ~44 tests.
  • Fix xs:boolean → integer subtypes: Route through IntegerValue for proper range validation. Fixes ~5 tests.
  • Fix generic ERROR codes to FORG0001: StringValue.checkType() and Base64BinaryValueType now use ErrorCodes.FORG0001. Fixes ~58 tests.

fn:json-doc URI resolution

  • Fix relative URI resolution: Resolve relative URIs against the static base URI before falling back to the database URI prefix. Fixes ~295 tests (misc-JsonTestSuite: 0% → 93%).
  • Fix FOJS0001 error code: JSON parse errors (JsonParseException) now correctly raise FOJS0001 instead of FOUT1170.

fn:parse-ietf-date improvements

  • Case-insensitive day/month names: Accept "SAT", "aug", etc.
  • Single-digit hours: Grammar allows 1-2 digits.
  • Optional whitespace before timezone: Allow "19:36:01GMT".
  • 24:00:00 normalization: Normalize to 00:00:00 of the next day.
  • Fixes ~8 tests.

XQTS HEAD Results

Metric Baseline After
Pass rate 85.3% (26563/31158) 88.8% (27215/30654)
Failures 3528 2459
Errors 160 157
Net improvement +652 tests

Note: test totals vary between runs due to BrokerPool shutdown timeouts.

Test plan

  • XQTS HEAD full suite run shows improvement from 85.3% to 88.8%
  • exist-core unit tests: BUILD SUCCESS (0 failures)
  • Codacy/PMD local analysis: no new warnings introduced
  • CI verification

🤖 Generated with Claude Code

joewiz and others added 4 commits March 22, 2026 14:47
Per XPath F&O 3.1 Section 19, casting between types that have no valid
conversion path (e.g., xs:time to xs:date, xs:anyURI to xs:hexBinary)
should raise XPTY0004, not FORG0001. FORG0001 is reserved for when the
cast IS allowed but the specific value is invalid.

Add Type.isCastable() implementing the XQuery 3.1 casting table to
pre-validate cast operations before attempting them. CastExpression
now checks castability first and raises XPTY0004 for impossible casts.
CastableExpression returns false immediately for impossible casts.

Fixes ~580 XQTS 3.1 test failures in prod-CastExpr and related sets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fn:min and fn:max threw FORG0006 when given mixed numeric types (e.g.,
xs:integer and xs:double) because getCommonSuperType() returned
ANY_ATOMIC_TYPE for cross-numeric-family types. Per XQuery 3.1, mixed
numeric types should be promoted to a common type before comparison.

Add a check: if both types are members of the xs:numeric union, allow
the comparison to proceed to the existing numeric promotion code.

Fixes ~60 XQTS 3.1 test failures in fn-min and fn-max.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When casting xs:double NaN, INF, or -INF to xs:integer or xs:decimal,
eXist incorrectly raised FORG0001. Per XPath F&O 3.1 Section 4.1.16,
FOCA0002 should be raised when the cast value is outside the target
type's value space (NaN and infinities have no integer/decimal
representation).

Fixes ~44 XQTS 3.1 test failures in prod-CastExpr.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
xs:boolean cast to integer subtypes like xs:nonPositiveInteger or
xs:negativeInteger hit the default case and threw an incorrect error.
Now routes through IntegerValue conversion which properly validates
the value against the subtype's range (e.g., true=1 is invalid for
xs:nonPositiveInteger, producing the correct FORG0001).

Also fixes the default error code from XPTY0004 to FORG0001 for
BooleanValue, since any cast that reaches convertTo() has already
passed the casting table validation.

Fixes ~5 XQTS 3.1 test failures in prod-CastExpr.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@joewiz joewiz requested a review from a team as a code owner March 22, 2026 18:49

public DecimalValue toDecimalValue() throws XPathException {
if (isNaN() || isInfinite()) {
throw conversionError(Type.DECIMAL);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no longer using the existing conversionError()method?


public IntegerValue toIntegerSubType(final int subType) throws XPathException {
if (isNaN() || isInfinite()) {
throw conversionError(subType);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no longer using the existing conversionError()method?


public IntegerValue toIntegerValue() throws XPathException {
if (isNaN() || isInfinite()) {
throw conversionError(Type.INTEGER);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no longer using the existing conversionError()method?


public DecimalValue toDecimalValue() throws XPathException {
if (isNaN() || isInfinite()) {
throw conversionError(Type.DECIMAL);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was the method call conversionError() removed and code duplication introduced? we could pass in an additional error code for the other usages instead in order to reduce code duplication.


public IntegerValue toIntegerValue() throws XPathException {
if (isNaN() || isInfinite()) {
throw conversionError(Type.INTEGER);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as in toDecimalValue()


public IntegerValue toIntegerSubType(final int subType) throws XPathException {
if (isNaN() || isInfinite()) {
throw conversionError(subType);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as in toDecimalValue()

*
* @return true if the cast may succeed (for some values), false if the cast can never succeed
*/
public static boolean isCastable(final int sourceType, final int targetType) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address the the NPath complexity of 8960, current threshold is 200.

DoubleValue: reuse conversionError() with an error code parameter
instead of inlining XPathException construction in toDecimalValue(),
toIntegerValue(), and toIntegerSubType().

Type.isCastable(): extract primitive casting table lookup into
isPrimitiveCastable() and factor repeated type-group checks into
isNumericTarget(), isDateTimeTarget(), isGregorianTarget(), and
isStringOrUntypedAtomic() helpers. Reduces NPath complexity from
8960 to below the 200 threshold.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@joewiz
Copy link
Copy Markdown
Member Author

joewiz commented Mar 22, 2026

[This response was co-authored with Claude Code. -Joe]

Thanks for the review, @reinhapa! Addressed both points in f92f3e6:

  1. DoubleValue: Added an overloaded conversionError(ErrorCode, int) so the NaN/INF paths reuse the existing helper instead of inlining. The original single-arg conversionError(int) now delegates to it.

  2. Type.isCastable(): Extracted the primitive casting table into isPrimitiveCastable() and factored the repeated type-group checks into isNumericTarget(), isDateTimeTarget(), isGregorianTarget(), and isStringOrUntypedAtomic() helpers. NPath complexity drops from 8960 to below the 200 threshold (confirmed via local Codacy/PMD run).

joewiz and others added 3 commits March 22, 2026 21:01
StringValue.checkType() threw XPathException without an ErrorCodes
constant for xs:language, xs:Name, xs:NCName, xs:ID, xs:IDREF,
xs:ENTITY, and xs:NMTOKEN validation failures. The runner reported
these as exerr:ERROR instead of FORG0001.

Base64BinaryValueType.verifyString() had the error code as a string
in the message ("FORG0001: Invalid base64 data") instead of using
ErrorCodes.FORG0001 as the structured error code parameter.

Fixes ~58 XQTS HEAD test failures in prod-CastExpr and
misc-CombinedErrorCodes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two issues prevented fn:json-doc from working with relative URIs:

1. Relative URIs (no scheme) were unconditionally prefixed with the
   eXist database URI prefix, bypassing filesystem resolution. Now
   resolves relative URIs against the static base URI first, matching
   the behavior expected by the XQTS test suite.

2. JSON parse errors (JsonParseException) were caught by the generic
   IOException handler and reported as FOUT1170 (source not found)
   instead of FOJS0001 (JSON syntax error). Now catches
   JsonParseException separately.

Fixes ~295 XQTS HEAD test failures in misc-JsonTestSuite (0% to 93%)
and improves fn-json-doc results.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Several IETF date parsing issues:

- Day and month names were case-sensitive (rejected "SAT", "aug").
  Now uses case-insensitive matching.
- Hours required exactly 2 digits but the IETF grammar allows 1-2
  (digit digit?). Changed parseInt minimum from 2 to 1.
- Whitespace between time and timezone was mandatory but should be
  optional per the grammar.
- Seconds detection relied on whitespace check instead of colon
  check, failing when timezone immediately followed time.
- 24:00:00 was rejected; now normalized to 00:00:00 of the next day.
- Timezone is now optional (grammar: (S? timezone)?).

Fixes ~8 XQTS HEAD test failures in fn-parse-ietf-date.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@joewiz
Copy link
Copy Markdown
Member Author

joewiz commented Mar 24, 2026

[This response was co-authored with Claude Code. -Joe]

The CDataIntergationTest.cdataWebDavApi failure is unrelated to this PR's changes. The test passes locally on both develop and this branch. This appears to be a flaky CI test (likely timing/port-binding in the embedded WebDAV server).

@joewiz
Copy link
Copy Markdown
Member Author

joewiz commented Apr 6, 2026

[This comment was co-authored with Claude Code. -Joe]

Closing — superseded by #6207 (v2/xq31-compliance-fixes).

This work has been consolidated into a clean v2/ branch as part of the eXist-db 7.0 PR reorganization. The new PR includes all commits from this PR plus additional related work, with reviewer feedback incorporated where applicable. See the reviewer guide for the full context.

@joewiz joewiz closed this Apr 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants