test(sparql): burn-down Wave 0 — harness hygiene, parser accept-more, lexer/formatter, expression high-yield (−80 registers)#1452
Open
aaj3f wants to merge 25 commits into
Open
test(sparql): burn-down Wave 0 — harness hygiene, parser accept-more, lexer/formatter, expression high-yield (−80 registers)#1452aaj3f wants to merge 25 commits into
aaj3f wants to merge 25 commits into
Conversation
…nt terminator A blank-node label must not end in a dot (BLANK_NODE_LABEL grammar), so _:o6. is the label o6 followed by the statement terminator. The lexer greedily consumed the dot into the label, then hard-errored (unexpected character '_'), rejecting valid Turtle and blocking the four W3C json-res tests' data load (#1444). Keep the greedy per-char scan byte-identical (spec-valid interior consecutive dots like _:a..b keep lexing exactly as before, per the adversarial correction in ROADMAP 1.1-9) and rewind any trailing dots after the scan so they lex as Dot tokens. The rewind branch only runs on inputs that previously errored, so the change strictly widens what lexes. Adds lexer unit tests (trailing dot, space form, interior/consecutive dots byte-identity, EOF, multiple trailing dots) and a fluree-db-api integration test inserting _:x.-shaped Turtle and querying it back through the JSON-LD surface (query-surface parity).
…e both-constant existence row (pp16, pp36) Two contained defects in the property-path operator (fluree-db-query/src/property_path.rs), per the burn-down roadmap PR-PP row and residual-eval.md §3.2: - pp16 — a zero-length path (`elt*` / `elt?`) with both endpoints unbound built its self-pair universe only from the path predicate's edges, and only from ref terms. Per SPARQL 1.1 §18.4 the universe is every term of the active graph — subjects and objects of any predicate, literals included. `compute_closure` (simple and composite branches) now emits self-pairs from a policy-filtered full-graph term scan, and the internal pair buffers carry full `Binding` terms instead of `(Sid, Sid)` so literal endpoints survive to output. Scoped to the SPARQL-lowered shape: typed, unbounded `*`/`?`. Cypher var-length shapes (untyped wildcard, hop-bounded) keep node-set semantics, and the `*`/`+` traversal itself (adjacency build, BFS, visited sets, bounded layering) is untouched — this changes what depth-0 emits, not how paths are walked. - pp36 — a both-constant path (`:a0 (:p)* :a1`) computes its existence row, but the operator materialized it through `Batch::new` with an empty schema, which infers len from the missing first column and collapses to 0 rows (binding.rs `test_batch_new_loses_len_for_empty_schema`). Apply the sibling operators' guard (`GraphOperator`/`SubqueryOperator::drain_buffer`): `num_cols == 0` → `Batch::empty_schema_with_len(n)`, in both unseeded and correlated emission. Register: SPARQL11_PROPERTY_PATH shrinks to pp34/pp35, which are graph-cluster tests (constant-GRAPH-IRI resolution, ROADMAP §6.1 — owner PR-BASE/PR-G1); comment corrected accordingly. Full W3C suite green (36 suites), 0 stale entries; property-path suite 31 passed / 2 registered.
jsonres01-04 (SPARQL11_JSON_RES, now empty) and entailment owlds02 all blocked on the _:label. lexer bug and now pass; the harness's stale-entry gate forces their removal. sparqldl-03 shared the same data-load failure mode: its data now loads but the test legitimately fails as an entailment result mismatch (expected 1 solution, got 0), so its entry moves to the result-mismatch group with the new rationale. Full W3C suite: 36/36 suites green, 0 failed, 0 stale.
) Every RDF-lexical serialization of a finite xsd:double used Rust's f64 Display (or {:E} in the N-Triples export), producing non-canonical forms like "1000000" and "1E6". Add one shared canonical_xsd_double helper (plus zero-alloc String/byte-buffer variants) in fluree-graph-ir and route all RDF-lexical sites through it: SPARQL Results JSON, SPARQL Results XML, CSV/TSV, LiteralValue::lexical() (Term Display, RDF/XML), and the N-Triples/Turtle export. Doubles now serialize in the W3C canonical form (1.0E6, 1.0E-3, 0.0E0); NaN/INF/-INF keep their XSD spellings. JSON-LD and the other JSON-native outputs (typed/cypher/agent JSON) are deliberately untouched: a double there is a native JSON number, never a lexical string, guarded by new parity tests. Greens W3C csv03 (lexical CSV comparison); remove its register entry. Decision transparency (ROADMAP PR-L2): scope extended to export.rs per verification finding 1.1-8; the R2RML extractor's lexical() use is flagged on the PR rather than changed independently.
The burn-down roadmap gates every property-path PR on a dedicated criterion bench (ROADMAP §2 PR-PP, §7 DoD-3); none existed. Follow the query_hot_bsbm pattern: file-backed ledger, full reindex, warm-cache GraphSnapshot reuse. Synthetic graph of disjoint 8-node chain clusters (closure output stays linear in scale) with one literal per node so the zero-length term universe is exercised. Three scenarios pin the operator's distinct execution modes: - star_closure: `?s ex:link* ?o` — both-variable closure + zero-length universe - seq_plus: `ex:seqRoot ex:p1/ex:p2+ ?o` — BGP join into correlated `+` forward BFS (the distinct-node traversal fast path) - zero_or_one: `?s ex:link? ?o` — ZeroOrOne closure branch Registered in regression-budget.json (tiny/small/medium, standard budgets); fluree-bench-support workspace_reconcile passes.
A FILTER referencing no variables (FILTER(true), FILTER(1 = 1),
FILTER(2 IN (1,2,3))) eliminated every solution. Two composing defects:
- reorder_patterns classified the filter as Deferred with an empty
dependency set, so it drained to the very front of the pattern list —
a FilterOperator over the synthetic unit seed, gating the whole group
instead of its solutions. Var-free filters now anchor after the
patterns that precede them (the MINUS/EXISTS order preservation);
filters that reference variables keep dependency placement unchanged.
- filter_batch/filter_batch_with_exists rebuilt zero-column batches with
Batch::new, which infers len = 0 from the absent first column and
silently dropped the surviving unit row (ASK { FILTER(true) }).
Plan-time classification only; no per-row cost on existing paths.
Register shrink (5): dawg-boolean-literal, add-literals (also a
variable-free FILTER), in01, notin01, plain-string-same. JSON-LD parity
tests per compliance § Query Surface Parity in
it_query_expression_semantics.rs.
Fixes #1439.
DATATYPE() and LANG() bailed unless their argument was a bare variable,
so every FILTER(datatype(<arithmetic>) = xsd:T) / cast-result inspection
errored and demoted to false. Both builtins now evaluate the argument
expression and classify the resulting value; the bare-variable fast path
still reads the binding directly, without materializing.
Non-literal arguments split per surface (decision D-12, ROADMAP §4):
Function::{Datatype,Lang} carry a lowering-time `strict` mode. SPARQL
lowers strict — DATATYPE/LANG of an IRI/ref/bnode is a type error
evaluated as "no value", so a FILTER excludes the row (even under !=)
and a project expression/BIND leaves the variable unbound (§17.2/§18.5).
JSON-LD lowers lenient, preserving the deliberate `@id`-datatype
extension and the "" LANG fallback. Both behaviors are asserted in
it_query_expression_semantics.rs; the mode is fixed at lowering time, so
there is no per-row dispatch cost and the LANG(?o)="tag" scan fast paths
now match both modes structurally.
Register shrink (30): type-promotion-01/02/06..20/22, the six
cast-{str,flt,dbl,dec,int,bool}, dawg-datatype-2, dawg-lang-1/2,
projexp05 (SPARQL11_PROJECT_EXPRESSION now empty), and the two
SPARQL12_RDF11 constant-datatype tests. tP-29/30 are newly registered:
they expect ASK=false and previously passed only because DATATYPE(expr)
errored — evaluating the argument unmasks the D4 numeric-promotion
defect (PR-X2). tP-03/04/05/21 stay registered on the same defect.
dawg-lang-3 stays registered: it fails on scan-path lang-tag case
matching, not on LANG().
Fixes #1440.
The SPARQL cast lowering table stopped at the numeric/string/boolean
constructors, so xsd:dateTime(?v) fell through to Function::Custom and
an "Unknown function" error. Add Function::{XsdDateTime,XsdDate,XsdTime}
with cast arms that reuse the existing temporal parsers: identity casts
pass through, string lexical forms parse, and everything else produces
no value (W3C SPARQL 1.1 §17.5 — an invalid cast is a type error, not a
query error).
Parse-time lowering plus a cast that only runs when a query names the
constructor; no cost to queries that don't.
Register shrink (1): cast-dT. Surface note per compliance § Query
Surface Parity: XSD constructor casts are SPARQL-only syntax (the
JSON-LD query surface has no cast form) — recorded as a surface-only
decision in the PR description.
BNODE(str) hashed only its label, so the same argument produced the same blank node across every solution; SPARQL 1.1 §17.4.2.9 requires the same label to map to the same bnode only WITHIN one solution and to fresh bnodes across solutions (bnode01). The bnode key now folds in a solution-identity hash: a commutative XOR-fold over the row's bound (var, value) pairs via a new RowAccess::for_each_binding visitor. Blank-node-valued and unbound/poisoned slots are excluded so a BNODE-produced column bound between two BNODE(label) calls on the same solution does not perturb the identity. The hash runs only inside BNODE(label) evaluation — no cost to any other path. Because BNODE is solution-scoped, a BIND containing it is now order-sensitive: reorder_patterns and the block planner anchor it after the patterns that precede it instead of floating it to the earliest point its variables bind (evaluating against a partial join prefix collapsed per-solution identity). Binds without BNODE keep dependency placement unchanged. Register shrink (1): bnode01.
build_regex_with_flags errored on the XPath fn:matches `q` flag, so
regex(?v, "a?+*.{}()[]c", "q") failed the whole FILTER. With `q` the
pattern is now escaped and matched literally; per XPath F&O §5.6.2 it
composes with `i` and neutralizes `m`/`s`/`x`. Unknown flags still
error. Compile-time only — the compiled regex stays behind the
thread-local LRU cache keyed by (pattern, flags).
Register shrink (2): regex-no-metacharacters,
regex-no-metacharacters-case-insensitive.
SPARQL VALUES rows and JSON-LD inline values tagged a bare integer literal xsd:long, while storage, the Turtle parser, arithmetic results, and triple-pattern lowering all tag xsd:integer. The scan post-filter's integer-family compatibility masked it on the join path, but in term-identity contexts (DISTINCT, GROUP BY, sameTerm) and in result serialization the same number surfaced as two distinct terms. Lower a bare integer to xsd:integer at both boundaries (fluree-db-sparql lower/term.rs VALUES rows; fluree-db-query parse/lower.rs JSON-LD inline values), matching RDF 1.1 and storage tagging. Lowering-time only. Scan-path datatype constraints and dt_compatible are deliberately untouched (that is D5b, PR-X2). No W3C register entry fails solely on this; regression coverage lives in it_query_expression_semantics.rs (SPARQL result datatype, sameTerm and DISTINCT collapse, plus the JSON-LD values-integer parity tests). Fixes #1319.
…pace The root Cargo.toml excludes testsuite-sparql by relative path, which does not apply when the repo is checked out as a nested git worktree — cargo then walks up to the enclosing workspace root and refuses to build. An empty [workspace] table makes the exclusion self-contained; behavior in a normal checkout is unchanged.
parse_rdf_dawg_result_set treated Event::Start and Event::Empty identically, pushing expectation state for self-closing elements that quick-xml never emits an End event for. Every <rs:value rdf:resource=.../> therefore skewed the state stack by one: the solution containing the unbound variable survived while every bound solution after the first self-closing value was dropped (the engine's sort output was correct all along). Complete self-closing elements immediately instead of pushing them, add the rdf:nodeID blank-node value form, and deregister dawg-sort-3/6/8, which now pass. The SRX parser had the same Start|Empty conflation: <literal/>, <uri/>, <bnode/> and <result/> were never committed because their End handling never ran, silently dropping empty-literal bindings and fully-unbound rows. Route both parsers' completion logic through one shared helper per parser and cover the self-closing and unbound-variable shapes with unit tests.
Comment-only edits from docs/audit/burn-down/ROADMAP.md \S6.1 — no entry is added or removed. Corrects the misattributed root causes so later burn-down PRs delete entries out of accurately-labeled groups: the insert-05a / same-bnode / delete-insert-01c group is multi-op ';' truncation (#1438), not named-graph INSERT loss; constructlist is a parse-time rejection, not a query-execution error; pp34/pp35 are graph-resolution defects, not path cardinality; the SPARQL12_VERSION failures are wave-2 reifier syntax, not VERSION support; syntax-update test_36/38/39/40/54 are update classes D/C/B, not graph-management grammar; plus cluster-ownership annotations (W-1/W-2, PR-BASE, PR-G1, PR-X1/X2, PR-U1/U2/U3/U6, PR-PP, PR-L2, PR-W2A, D-6/D-8).
…audit Per the burn-down verification (ROADMAP \S6.3): the \S8 'INSERT into a not-yet-existing named graph silently loses the triples' and 'combined DELETE/INSERT WHERE applies inserts without the deletes' findings are refuted — both are multi-operation ';' truncation (#1438); \S4.2 item 7 no longer lists pp06 (it passes and is deregistered) and notes the pp34/pp35 reattribution; the headline register split is 816 passing / 541 gaps / 63 not-applicable, not 815/538/67.
…own PR-1)
Accept-more, parse-time-only fixes for the W3C syntax positives the
parser still rejected (docs/audit/burn-down/parser-syntax-validation.md
Section 2, PR-1):
- P1: RDF collections '( ... )' in subject and object positions,
desugared at parse time to rdf:first/rest/nil triples over fresh
'#collN' blank-node cells riding the existing pending_bnpl_triples
channel — no new AST, IR, or engine surface. '()' is the plain IRI
rdf:nil. Collections stay rejected inside quoted triples and triple
terms (rtSubject/rtObject exclude them; guards keep the four
list-*/quoted-list-* negative tests red).
- P4: empty IRIREF '<>' — drop the lexer's empty-body rejection; the
IRIREF production permits zero characters.
- P5a: extension-function calls with a NIL arg list 'f()' — the three
iri-or-function guards in parse_primary_expr now accept the Nil token
(parse_expression_list already handled it).
- P5b: bare Constraint as an ORDER BY condition ('ORDER BY str(?o)'),
mirroring parse_group_condition's position/restore guard.
- P7: 'VALUES () { ... }' — NIL variable list (zero variables) with
NIL rows (zero-binding rows).
- P8: property paths as the verb inside a blank-node property list
('[ :p|:q ?x ]') via a new pending_bnpl_patterns channel; template
contexts (CONSTRUCT / INSERT / DELETE) reject it explicitly instead
of silently dropping the pattern.
- SPARQL 1.2 codepoint-escape pre-pass: \uXXXX/\UXXXXXXXX are
unescaped over the whole query string before tokenizing, with a Cow
fast path (no allocation when no backslash / no codepoint escape is
present). Escapes are processed exactly once, left to right, so
"\\u0041" decodes to the invalid string escape \A and is rejected.
The roadmap's P6 (sub-select placement) is fully subsumed by main's
PR #1436 (sub-SELECT as a set-operation operand), which this branch is
rebased on — no P6 code lands here.
All fixes are parse-time; zero runtime/engine code is touched.
Removed (29 entries), verified by the both-way register policing: - SPARQL10_SYNTAX: all 17 'parser rejects valid input' entries (syntax-lists-01..05 x2 suites, syntax-forms-01/02, syntax-order-07, syntax-qname-05, syntax-function-01..03). - SPARQL11_SYNTAX_QUERY: test_35a, test_36a, test_63, test_pp_coll (4). (test_21/test_23/test_64 and subquery12 were already removed by the base branch's re-baseline for main's #1436 sub-SELECT fix, which subsumed this PR's P6 item; test_65 was likewise already registered under accepts-invalid awaiting PR-2's SELECT-scope validation.) - SPARQL12_CODEPOINT_ESCAPES: all 6 (register now empty). - SPARQL12_SYNTAX_TRIPLE_TERMS_POSITIVE: annotation-reifier-07 and annotation-anonreifier-07 — their only blocker was the path-in-'[]' form inside the annotation block (P8). Cross-register removal coordinated with PR-W2A per ROADMAP Section 1.1-12. NOT removed, with corrected register comment: - SPARQL10_QUERY_EVAL basic#list-1..4: the cluster doc's claim that P1 greens them is refuted — Fluree's Turtle ingest emits OBJECT-position collections as Fluree list_index items (parse_collection_as_list) and drops '()' objects entirely, so the rdf:first/rest triples the desugared query pattern needs are never stored. Eval-side ingest/model gap, not parser territory.
Per docs/contributing/sparql-compliance.md 'Query Surface Parity': P1 (collection patterns) is surface sugar over rdf:first/rest/nil triples the shared engine already executes, so the JSON-LD surface must be able to match exactly the data a SPARQL '( ... )' pattern matches. Two tests over an explicitly-seeded first/rest chain: - a JSON-LD where clause spelling out rdf:first/rdf:rest/rdf:nil returns identical rows to 'ex:x ex:letters (?v ?w)'; - a JSON-LD rdf:nil object pattern returns identical rows to the empty collection 'ex:s ex:letters ()'. The seed uses an explicit first/rest chain because Fluree's JSON-LD @list ingest stores ordered list_index values, not first/rest triples — recorded in the test header and the PR description.
# Conflicts: # testsuite-sparql/tests/registers/mod.rs
# Conflicts: # testsuite-sparql/tests/registers/mod.rs
# Conflicts: # testsuite-sparql/tests/registers/mod.rs
# Conflicts: # testsuite-sparql/tests/registers/mod.rs
bplatz
reviewed
Jul 8, 2026
bplatz
approved these changes
Jul 8, 2026
bplatz
left a comment
Contributor
There was a problem hiding this comment.
A few comments, approved pending your review.
Four items from bplatz's review of the Wave-0 property-path and parser work. 1. Property-path zero-length universe (fluree-db-query property_path.rs). The full-graph term scan behind a both-unbound `?s :p* ?o` had no cancellation poll -- unlike every other traverse/closure loop in the operator -- and ran before the guarded BFS, so it was uninterruptible. Poll `bail_if_cancelled` in the scan loop so a runaway enumeration is bounded by the query timeout. No hard term cap is added: the universe is inherently O(graph) per SPARQL 1.1 §18.4 (the pp16 fix requires every graph term as a self-pair, literals and non-path terms included), and `max_visited` is a traversal-depth bound (always 10k) that would reject spec-valid queries on modestly large graphs. The up-front `range_with_overlay` materialization is unchanged (shared with the wildcard closure) and documented as a separate concern. 2. Property-path bench coverage (query_hot_property_path.rs). The link/p2-everywhere graph made the zero-length universe scale with the path domain, so no scenario exercised a selective predicate over a large graph -- the shape where the universe scan dominates. Add a `rare_star` scenario (one fixed `ex:rare` edge) that isolates the full-graph universe-scan cost, and tighten the medium budget from 5.0 to 3.0 to match the bsbm siblings (still a placeholder pending the first nightly baseline). 3. Bare ORDER BY / GROUP BY (fluree-db-sparql parse/query/modifier.rs). The bare condition branch used full `parse_expression` where the grammar allows only `Constraint | Var` (Constraint = BrackettedExpression | BuiltInCall | FunctionCall), so `ORDER BY ?x - 1` silently parsed as two keys [?x, -1], corrupting the sort. Restrict the bare branch (ORDER BY and GROUP BY) to a call-shaped Constraint via `is_bare_constraint`: `str(?o)` / `ex:fn(?o)` stay valid, a trailing `- 1` is no longer absorbed as a key, and a leading bare arithmetic hard-errors. Adds parser regression tests. 4. Codepoint-escape corners (fluree-db-sparql lex/lexer.rs). The SPARQL 1.2 §19.2 pre-pass is correctly global-before-tokenizing (esc-01/07/08 require it), so it is comment-agnostic, and an escape that decodes to a delimiter acts as that delimiter (a decoded `>` closes an IRI, the same rule as esc-08's decoded `"` opening a string). Neither corner is covered by the W3C suite; pin both with unit tests and document them on the pre-pass. No behavior change. Validation: W3C SPARQL suite 36/36 green, 0 stale; fluree-db-sparql lib 521 tests green; property-path bench compiles and runs all four scenarios; fmt and clippy clean.
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.
Reviewer quick reference
End-user API/contract changes: (a)
xsd:doubleRDF-lexical output is now the XSD canonical form ("1000000"/"1E6"→"1.0E6") across SPARQL-JSON/XML, CSV/TSV, Turtle/N-Triples export, andLiteralValue::lexical()— downstream string-matchers must adjust (changelog note below); JSON-LD numeric output is deliberately unchanged. (b) SPARQLDATATYPE()/LANG()now accept expression arguments (additive), andDATATYPEon a non-literal is a type error on the SPARQL surface only (see Decisions). (c) Everything else in this wave is accept-more parsing or bug fixes — no previously-working query changes meaning.Performance verdict: no hot-path regressions anywhere; all standard gates within
regression-budget.json. One must-accept output cost: correct zero-length property-path answers are strictly larger result sets on shapes that previously returned wrong (incomplete) answers — details and numbers in Performance Notes below. This wave also adds a new pinned bench (query_hot_property_path).Production bugs found & fixed along the way (not just W3C compliance):
FILTER(true)/any variable-free filter silently returned zero rows (#1439);DATATYPE()/LANG()rejected composed expressions (#1440); Turtle import rejected valid_:label.(#1444); double serialization was non-canonical and internally inconsistent across formats (#1445); bare integers in VALUES mintedxsd:longterm identities (#1319 mechanism); and the W3C harness's own.rdfresult parser dropped bound solutions on self-closing XML elements while keeping unbound ones.Decisions actioned in this PR
D-12 (scoping of strict SPARQL type errors) — actioned by the expression component (pr-x1, D2b).
DATATYPE()is applied to a non-literal (IRI/bnode), SPARQL 1.1 requires a type error; Fluree's JSON-LD surface deliberately returns the@idextension value instead.docs/audit/burn-down/ROADMAP.md§4.)D-13 (surfaced here, deliberately NOT actioned): the collections work revealed that Turtle ingest stores object-position RDF collections as native
list_indexitems —rdf:first/resttriples never exist in storage, sobasic#list-1..4cannot green from parser work. Materialize-at-ingest vs translate-at-query vs documented divergence is an open team decision; the register entries say exactly this.Wave 0 of the W3C SPARQL burn-down (6 components, ≈−80 register entries)
First implementation wave of the burn-down plan in
docs/audit/burn-down/ROADMAP.md(see PR #1437 for the enforcement model). Every component was implemented against its adversarially-verified cluster audit, is individually suite-green, and was integrated here with register conflicts resolved to the mechanical fixpoint dictated by the both-way CI gate. Zero-to-low engine risk by design: this wave is harness fixes, parser accept-more, lexer/formatter corrections, and expression fixes on cold/erroring paths. All bench gates withinregression-budget.jsonbudgets.Components
1. Harness + register hygiene (
pr-h1, −3). Fixes the harness's.rdf/SRX result parsers: self-closingEvent::Emptyelements skewed the state stack (keeping unbound solutions and dropping bound ones — the mechanism was verified live againstresult-sort-3.rdf), plus missingrdf:nodeIDblank-node value support. Applies the full register-comment correction batch so every entry names its root cause and owning future PR. Also commits the standalone-[workspace]marker for the harness (root path-exclusion doesn't reach git worktrees).2. Parser accepts valid syntax (
pr-1, −29). RDF collections( … )in triple patterns (desugared tordf:first/rest/nil, guarded out of quoted-triple contexts per the RDF 1.2 grammar's negative tests), empty IRIREF<>, function-callNILarglists, bare ORDER BY constraints, sub-SELECT placement,VALUES (), property paths inside blank-node property lists, and a SPARQL 1.2 codepoint-escape pre-pass (Cowfast path — zero alloc when no escapes). Notable finding:basic#list-1..4do NOT green — Turtle ingest stores object-position collections as nativelist_indexitems, neverrdf:first/resttriples; registered as decision D-13. Its P6 sub-SELECT work was later subsumed by main's #1436 and dropped cleanly. JSON-LD parity:@list↔ first/rest equivalence test.3. Turtle bnode-dot lexer (
pr-l1, −5)._:o6.(blank-node label + statement dot) failed to lex (#1444). Fix is greedy-scan + trailing-dot rewind — the naive lookahead alternative was rejected because it broke spec-valid_:a..b(adversarial-verification catch, ROADMAP §1.1-9). Import-hot path: interleaved ABAB benches at n=100 (single runs measured ±11–15% noise on identical binaries); all within budget. Bonus green: entailmentowlds02(its data was_:x.-shaped).4. Canonical
xsd:double(pr-l2, −1). One shared zero-alloccanonical_xsd_doublehelper (fluree-graph-ir), routed through every RDF-lexical site: SPARQL-JSON/XML, CSV/TSV (drops theryudep),LiteralValue::lexical(), and the N-Triples/N-Quads export sites that emitted dotless1E6(#1445). Behavior change / changelog: RDF-lexical double output is now the XSD canonical form (1.0E6); JSON-LD numeric output deliberately untouched (native JSON numbers) with a guard test. R2RML inherits the canonical form (spec-correct; flagged for that workstream).5. Expression high-yield fixes (
pr-x1, −37). Six per-defect commits, each individually suite-green: variable-free FILTER dropped ALL rows (FILTER(true)→ 0 results, #1439 — plan-time fix, var-full paths byte-identical);DATATYPE()/LANG()rejected expression arguments (#1440); xsd:dateTime/date/time casts;BNODE(label)per-solution identity; regexqflag; bare integers in VALUES lowered toxsd:integer(#1319 — at lowering,dt_compatibleuntouched per the verified mechanism). Decision transparency (D-12): the DATATYPE-on-non-literal type error is SPARQL-scoped only; the JSON-LD@idextension behavior is deliberate and pinned by parity tests (options and rationale in ROADMAP §4). Fixing D2 unmasked the deeper D4 promotion defect ontP-29/30— honestly registered for PR-X2. BSBM benches at/inside the noise floor.6. Property-path fixes (
pr-pp, −2). pp16: the zero-length-path universe now spans ALL graph terms including literals (SPARQL §18.4), gated at plan level on theZeroOrMore/ZeroOrOnemodifiers —*/+traversal untouched; pp36:empty_schema_with_lenguard for zero-column existence rows. Adds thequery_hot_property_pathbench +regression-budget.jsonentries (reconcile test passes). The slower closure numbers are spec-mandated larger output on previously-wrong shapes;seq_plusand BSBM unchanged.Verification
fluree-db-sparql601 unit tests; JSON-LD groups (grp_query,grp_query_sparql,grp_transact) green; fmt + clippy-D warningsclean.Issues closed by this wave when merged: #1439, #1440, #1444, #1445 (+ #1319 mechanism half).
Performance notes (per component)
Harness/registers (pr-h1): harness-only; no engine code.
Parser accept-more (pr-1): 100% parse-time; zero runtime code touched. The SPARQL 1.2 codepoint-escape pre-pass uses a
Cowfast path — zero allocation and a single scan when a query contains no escapes (the overwhelmingly common case).Turtle bnode-dot lexer (pr-l1) — import-hot path: the fix deliberately keeps the existing greedy per-character scan byte-identical (no added lookahead, no backtracking); trailing dots are handled by a post-scan rewind, one extra check per label, not per character. Measurement was rigorous because single runs proved noise-dominated (±11–15% measured on identical binaries): interleaved ABAB at n=100 gave
import_bulk+1.0%/−9.2% (noise band), turtle 100-nodes −0.3%, turtle 10-nodes ~+2.1% attributable to code layout — the changed function is unreachable by the bench corpus, which contains no_:tokens (corpus gap flagged for the bench backlog). All within the 5% budgets.Canonical double (pr-l2): formatter-layer only, no query-path involvement. The shared helper post-processes
{:e}in a fixed stack buffer (zero heap allocation); the CSV/TSV writer gained a write-into-buffer variant and dropped theryudependency.Expression high-yield (pr-x1): the variable-free FILTER fix (D1) is plan-time classification in the planner — variable-bearing filter paths are byte-identical; all other defects are on cold or previously-erroring evaluation paths. BSBM before/after (quick profile, declared): q3 +1.9–3.4%, q5 −1.6–−0.3%, q9 +2.0–3.9%, bi/f2 +1.1–2.2% — same-code reruns on this box swing ±1–4%, so deltas are at/inside the noise floor and within the small-budget; the nightly full-profile bench is the backstop. Each defect landed as its own suite-green commit for bisectability.
Property paths (pr-pp): the
*/+traversal fast path is untouched; the zero-length term universe is selected at plan level, only for the SPARQLZeroOrMore/ZeroOrOneshapes, and pair buffers carry full terms only on that path. Must-accept cost, stated plainly: star_closure 2.37→6.06 ms and zero_or_one 1.69→6.27 ms in the new bench — these are NOT like-for-like comparisons: the pre-fix numbers computed incomplete (wrong) results, and SPARQL §18.4 requires every graph term (including literals) as a zero-length self-pair, so the correct output is strictly larger on both-ends-unbounded queries. The hot signals hold: seq_plus 43.50→43.87 ms (noise),query_hot_bsbmunchanged (q3 0.92 / q5 1.42 / q9 2.06 ms). This PR addsquery_hot_property_path+regression-budget.jsonentries, so path-traversal performance is pinned from now on (workspace_reconcilepasses).Issues closed on merge
Closes #1439
Closes #1440
Closes #1444
Closes #1445
Also fixes the reported mechanism of #1319 (bare VALUES/inline integers now lower to
xsd:integer; the separate scan-path datatype-constraint question stays tracked there — left open deliberately rather than auto-closed).Mechanics note: GitHub fires these keywords when the PR merges into the default branch. In the stacked flow (#1437 merges first, this PR is then auto-retargeted to
main), that happens exactly when these fixes actually reachmain.