Skip to content

fix: history @t/@op for IRI-valued objects + boolean @op#1203

Merged
bplatz merged 1 commit intomainfrom
fix/sid-t-op
Apr 29, 2026
Merged

fix: history @t/@op for IRI-valued objects + boolean @op#1203
bplatz merged 1 commit intomainfrom
fix/sid-t-op

Conversation

@bplatz
Copy link
Copy Markdown
Contributor

@bplatz bplatz commented Apr 25, 2026

This is based on #1198 branch and continues history query fixes

History queries on the fix/history-ranges branch only carried @t/@op
metadata onto literal-valued objects. Ref-valued predicates (rdf:type,
skos:inScheme, skosxl:prefLabel, etc.) were emitted with ?v populated
but ?t and ?op null. The root cause was structural: Binding::Sid(Sid) had no metadata
channel, so the flakes_to_bindings ref arm and the
{materialized,late_materialized}_object_binding helpers all dropped
flake.t / flake.op whenever the object was a FlakeValue::Ref.

This PR fixes the asymmetry at the type layer rather than at individual
call sites, so the invariant "object bindings carry assertion metadata"
is uniform across the literal and ref paths. It also flips the on-the-wire
@op representation from string ("assert" / "retract") to boolean
(true / false) to match Flake.op on disk and avoid a per-row Arc
allocation.

What changed

Binding type — metadata-capable struct variants

fluree-db-query/src/binding.rs

  • Binding::Sid is now Sid { sid: Sid, t: Option<i64>, op: Option<bool> }.
  • Binding::EncodedSid gains the same t/op Option fields.
  • Both fields are intentionally excluded from PartialEq and Hash,
    matching the discipline already in place for Binding::Lit. Set
    semantics (joins, DISTINCT, GROUP BY, hash maps) ignore the metadata, so
    a metadata-bearing object binding compares equal to a metadata-free one
    with the same SID.
  • New constructors keep call sites concise:
    • Binding::sid(sid) — no metadata (subjects, predicates, constants,
      VALUES rows, expression results).
    • Binding::sid_with_t(sid, t) — current-state object binding from a
      flake.
    • Binding::sid_with_t_op(sid, t, op) — history-mode object binding.
    • Binding::encoded_sid(...) and Binding::encoded_sid_with_t_op(...)
      mirror these for the late-materialised path.
  • Central accessors Binding::t() -> Option<i64> and
    Binding::op() -> Option<bool> cover all four metadata-bearing variants
    (Lit, EncodedLit, Sid, EncodedSid). eval_t / eval_op route
    through these so the variant list lives in exactly one place.

Object-construction sites threading t/op

Object positions only — subject and predicate bindings still emit
t: None, op: None, so T(?s) / OP(?p) keep the previous "no metadata"
semantics rather than inventing a meaning.

  • binary_scan.rs::flakes_to_bindings — ref arm now uses
    sid_with_t(...) for current-state scans (so T(?v) works for
    ref-valued predicates outside history mode too) and sid_with_t_op(...)
    in history mode.
  • object_binding.rs::{materialized_object_binding, late_materialized_object_binding}
    — accept an op: Option<bool> parameter and propagate it to both the
    Lit arm and the Sid / EncodedSid arm. IriRef and BlankNode
    decode kinds now carry t like the literal kinds do.
  • binding.rs::from_object_with_t_op — fixed; this is the originally
    reported zero-call-site constructor that motivated the bug write-up.
    Both the literal and ref arms now thread t/op through.

OP() returns boolean (true / false)

Flake.op is a boolean on disk. The previous string surface required a
per-row Arc::from("assert" | "retract") allocation in expression
evaluation and didn't reflect storage. Now:

  • eval_op returns ComparableValue::Bool(op).
  • The parser accepts:
    • "@op": "?var" — generates BIND(op(?v) AS ?var).
    • "@op": true / "@op": false — generates
      FILTER(op(?v) = true|false).
  • String constants ("assert", "retract") are no longer accepted; the
    parser returns a clear error pointing users at the boolean form. (No
    backward-compatibility shim — product is pre-release.)

fluree-db-query/src/expression/fluree.rs,
fluree-db-query/src/parse/node_map.rs (new internal OpAnnotation enum
with Variable(Arc<str>) / Constant(bool) variants).

Parser: @t / @op permitted with @type: "@id"

Removed the parser-level rejection that forbade @t / @op annotations on
explicitly IRI-typed value objects. Now that ref-valued bindings carry the
metadata too, the parser-generated BIND(t(?v) AS ?t) /
BIND(op(?v) AS ?op) resolve correctly.

Tests

fluree-db-api/tests/it_query_history_range.rs

  • All existing literal-valued cases migrated to bool form (?op is now
    bool in the flattened-row tuples, ordered as false < true).
  • Two new IRI-object regression cases against ex:knows:
    • history_range_iri_object_sidecar_plus_base — verifies the
      sidecar+base path threads t/op through the ref arm.
    • history_range_iri_object_novelty_only — verifies the novelty branch
      of the history collector does too.
  • Constant-filter tests now use "@op": true / "@op": false.

@bplatz bplatz requested review from aaj3f and zonotope April 25, 2026 14:09
Copy link
Copy Markdown
Contributor

@zonotope zonotope left a comment

Choose a reason for hiding this comment

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

🔦

Base automatically changed from fix/history-ranges to main April 29, 2026 10:59
@bplatz bplatz merged commit b4c5334 into main Apr 29, 2026
8 checks passed
@bplatz bplatz deleted the fix/sid-t-op branch April 29, 2026 11:01
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