Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions docs/api/endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -989,16 +989,18 @@ Query the history of entities using the standard `/query` endpoint with `from` a
```

The `@t` and `@op` annotations capture transaction metadata:
- **@t** - Transaction time when the value was asserted or retracted
- **@op** - Operation type: `"assert"` or `"retract"`
- **@t** - Transaction time (integer) when the fact was asserted or retracted.
- **@op** - Operation type as a boolean: `true` for assertions, `false` for retractions. (Mirrors `Flake.op` on disk; constants `"assert"` / `"retract"` are not accepted.)

Both annotations work uniformly for literal-valued and IRI-valued objects.

**Response:**

```json
[
["Alice", 30, 1, "assert"],
["Alice", 30, 5, "retract"],
["Alicia", 31, 5, "assert"]
["Alice", 30, 1, true],
["Alice", 30, 5, false],
["Alicia", 31, 5, true]
]
```

Expand Down
20 changes: 10 additions & 10 deletions docs/concepts/time-travel.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ History queries capture both the retraction and assertion with `@op`:

```json
[
[25, 1, "assert"],
[25, 5, "retract"],
[26, 5, "assert"]
[25, 1, true],
[25, 5, false],
[26, 5, true]
]
```

Each row shows `[value, transaction_time, operation]`.
Each row shows `[value, transaction_time, op]` where `op` is `true` for assertions and `false` for retractions.

### Valid Time vs Transaction Time

Expand Down Expand Up @@ -257,16 +257,16 @@ Track all changes to a specific entity over time by specifying a time range:
```

The `@t` and `@op` annotations bind the transaction time and operation type:
- **@t** - Transaction time when the fact was asserted or retracted
- **@op** - Either `"assert"` or `"retract"`
- **@t** - Transaction time (integer) when the fact was asserted or retracted.
- **@op** - Boolean: `true` for assertions, `false` for retractions. Mirrors `Flake.op` on disk. Both literal- and IRI-valued objects carry the metadata.

Returns results showing all changes:

```json
[
["Alice", 1, "assert"],
["Alice", 5, "retract"],
["Alicia", 5, "assert"]
["Alice", 1, true],
["Alice", 5, false],
["Alicia", 5, true]
]
```

Expand Down Expand Up @@ -561,7 +561,7 @@ Use history queries to identify when a specific change happened:
}
```

The results show when `ex:status` changed, with `"retract"` for the old value and `"assert"` for the new value at the same transaction time.
The results show when `ex:status` changed, with `?op = false` (retract) for the old value and `?op = true` (assert) for the new value at the same transaction time.

### Audit Trail for Compliance

Expand Down
14 changes: 7 additions & 7 deletions docs/getting-started/quickstart-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,15 +304,15 @@ curl -X POST http://localhost:8090/v1/fluree/query \
}'
```

The `@t` annotation binds the transaction time and `@op` shows the operation type (`"assert"` or `"retract"`).
The `@t` annotation binds the transaction time, and `@op` binds the operation type as a boolean (`true` = assert, `false` = retract).

Response shows all changes:

```json
[
["Alice", 30, 1, "assert"],
["Alice", 30, 5, "retract"],
["Alicia", 31, 5, "assert"]
["Alice", 30, 1, true],
["Alice", 30, 5, false],
["Alicia", 31, 5, true]
]
```

Expand Down Expand Up @@ -342,9 +342,9 @@ Response:

```json
[
[30, 1, "assert"],
[30, 5, "retract"],
[31, 5, "assert"]
[30, 1, true],
[30, 5, false],
[31, 5, true]
]
```

Expand Down
13 changes: 9 additions & 4 deletions docs/query/jsonld-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -801,8 +801,10 @@ History queries let you see all changes (assertions and retractions) within a ti

Use `@t` and `@op` annotations on value objects to capture metadata:

- **@t** - Binds the transaction time when the fact was asserted/retracted
- **@op** - Binds the operation type: `"assert"` or `"retract"`
- **@t** - Binds the transaction time (integer) when the fact was asserted/retracted.
- **@op** - Binds the operation type as a boolean: `true` for assertions, `false` for retractions. (Mirrors `Flake.op` on disk; constants `"assert"` / `"retract"` are *not* accepted — use `true` / `false`.)

Both annotations work uniformly for literal-valued and IRI-valued objects.

**Entity History:**

Expand Down Expand Up @@ -851,19 +853,22 @@ Use `@t` and `@op` annotations on value objects to capture metadata:

