fix(format): CURIE-align virtual graph-source IRIs in sparql_json (F9) - #1499
Closed
aaj3f wants to merge 11 commits into
Closed
fix(format): CURIE-align virtual graph-source IRIs in sparql_json (F9)#1499aaj3f wants to merge 11 commits into
aaj3f wants to merge 11 commits into
Conversation
aaj3f
force-pushed
the
perf/r2rml-pr5-scan-topk
branch
from
July 15, 2026 17:06
a8de70e to
83347b7
Compare
Rider-1 mechanism investigation: SPARQL-JSON compaction is @context/PREFIX-driven via ContextCompactor::compact_id, which never reads the namespace-code map. The virtual gap is a formatter dispatch (Binding::Iri written raw at sparql.rs:333/487) vs native's Binding::Sid -> compact_id_sid -> compact_id_iri. Approved Option B (seed the snapshot namespace map) is a no-op for the hash; corrected fix is to route Binding::Iri through compact_id_iri. Pending lead re-review.
…ites + measured option-C exposure Pre-code proof obligation (lead rider): enumerated all Binding::Iri construction sites workspace-wide. Native-reachable sites exist (eval/value.rs:522 BIND(IRI)/UUID, sparql_results.rs SERVICE, graph.rs GRAPH ?g, bm25/vector search), so the unconditional shared-formatter fix is native-visible — blocks it pending scope decision. Measured exposure: option C regresses zero test gates (W3C runner uses empty ParsedContext => compact_id_iri no-op, plus CURIE-expansion+isomorphic compare; native 54/54 byte-identical, no corpus GRAPH/SERVICE/BIND(IRI)). Options C (two-line, needs AJ sign-off on narrow native consistency change) vs A (provenance-scoped enum variant). Awaiting lead C-or-A + AJ.
… design D (emit IriMatch from r2rml/operator.rs) is UNSOUND: IriMatch carries a real originating-ledger primary_sid that eq_for_join (join.rs:1045) compares for same-ledger bindings (binding.rs:778); virtual IRIs have no SID, so fabricating one breaks virtual joins. A real SID = option B (ruled out). Recommend option A via the existing GraphDb.graph_source_id (None for native, Some for virtual) -> QueryResult.from_graph_source -> IriCompactor flag -> the two sparql.rs Iri arms. Structural native untouchability, no enum change. Awaiting lead nod on the marker design.
…s-link #1496 F9 SHARPENED namespace-map claim refuted via dated addendum (preserved, not rewritten): compaction is @context/PREFIX-driven (compact.rs:60), never the namespace map; real gap is the raw Binding::Iri formatter arm. New F16: other formatters (jsonld/sparql_xml/typed/delimited) render virtual graph-source IRIs raw — consistency follow-up needing own gates. Native provenance-rendering question filed as #1496; cross-linked both ways. Summary table rows updated.
Virtual (R2RML) predicate/type IRIs rendered as full IRIs in SPARQL-results JSON while native compacted them to CURIEs from the query PREFIX, because the formatter's Binding::Iri arm was written raw whereas native's Binding::Sid arm runs through compact_id_iri. compact_id is @context/PREFIX-driven and never consults the namespace map, so the fix is a formatter dispatch change, scoped to graph-source results and sparql_json only. - QueryResult.from_graph_source (set in query_view_with_r2rml_options from the resolved view's graph_source_id); native/mixed-dataset paths stay false. - IriCompactor.compact_graph_source_iris flag, built into the compactor only in the sparql_json entry points; every other formatter keeps raw IRIs (F16). - The two sparql.rs Binding::Iri arms compact via compact_id_iri when set; DOM/streaming parity preserved. - Kill switch FLUREE_R2RML_CURIE_ALIGN (default on) for the revert differential and production safety. Gate (DW_SF01): q002 8cd3964a->d65ea385, q042 0615a095->33b91b54 now MATCH the native oracle byte-for-byte (heads verified: every changed cell full-IRI->CURIE; rdf:type, data.fluree.dev/edw/ references, and literals unchanged). No re-bless: the expected/ oracles were blessed from native and already carry the CURIE form, so F9 was 'virtual diverges from oracle' and the fix closes it with zero baseline churn. Blast radius exactly q002+q042 among full-hash-gated queries (rerun-proven: every other full-gated query is switch-invariant, OFF==ON). Full virtual corpus ON compare 0 mismatches; native 54/54 clean; W3C green; fluree-db-api lib 664/664 incl. 3 new tests (context-driven compaction proof, flag-scoping guard, DOM/stream parity with a Binding::Iri row).
…ate) q029 (rows_only, unordered LIMIT) DNF'd at the 120s deadline in the F9 gate; standalone healthy-network rerun completed ok/rows=100/~150s. Correct but slow; F9-neutral (formatter-only, DNF'd identically on/off); upstream scan variance. Revisit for its own F-number if still ~150s post-PAT-swap.
…e q029 note q029 DNF in the F9 gate was a harness deadline mismatch, not a regression: manifest timeout_s=180, completes ~142-150s (scan-bound/re-scan-amplified, 09-stacked-rebaseline §3), but the gate applied a blanket 120s. F17 records the mechanism (UNION absorbs the LIMIT budget -> both FACT_WEB_EVENT branch scans re-driven ~253x -> 1.94M reads for 100 rows) and remedy (budget propagation through UNION, mechanism-class D; mirror PR-5 wrapper-forwarding; likely subsumed by F14/PR-4d). pf9_gate.sh now honors per-query timeout_s.
Clean 3-rep hot baseline (supersedes contaminated pf9_virt_on): 42/50 ok-queries already <=3s; 8-query tail with mechanism+remedy. Headline: q031 72s is a REGRESSION - PR-8b query-scoped parent-memo present + unit tests pass but q031's plan no longer reaches it (1448 un-memoized dim re-scans); A/B FLUREE_R2RML_LIMIT_PUSHDOWN=0 -> scan_table 1448->8; PR-4c /PR-7/topk refuted; leading culprit PR-5 GraphOperator forwarding refactor (51aafc7), confirm via bisect @2a07bbbc4. Plus q029 (F17), q016 (F14/4d), q038 un-fused COUNT (48s/1row, wall in generic eval), q041 ref-OR not pushed, q053/q017 anti-joins. Slate to AJ first.
…nesty pass Bisect + PR-8b gate forensics exonerate ALL PRs: the 188ms 'sentinel' was a fully-warm reps=3 artifact (q029 also 156ms in that phase, vs 125s cold); the gate's recorded q031 was 65s/scan_table 1448 = same as now. So q031 is cache-sensitive (188ms warm <-> 65-72s thrashed), pre-existing since PR-8b - NOT a regression; retract the PR-5 indictment. File F18 (parent-memo x LIMIT-pushdown interaction). Corrected the burndown end-state (low-single-digit s cache-thrashed, NOT the 0.2s warm-only figure) + slate. Gate-protocol lesson: the 188ms sentinel hid the 65s cold cost in the same gate (~350x optimism) -> sentinels must be cache-thrashed; full-corpus baseline per stacked-PR head.
… source The horizon-demo 'enterprise' source carried a stale/revoked stored PAT (client_secret) -> OAuth unauthorized_client 401 (not a grant/scope gap; ICEBERG_READER already has full ENTERPRISE_DEMO.DW). Re-registered a harness-owned source in ~/vbench/.fluree via 'fluree iceberg map enterprise-sf20-v' (16 DW.* TriplesMaps) with a fresh client_secret; retarget fluree_home -> ~/vbench/.fluree, alias -> enterprise-sf20-v. Verified: exec-one virtual-sf20 q001 = ok, 2236 rows, 6.2s (401->ok).
…spelling The new write_term Binding::Iri arm (block form) tripped clippy:: semicolon_if_nothing_returned (a workspace-denied lint) at sparql.rs:337/339 — add the two semicolons. curie_align_enabled accepted only "0" as falsy while the switch family accepts 0/false/off/no, so FLUREE_R2RML_CURIE_ALIGN=false silently stayed on — widen to the family set. Verified q002/q042 carry no stale F9 markers (hash_gate=full, no expected_status).
aaj3f
force-pushed
the
fix/f9-virtual-curie
branch
from
July 15, 2026 17:11
e1ac131 to
f815669
Compare
Contributor
Author
|
Consolidating at maintainer request. #1499 (F9 — CURIE-alignment of virtual graph-source IRIs in |
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.
What
Virtual (R2RML / Iceberg graph-source) queries rendered predicate and
@typeIRIs as full IRIs in SPARQL-results JSON, while native compacted them to CURIEs from the query'sPREFIXdeclarations. This closes that divergence (finding F9) so virtual output matches native.q002 / q042 flip — now byte-identical to the native oracle:
8cd3964a…d65ea385…d65ea385…0615a095…33b91b54…33b91b54…Head row-diff verified: every changed cell is exactly full-IRI → CURIE —
<edw:openDate>,<edw:name>,<edw:channel>,<edw:storeId>,<edw:storeType>,<edw:geography>,<edw:regionManager>, and therdf:typeobject<edw:Store>. Therdf:typepredicate stays a full IRI (nordf:prefix declared), thedata.fluree.dev/edw/reference objects stay full (that namespace isn't declared), and every literal is unchanged — all matching the oracle. No baseline re-bless: theexpected/oracles were blessed from native and already carry the CURIE form, so virtual now simply matches them.Why (mechanism)
SPARQL-results-JSON compaction is
@context/PREFIX-driven:ContextCompactor::compact_idkeys only on the query's declared prefixes and never consults the namespace-code map. Native predicates arrive asBinding::Sidand run throughcompact_id_sid → compact_id_iri(compacts); virtual predicates arrive asBinding::Iri(a raw graph-source string) and were written verbatim atformat/sparql.rs(streamingwrite_termand DOMformat_binding), bypassing the compactor. The fix routesBinding::Irithrough the samecompact_id_irinative already uses — so parity holds by using the identical compaction call.(This corrects an earlier "namespace-map gap" hypothesis: native renders
rdf:typeanddata.fluree.dev/edw/references full despite both being in the ledger namespace map, precisely because compaction tracks declared prefixes, not the map.)Scope
from_graph_source && FLUREE_R2RML_CURIE_ALIGN && format == SparqlJson. Native results (Binding::Sid) are untouched; only virtual/graph-sourceBinding::Iriinsparql_jsonis affected.QueryResult.from_graph_sourceis set in exactly one place —query_view_with_r2rml_options, from the resolved view'sgraph_source_id. Nativequery_with_optionsand mixed-dataset paths leave it false (raw, unchanged); the native-ledger-reached-with-.with_r2rml()-but-no-mapping edge is documented at the set site.falseand keep raw graph-source IRIs — that cross-format consistency work is tracked as register F16.FLUREE_R2RML_CURIE_ALIGN(default on) forces the pre-F9 raw rendering — powers the revert differential and gives production a revert lever.Design alternatives were refuted at review gates: encoding at the R2RML operator (hot-path namespace work) and emitting
Binding::IriMatch(unsound — it carries a load-bearing originating-ledgerprimary_sidthateq_for_joincompares; virtual IRIs have none). The broader question of whether native should also compact its constructed/federated/GRAPH IRIs (a provenance-dependent-rendering finding) is deferred to #1496.Blast radius — exactly q002 + q042
Among full-hash-gated queries, the switch moves only q002 and q042 — rerun-proven. The raw ON-vs-OFF differential listed more, which decompose cleanly:
hash_gate=rows_onlyqueries (unorderedLIMIT) vary run-to-run by subset nondeterminism, not the switch (they project only literals — the switch is a definitional no-op); every other full-gated query was re-run with the switch off on a healthy network and is byte-identical to its switch-on hash. A transient Snowflake network reset during the original OFF phase errored a block of queries (confirmed transport, not the switch); all completed cleanly on rerun.Gates
fluree-db-apilib: 664/664 including three new tests — a context-driven-compaction mechanism proof (namespace map irrelevant;rdf:typestays full), a flag-off-raw / flag-on-CURIE scoping guard on the sameBinding::Irirow, and a DOM↔streaming parity assertion with aBinding::Irirow so both arms must flip together (closing the diagnosed partial-fix no-op loophole).Stacked on
perf/r2rml-pr5-scan-topk.