Repository navigation
fix(turtle): resolve RFC 3986 §5.3 fragment refs against @base - #1399
Merged
Merged
Conversation
resolve_iri dropped the fragment when resolving a relative reference such as <#Name> against an @base, collapsing every <#A>/<#B> to the same base IRI (base minus its fragment). Per RFC 3986 §5.2.2 the resolved fragment is always the reference's own fragment and is never inherited from the base. Split the reference fragment off up front, resolve scheme/authority/ path/query against the remainder, and re-attach #<fragment> during §5.3 recomposition. Same-document refs (<> and <#frag>) now keep the base path and query but drop the base fragment; <> resolves to base-minus-fragment. Adds parser unit tests including the regression guard that <#A> and <#B> resolve to distinct IRIs, plus RFC 3986 §5.4.1 query+fragment cases. Refs #1395
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(turtle): resolve RFC 3986 §5.3 fragment refs against
@baseSummary
The Turtle parser's
resolve_iridropped the fragment when resolving arelative IRI reference such as
<#Name>against an@base. As a result, every<#DimDate>,<#DimProduct>, … collapsed to the same IRI (the base with itsfragment stripped). This is a direct RFC 3986 §5.2.2 / §5.3 violation and is the
root cause of issue #1395: an idiomatic
@base+<#Name>multi-TriplesMapR2RML mapping silently collapsed into a single TriplesMap / single logical table.
This PR is Phase 1 — the foundational parser fix at the bottom of the
fluree/db ↔ Snowflake-managed-Iceberg stack. It is fully self-contained in the
fluree-graph-turtlecrate.Root cause
Per RFC 3986 §5.2.2, the fragment of a resolved reference is always the
reference's own fragment (
T.fragment = R.fragment); it is never inherited fromthe base. The previous
resolve_iri(a) returnedNonefor the fragment on the#-only branch, (b) never appended#<fragment>during component recomposition,and (c) used helpers (
split_path_query) that strip everything after#. So thereference fragment was discarded on every relative resolution, and the empty
<>reference incorrectly returned the base with its fragment intact. Existing
R2RML fixtures all used absolute TriplesMap IRIs, which short-circuit
resolution entirely, so the bug was never exercised.
What changed
fluree-graph-turtle/src/parser.rs—resolve_irionly:#) and thefragment-less remainder up front (RFC 3986 §5.2.1). Scheme/authority/path/query
are resolved against the remainder; the reference fragment is re-attached during
recomposition (§5.3).
<>and<#frag>) now inherit the base path andquery but not the base fragment (the base fragment is naturally dropped by
parse_iri_components).#<fragment>in the result assembly when (and only when) the referenceitself carries a fragment.
<>reference, which hadreturned the full base including its fragment;
<>now correctly resolves tobase-minus-fragment.
No helper signatures changed and no other call sites were touched. Absolute-IRI
and prefixed-name inputs are completely unaffected.
Tests added
Six new parser unit tests in
fluree-graph-turtle/src/parser.rs(all driventhrough the real
parsepath viaparse_to_graph):test_base_fragment_refs_resolve_to_distinct_iris— the regression guard forTurtle parser drops the fragment resolving
<#name>against@base, collapsing multi-TriplesMap R2RML mappings to one table #1395: with@base <http://example.org/edw>,<#A>and<#B>resolve to twoDISTINCT IRIs (
…/edw#A,…/edw#B).test_base_relative_path_with_fragment—<DimDate#col>→…/DimDate#col.test_fragment_ref_against_base_with_fragment— a base that itself has afragment:
<#new>replaces it;<other#f>carries its own fragment.test_empty_ref_against_fragmented_base_drops_fragment—<>against afragmented base → base minus fragment.
test_ref_without_fragment_does_not_inherit_base_fragment— a ref with nofragment does NOT inherit the base's.
test_query_and_fragment_resolution_rfc3986— RFC 3986 §5.4.1 query+fragmentexamples against
@base <http://a/b/c/d;p?q>(<#s>,<?y#s>,<g#s>,<g?y#s>,<>).Results
cargo test -p fluree-graph-turtle— 77 passed, 0 failed (incl. the 6 new + 1 doctest).cargo clippy -p fluree-graph-turtle --all-targets— clean, no warnings.cargo fmt -p fluree-graph-turtle— applied.cargo test -p fluree-db-r2rml— 46 passed, 0 failed;cargo check -p fluree-db-api— clean.Risk / compatibility
resolve_iriis the resolution path for all relative IRIs in Turtle ingestion(R2RML loader,
fluree-db-memory/turtle_io.rs). Fragments were always droppedbefore, so any
@base+ fragment Turtle was already parsed incorrectly — the fixis strictly more correct. The change is conservative:
only
.ttlcarrying@baseis the MCP server's own.fluree-memory/repo.ttl(not a test fixture). The W3C SPARQL testsuite uses its own separate base
resolver (
testsuite-sparql/src/result_format.rs), not thisresolve_iri.test_empty_iri_resolves_to_base) uses a basewith no fragment and stays green.
Phases 2–4 from issue #1395 (R2RML dup-IRI hardening,
Tables: NUX, O(1) mapselection) are intentionally out of scope for this PR and stack on top.
Fixes #1395