Skip to content

(R2RML) Error on Colliding TriplesMap IRIs & Report Table Count - #1401

Merged
aaj3f merged 1 commit into
mainfrom
fix/r2rml-multitable-hardening
Jun 30, 2026
Merged

(R2RML) Error on Colliding TriplesMap IRIs & Report Table Count#1401
aaj3f merged 1 commit into
mainfrom
fix/r2rml-multitable-hardening

Conversation

@aaj3f

@aaj3f aaj3f commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Stacked PR. Base branch: fix/turtle-fragment-resolution (#1399)

Summary

This is Phases 2 & 3 of issue #1395 — the R2RML half of the multi-table
collapse fix, layered on top of PR-1's Turtle parser fragment fix.

  • Phase 2 (hardening): make a silent multi-TriplesMap collapse impossible.
    When two or more rr:TriplesMap subjects resolve to the same IRI, the extractor
    now returns a hard error instead of merging them (first-wins logical table /
    subject map, union of every predicate-object map) into plausible-but-wrong data.
  • Phase 3 (reporting): surface table coverage at registration. The R2RML
    create result and fluree iceberg map now report Tables: N (names…) next to
    TriplesMaps: N, so a 16-table star schema that has silently collapsed shows
    Tables: 1 immediately rather than failing mysteriously at query time.

Phase 1 (the parser fix) is the base branch. Phase 4 (wiring the operator's
prebuilt find_maps_for_* selection indexes for O(1) map selection) is
intentionally deferred and out of scope here.

What changed

Phase 2 — collapse is now a hard error

fluree-db-r2rml/src/loader/extractor.rsMappingExtractor::extract_all:

  • Each rr:TriplesMap IRI is extracted exactly once (a repeated
    a rr:TriplesMap triple for an already-seen subject is harmless redundancy and
    is skipped).
  • Before extracting, a new ensure_no_collision guard counts the rr:logicalTable
    and rr:subjectMap edges on the subject. A well-formed TriplesMap has exactly
    one of each; more than one is the signature of two definitions merged onto one
    IRI
    , and is now rejected with R2rmlError::DuplicateTriplesMap. This signal is
    independent of graph bag/set semantics (the merged subject always carries the
    distinct logical-table blank nodes) and produces no false positive for a
    redundant type triple on a single well-formed map.

fluree-db-r2rml/src/error.rs — new R2rmlError::DuplicateTriplesMap(String)
variant whose message names the colliding IRI and points at the usual cause
(relative <#fragment> subjects collapsing against @base).

Phase 3 — Tables: N (…) at registration

  • fluree-db-api/src/graph_source/result.rsR2rmlCreateResult gains
    table_count: usize and table_names: Vec<String> (sorted, distinct).
  • fluree-db-api/src/graph_source/r2rml.rs — both the CAS-stored
    (R2rmlMappingInput::Content) and address-validated
    (R2rmlMappingInput::Address) paths now derive the table list from the compiled
    mapping via a small sorted_table_names helper; validate_r2rml_mapping_from_address
    returns (count, table_names).
  • fluree-db-cli/src/commands/iceberg.rs — prints
    Tables: N (DW.X, DW.Y, …) after TriplesMaps: on both the local and
    remote (fluree iceberg map) output paths, via a shared format_table_summary
    helper (falls back to bare N when no names are available, e.g. an unvalidated
    address mapping).
  • fluree-db-server/src/routes/iceberg.rs — the /iceberg/map JSON response
    (IcebergMapResponse) carries optional table_count / table_names so the
    remote CLI path renders the same line. No behavior change for the raw (non-R2RML)
    Iceberg branch.

Tests added

In fluree-db-api/tests/it_graph_source_r2rml.rs:

  • test_base_fragment_multi_table_compiles_to_distinct_tables — the offline
    regression guard. An idiomatic @base <http://ex/edw> + <#DimDate> /
    <#DimProduct> / <#FactSales> fixture compiles to 3 distinct TriplesMaps
    (…edw#DimDate, …edw#DimProduct, …edw#FactSales) over 3 distinct tables
    (DW.DIM_DATE, DW.DIM_PRODUCT, DW.FACT_SALES) — not one merged map.
  • engine_e2e_base_fragment_scans_non_first_table — drives the engine with a
    recording provider (the MultiTableMockProvider pattern). Querying a predicate
    owned only by the non-first map scans DW.DIM_PRODUCT and never
    DW.DIM_DATE (the first map's table — the exact collapse symptom), returning the
    correct rows.
  • test_colliding_triples_map_iris_error — the Phase-2 hardening test: a document
    with two rr:TriplesMap subjects resolving to one IRI fails to compile with a
    Duplicate TriplesMap IRI error naming the colliding subject.

Results

  • cargo test -p fluree-db-r2rml46 passed, 0 failed (+1 doctest);
    --features turtle55 passed, 0 failed (+1 doctest).
  • cargo test -p fluree-db-api --features iceberg — full suite green; the touched
    binary it_graph_source_r2rml is 32 passed, 0 failed (2 ignored — external
    Polaris/MinIO), including the 3 new tests. No regressions across the rest of the
    api suite (640 unit + all grp_*/it_* integration binaries, 0 failures).
  • cargo clippy -- -D warnings (all --all-targets) — clean on
    fluree-db-r2rml --features turtle, fluree-db-api --features iceberg,
    fluree-db-cli --features iceberg, and fluree-db-server.
  • cargo fmt — applied. cargo check -p fluree-db-cli --features iceberg — clean.

Risk / compatibility

  • Phase 2 may surface a previously "working" mapping as an error. That mapping
    was already silently corrupt (first-wins table + union projection); a loud error
    is the correct, strictly-safer outcome. Only mappings that actually collide are
    affected — distinct-IRI multi-table mappings (the norm) are untouched.
  • Phase 3 is purely additive: two new result fields and an extra output line;
    the server response fields are skip_serializing_if = "Option::is_none", and the
    only R2rmlCreateResult constructor is updated. The raw-Iceberg path is
    unchanged.
  • All existing R2RML fixtures use absolute or now-correctly-resolved IRIs and stay
    green.

Fixes #1395

Phase 2 (hardening): MappingExtractor::extract_all now rejects a mapping
where two or more rr:TriplesMap subjects resolve to the same IRI. Such a
collapse (classically an idiomatic relative <#fragment> subject resolved
against @base) previously merged silently -- first-wins logical table and
subject map, union of every predicate-object map -- yielding
plausible-but-wrong triples that scan only the first table. The collision
is now a hard R2rmlError::DuplicateTriplesMap.

Phase 3 (reporting): R2rmlCreateResult gains table_count and table_names,
populated from the compiled mapping on both the CAS-stored and
address-validated paths. `fluree iceberg map` now prints
`Tables: N (DW.X, DW.Y, ...)` alongside `TriplesMaps: N` for local and
remote output, so mapping a 16-table star schema visibly shows 16 tables,
not 1. The /iceberg/map server response carries the new fields for remote
parity.

Tests: an idiomatic `@base <http://ex/edw>` + `<#DimX>`/`<#FactY>`
multi-table fixture asserts N distinct TriplesMaps over N distinct tables
and that a non-first map scans its own table rather than the first map's;
a hardening test asserts a colliding-IRI document errors.

Phase 1 (Turtle parser fragment fix) is on the base branch; Phase 4
(operator index wiring) is deferred.

Refs #1395
@aaj3f
aaj3f requested a review from bplatz June 30, 2026 20:03
Base automatically changed from fix/turtle-fragment-resolution to main June 30, 2026 20:14

@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.

👏

@aaj3f
aaj3f merged commit 7d13a5b into main Jun 30, 2026
9 checks passed
@aaj3f
aaj3f deleted the fix/r2rml-multitable-hardening branch June 30, 2026 20:57
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.

Turtle parser drops the fragment resolving <#name> against @base, collapsing multi-TriplesMap R2RML mappings to one table

2 participants