**Filter by Operation:**

You can either use a constant `@op` shorthand (preferred) or filter on the bound variable:

```json
{
"@context": { "ex": "http://example.org/ns/" },
"from": "ledger:main@t:1",
"to": "ledger:main@t:latest",
"select": ["?name", "?t"],
"where": [
{ "@id": "ex:alice", "ex:name": { "@value": "?name", "@t": "?t", "@op": "?op" } },
["filter", "(= ?op \"retract\")"]
{ "@id": "ex:alice", "ex:name": { "@value": "?name", "@t": "?t", "@op": false } }
]
}
```

The shorthand `"@op": false` lowers to `FILTER(op(?name) = false)`. Equivalent long form using a bound variable: `"@op": "?op"` plus `["filter", "(= ?op false)"]`.

**All Properties History:**

```json
Expand Down
6 changes: 3 additions & 3 deletions docs/query/sparql.md
Original file line number Diff line number Diff line change
Expand Up @@ -836,8 +836,8 @@ ORDER BY ?t
```

The `<< subject predicate object >>` syntax (RDF-star) treats the triple as an entity that can have metadata:
- `f:t` - Transaction time when the fact was asserted or retracted
- `f:op` - Operation type: `"assert"` or `"retract"`
- `f:t` - Transaction time (integer) when the fact was asserted or retracted.
- `f:op` - Operation type as a boolean: `true` for assertions, `false` for retractions. Mirrors `Flake.op` on disk.

**Filter by operation type:**

