Skip to content

emit absolute iris in w3c result formats - #1630

Merged
aaj3f merged 6 commits into
mainfrom
fix/w3c-result-formats-absolute-iris
Aug 11, 2026
Merged

emit absolute iris in w3c result formats#1630
aaj3f merged 6 commits into
mainfrom
fix/w3c-result-formats-absolute-iris

Conversation

@aaj3f

@aaj3f aaj3f commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

When a SPARQL query declares a prefix, the lowerer builds a JSON-LD-style context from the prologue and the formatters compact against it. That's the intended behavior for our JSON-LD-flavored outputs and CLI display — but it also reached application/sparql-results+json, CSV, and TSV, where the specs define a uri value as the absolute IRI and the formats carry no prefix map for a consumer to expand with. A BASE-only prologue was worse still: relative IRIs in the output. SPARQL-XML was already correct on this half — it never compacted node IRIs.

FormatterConfig grows an absolute_iris profile, true for the W3C-format constructors and threaded into the compactor as a suppress flag (same pattern as the existing graph-source flag). The CLI's SPARQL display paths explicitly construct the compacting variant, so --format json/csv/tsv keep today's output — that contract is now pinned by a CLI test rather than being emergent. In the same profile, datatype omission is narrowed to xsd:string: the old allow-list dropped tags from string-backed typed literals (e.g. STRDT results), which changes term identity in a format whose values are always text.

That datatype rule applies to all four W3C result formats, XML included. SPARQL-XML gated on the same is_inferable_datatype allow-list, and more broadly than SRJ did — its single gate covered every value kind, so a stored xsd:long serialized as a bare <literal>42</literal>. <literal> content is text, so nothing about the datatype survives serialization there either, and its own test pinned the wrong behavior. Both writers now route through may_omit_datatype, so the four formats are genuinely consistent.

The W3C harness previously rendered results with an empty context, so compaction was invisible to it; it now renders with the query's own context and the full suite stays green against the fixed writer — and the datatype change greened five equality tests whose register entries had misattributed the failures. Behavior note for release: SRJ/CSV/TSV consumers that had adapted to compacted output will see absolute IRIs, and SRJ/XML consumers will see datatype on typed literals that previously arrived bare; every consumer that handles the fuller form is unaffected.

@aaj3f
aaj3f requested review from bplatz and zonotope August 11, 2026 13:32

@bplatz bplatz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One inline comment: the datatype half of this fix stops one format short of where the same bug lives.

Merge note: this and #1629 both add module lines to fluree-db-api/tests/grp_query_sparql.rs (different anchors, so probably clean) and both remove entries from testsuite-sparql/tests/registers/mod.rs (different consts). Whichever lands second should re-run the full suite rather than trusting its own pre-merge green — this PR also changes what context the harness renders results with, which #1629's newly-greened agg-count-rows-distinct runs through.

Comment thread fluree-db-api/src/format/datatype.rs Outdated
/// Whether a **string-backed** literal may be serialized without its `datatype`.
///
/// `w3c_strict` selects the rule for the W3C result serializations (SPARQL
/// Results JSON, CSV, TSV — see [`crate::FormatterConfig::absolute_iris`]).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SPARQL Results XML belongs in this list. sparql_xml.rs:330 still calls is_inferable_datatype, so it has exactly the bug the doc comment above describes: <literal> content is text, nothing about the datatype is recoverable from the serialized form, and STRDT("2", xsd:integer) comes back as a plain literal — a different RDF term than the one the query produced.

The description says "SPARQL-XML was already correct," which is true for the IRI half (it never compacted) but not for this half. Its own test inferable_datatype_omitted_long currently pins the wrong behavior.

Either route sparql_xml through omit_datatype_for_string_literal(dt, true) here, or say explicitly in the description that XML is knowingly left on the loose rule — as written the PR reads as though all four W3C formats are now consistent, and they aren't.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right, and it's worse in XML than it was in SRJ — thank you for catching this, it would have shipped a half-done boundary.

The SRJ gate only ever fired on the FlakeValue::String arm, so a stored integer escaped it by landing as FlakeValue::Long and taking a different arm that always wrote the datatype. XML has a single gate at sparql_xml.rs:330, above the match val, so it applied to every value kind: a stored xsd:long serialized as a bare <literal>42</literal>, not just the STRDT shape. So the format I'd described as "already correct" was actually the one leaking the most.

I've routed it through the shared rule. A few notes on what that touched:

  • Renamed the helper to may_omit_datatype (datatype.rs). The old omit_datatype_for_string_literal name would have been actively misleading at the XML call site, since XML gates all value kinds through it, and the doc comment now names all four W3C formats rather than three.
  • FormatterConfig::sparql_xml() now sets absolute_iris, and format_results_string threads it into the XML compactor. That flag is inert for IRIs here — write_sid_ref (sparql_xml.rs:252) streams namespace prefix + name and never consults the compactor, which is exactly why XML was already right on the IRI half — so it only carries the datatype rule. I went that way rather than hard-coding true at the call site so sparql_xml() stops being the one W3C constructor that doesn't declare itself one.
  • inferable_datatype_omitted_long is now w3c_profile_keeps_datatype_on_inferable_types, asserting the datatype is emitted, plus the string-backed xsd:integer (STRDT) shape. I also had it assert that the loose profile still omits, so the test measures the flag rather than a constant.
  • inferable_datatype_omitted_string stays as-is — a literal with no datatype and no xml:lang genuinely is an xsd:string, so that's the one omission that's exact rather than lossy.
  • The it_w3c_result_formats.rs STRDT matrix now runs the XML writer alongside SRJ, and the four-prologue matrix asserts the stored literal's datatype survives in XML too.

I checked the new assertions actually bite: reverting just the absolute_iris line on sparql_xml() fails them with <literal>2</literal> for both STRDT("2", xsd:integer) and the stored xsd:integer — i.e. exactly the term-identity change you described.

Full testsuite-sparql re-run after this commit was 1420 total / 1231 passed / 189 ignored / 0 failed — unchanged, i.e. the XML change moves no W3C test on its own. (The post-rebase number is different and better; see the merge-note reply below.) fluree-db-api is 3133 / 0, and server and CLI are green too, since the server serves sparql-results+xml.

I've also updated the PR description — the "SPARQL-XML was already correct" line now says it was correct on the IRI half only, and there's a paragraph on the datatype half covering all four formats. You were right that as written it read as though the four were consistent when they weren't.

@bplatz bplatz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Left some feedback below to look at before merge

@aaj3f
aaj3f force-pushed the fix/w3c-result-formats-absolute-iris branch from 46358b7 to 27fbced Compare August 11, 2026 18:41
@aaj3f

aaj3f commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

@bplatz — on the merge note:

You called this exactly right, and #1629 landed first, so this PR is the lands-second party — I've rebased onto main and re-run rather than relying on the pre-merge green.

Result at the rebased head: 1420 total / 1232 passed / 188 ignored / 0 failed, against 1231 / 189 before the rebase. That +1 passed / −1 ignored is agg-count-rows-distinct — i.e. the specific test you flagged is now running through the changed harness rendering, and it passes. So the interaction you were worried about is exercised rather than assumed.

The textual merge was clean, and I checked both overlap points rather than trusting that: grp_query_sparql.rs ended up with all four of #1629's module lines plus this PR's it_w3c_result_formats, and registers/mod.rs kept #1629's agg-count-rows-distinct removal alongside this PR's open-eq-07/08/10/11/12 removals (only open-eq-05/06 remain in that block). cargo check --workspace --all-targets and cargo clippy --workspace --all-targets are both clean post-restack — worth doing explicitly, since a zero-conflict rebase can still leave a broken tree when the two sides touch the same call surface.

@aaj3f
aaj3f merged commit d3cffc6 into main Aug 11, 2026
15 checks passed
@aaj3f
aaj3f deleted the fix/w3c-result-formats-absolute-iris branch August 11, 2026 19:38
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.

2 participants