Expand All @@ -851,7 +851,7 @@ TO <ledger:main@t:latest>
WHERE {
<< ex:alice ex:age ?age >> f:t ?t .
<< ex:alice ex:age ?age >> f:op ?op .
FILTER(?op = "retract")
FILTER(?op = false)
}
```

Expand Down
6 changes: 3 additions & 3 deletions docs/transactions/retractions.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,12 +345,12 @@ curl -X POST http://localhost:8090/v1/fluree/query \
Response:
```json
[
["Alice", 1, "assert"],
["Alice", 5, "retract"]
["Alice", 1, true],
["Alice", 5, false]
]
```

The `@t` annotation captures the transaction time and `@op` shows whether each value was asserted or retracted.
The `@t` annotation captures the transaction time and `@op` binds a boolean — `true` for assertions, `false` for retractions (mirroring `Flake.op` on disk).

## Error Handling

Expand Down
4 changes: 3 additions & 1 deletion fluree-db-api/src/format/agent_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,9 @@ fn format_row_with_types(
fn binding_type_label(binding: &Binding, compactor: &IriCompactor) -> Result<Option<String>> {
match binding {
Binding::Unbound | Binding::Poisoned => Ok(None),
Binding::Sid(_) | Binding::IriMatch { .. } | Binding::Iri(_) => Ok(Some("uri".to_string())),
Binding::Sid { .. } | Binding::IriMatch { .. } | Binding::Iri(_) => {
Ok(Some("uri".to_string()))
}
Binding::EncodedSid { .. } | Binding::EncodedPid { .. } => Ok(Some("uri".to_string())),
Binding::Lit { dtc, .. } => {
if dtc.lang_tag().is_some() {
Expand Down
6 changes: 3 additions & 3 deletions fluree-db-api/src/format/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ fn resolve_subject_term(
};

match binding {
Binding::Sid(sid) => {
Binding::Sid { sid, .. } => {
let expanded_iri = compactor.decode_sid(sid)?;
Ok(Some(IrTerm::iri(expanded_iri)))
}
Expand Down Expand Up @@ -207,7 +207,7 @@ fn resolve_predicate_term(
};

match binding {
Binding::Sid(sid) => {
Binding::Sid { sid, .. } => {
let expanded_iri = compactor.decode_sid(sid)?;
Ok(Some(IrTerm::iri(expanded_iri)))
}
Expand Down Expand Up @@ -304,7 +304,7 @@ fn binding_to_ir_term(
Binding::Unbound | Binding::Poisoned => Ok(None),

// Reference - IRI (expanded)
Binding::Sid(sid) => {
Binding::Sid { sid, .. } => {
let expanded_iri = compactor.decode_sid(sid)?;
Ok(Some(IrTerm::iri(expanded_iri)))
}
Expand Down
24 changes: 12 additions & 12 deletions fluree-db-api/src/format/delimited.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ fn write_binding_cell(
Binding::Unbound | Binding::Poisoned => {
// Empty cell
}
Binding::Sid(sid) => {
Binding::Sid { sid, .. } => {
write_compacted_sid(cell, compactor, sid)?;
}
Binding::IriMatch { iri, .. } => {
Expand All @@ -384,7 +384,7 @@ fn write_binding_cell(
Binding::Lit { val, .. } => {
write_flake_value(cell, val, compactor);
}
Binding::EncodedSid { s_id } => {
Binding::EncodedSid { s_id, .. } => {
let gv = require_graph_view(gv)?;
let store = gv.store();
let iri = store.resolve_subject_iri(*s_id).map_err(|e| {
Expand Down Expand Up @@ -659,7 +659,7 @@ mod tests {
fn test_tsv_sid_binding_no_context() {
// Without @context, Sid outputs full IRI (no compaction possible)
let snapshot = make_test_snapshot();
let result = make_result(&["?s"], vec![vec![Binding::Sid(Sid::new(100, "alice"))]]);
let result = make_result(&["?s"], vec![vec![Binding::sid(Sid::new(100, "alice"))]]);
let tsv = format_tsv(&result, &snapshot).unwrap();
assert_eq!(tsv, "s\nhttp://example.org/alice\n");
}
Expand All @@ -670,7 +670,7 @@ mod tests {
let snapshot = make_test_snapshot();
let result = make_result_with_context(
&["?s"],
vec![vec![Binding::Sid(Sid::new(100, "alice"))]],
vec![vec![Binding::sid(Sid::new(100, "alice"))]],
make_test_context(),
);
let tsv = format_tsv(&result, &snapshot).unwrap();
Expand Down Expand Up @@ -713,7 +713,7 @@ mod tests {
let snapshot = make_test_snapshot();
let result = make_result(
&["?a", "?b"],
vec![vec![Binding::Sid(Sid::new(100, "x")), Binding::Unbound]],
vec![vec![Binding::sid(Sid::new(100, "x")), Binding::Unbound]],
);
let tsv = format_tsv(&result, &snapshot).unwrap();
assert_eq!(tsv, "a\tb\nhttp://example.org/x\t\n");
Expand All @@ -725,9 +725,9 @@ mod tests {
let result = make_result(
&["?s"],
vec![
vec![Binding::Sid(Sid::new(100, "a"))],
vec![Binding::Sid(Sid::new(100, "b"))],
vec![Binding::Sid(Sid::new(100, "c"))],
vec![Binding::sid(Sid::new(100, "a"))],
vec![Binding::sid(Sid::new(100, "b"))],
vec![Binding::sid(Sid::new(100, "c"))],
],
);
let tsv = format_tsv(&result, &snapshot).unwrap();
Expand All @@ -743,9 +743,9 @@ mod tests {
let result = make_result(
&["?s"],
vec![
vec![Binding::Sid(Sid::new(100, "a"))],
vec![Binding::Sid(Sid::new(100, "b"))],
vec![Binding::Sid(Sid::new(100, "c"))],
vec![Binding::sid(Sid::new(100, "a"))],
vec![Binding::sid(Sid::new(100, "b"))],
vec![Binding::sid(Sid::new(100, "c"))],
],
);
let (tsv, total) = format_tsv_limited(&result, &snapshot, 2).unwrap();
Expand Down Expand Up @@ -882,7 +882,7 @@ mod tests {
let snapshot = make_test_snapshot();
let result = make_result_with_context(
&["?s"],
vec![vec![Binding::Sid(Sid::new(100, "alice"))]],
vec![vec![Binding::sid(Sid::new(100, "alice"))]],
make_test_context(),
);
let csv = format_csv(&result, &snapshot).unwrap();
Expand Down
4 changes: 2 additions & 2 deletions fluree-db-api/src/format/graph_crawl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,12 @@ pub async fn format_async(
let materialized =
super::materialize::materialize_binding(result, binding)?;
match materialized {
Binding::Sid(sid) => Some(sid),
Binding::Sid { sid, .. } => Some(sid),
Binding::IriMatch { primary_sid, .. } => Some(primary_sid),
_ => None,
}
}
Some(Binding::Sid(sid)) => Some(sid.clone()),
Some(Binding::Sid { sid, .. }) => Some(sid.clone()),
Some(Binding::IriMatch { primary_sid, .. }) => Some(primary_sid.clone()),
Some(Binding::Unbound | Binding::Poisoned) | None => None,
Some(
Expand Down
4 changes: 2 additions & 2 deletions fluree-db-api/src/format/jsonld.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub(crate) fn format_binding(binding: &Binding, compactor: &IriCompactor) -> Res
Binding::Unbound | Binding::Poisoned => Ok(JsonValue::Null),

// Reference (IRI or blank node) - compact using @context
Binding::Sid(sid) => Ok(JsonValue::String(compactor.compact_sid(sid)?)),
Binding::Sid { sid, .. } => Ok(JsonValue::String(compactor.compact_sid(sid)?)),

// IriMatch: use canonical IRI, then compact (multi-ledger mode)
Binding::IriMatch { iri, .. } => Ok(JsonValue::String(compactor.compact_iri(iri)?)),
Expand Down Expand Up @@ -387,7 +387,7 @@ mod tests {
#[test]
fn test_format_binding_sid() {
let compactor = make_test_compactor();
let binding = Binding::Sid(Sid::new(100, "alice"));
let binding = Binding::sid(Sid::new(100, "alice"));
let result = format_binding(&binding, &compactor).unwrap();
// Without @context, returns full IRI
assert_eq!(result, json!("http://example.org/alice"));
Expand Down
8 changes: 4 additions & 4 deletions fluree-db-api/src/format/materialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ fn materialize_encoded_binding(
) -> std::io::Result<Binding> {
let store = gv.store();
match binding {
Binding::EncodedSid { s_id } => {
Binding::EncodedSid { s_id, .. } => {
let iri = store.resolve_subject_iri(*s_id)?;
let sid = store.encode_iri(&iri);
Ok(Binding::Sid(sid))
Ok(Binding::sid(sid))
}
Binding::EncodedPid { p_id } => match store.resolve_predicate_iri(*p_id) {
Some(iri) => Ok(Binding::Sid(store.encode_iri(iri))),
Some(iri) => Ok(Binding::sid(store.encode_iri(iri))),
None => Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Unknown predicate ID: {p_id}"),
Expand Down Expand Up @@ -75,7 +75,7 @@ fn materialize_encoded_lit(binding: &Binding, gv: &BinaryGraphView) -> std::io::
let store = gv.store();
let val = gv.decode_value_from_kind(*o_kind, *o_key, *p_id, *dt_id, *lang_id)?;
match val {
FlakeValue::Ref(sid) => Ok(Binding::Sid(sid)),
FlakeValue::Ref(sid) => Ok(Binding::sid(sid)),
other => {
let dt_sid = store
.dt_sids()
Expand Down
4 changes: 2 additions & 2 deletions fluree-db-api/src/format/sparql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ fn format_binding(
Binding::Unbound | Binding::Poisoned => Ok(None),

// Reference (IRI or blank node)
Binding::Sid(sid) => {
Binding::Sid { sid, .. } => {
// SPARQL JSON output uses compact IRIs where possible (not full IRIs).
let iri = compactor.compact_sid(sid)?;
// Check if it's a blank node (starts with _:)
Expand Down Expand Up @@ -480,7 +480,7 @@ mod tests {
fn test_format_binding_uri() {
let compactor = make_test_compactor();
let result = make_test_result();
let binding = Binding::Sid(Sid::new(100, "alice"));
let binding = Binding::sid(Sid::new(100, "alice"));
let formatted = format_binding(&result, &binding, &compactor)
.unwrap()
.unwrap();
Expand Down
4 changes: 2 additions & 2 deletions fluree-db-api/src/format/sparql_xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ fn format_binding_xml(
match binding {
Binding::Unbound | Binding::Poisoned => Ok(None),

Binding::Sid(sid) => {
Binding::Sid { sid, .. } => {
let iri = compactor.decode_sid(sid)?;
Ok(Some(if iri.starts_with("_:") {
let mut s = String::from("<bnode>");
Expand Down Expand Up @@ -384,7 +384,7 @@ mod tests {

let schema = std::sync::Arc::from(vec![s_var].into_boxed_slice());
let sid = Sid::new(100, "alice");
let batch = Batch::single_row(schema, vec![Binding::Sid(sid)]).unwrap();
let batch = Batch::single_row(schema, vec![Binding::sid(sid)]).unwrap();
result.batches = vec![batch];

let xml = format(&result, &compactor, &FormatterConfig::sparql_xml()).unwrap();
Expand Down
Loading