Skip to content

feat: Enrich Cypher transaction commit result for write parity - #1527

Merged
bplatz merged 3 commits into
mainfrom
feat/cypher-txn-commit-result-parity
Jul 22, 2026
Merged

bplatz merged 3 commits into
mainfrom
feat/cypher-txn-commit-result-parity

Conversation

@bplatz

@bplatz bplatz commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Problem

commit_cypher_transaction returned only the committed t, and both write staging helpers ran Tracker::disabled(). As of v4.1.3, WritePlan gained a Sequential variant for multi-clause statements that can only be driven through the CypherTransaction API — but that API couldn't report the signals a transactor reads off stage().execute(): commit_id, fuel (billing), and indexing state (needed, novelty_size). Without fuel, multi-clause writes can't be billed; without the indexing signals, novelty accumulates and never triggers background reindexing.

Changes

  • Meter fuel. begin_cypher_transaction now takes TrackingOptions and pins a shared Tracker on the transaction. Since Tracker accumulates atomically and fuel is charged at staging time, fuel sums automatically across every write statement and every clause of a sequential write — no per-clause bookkeeping. Both Tracker::disabled() sites now use the transaction's tracker.
  • Enrich the commit result. commit_cypher_transaction returns TransactResultRef — the same struct the autocommit execute() path yields — so callers map one result shape. Carries the final commit_id + t, the flake count summed across all staged statements, the indexing signals (needed, novelty_size), and the accumulated fuel tally. Per-statement RETURN rows continue to flow via CypherTxnWriteOutcome.
  • Warning cleanup. Gate the format_binding_with_result re-export to the iceberg feature (its only consumer), clearing an unused-import warning under default features.

Callers updated: bolt.rs (begin + commit) and the bolt transaction test. Added tests covering the fuel tally + indexing signals on the enriched result, and that opting out of tracking leaves the tally empty.

Testing

  • it_cypher_seq_writes — 20/20 pass (incl. 2 new).
  • Server bolt/cypher suite — 48/48 pass.
  • cargo clippy -p fluree-db-api -p fluree-db-server --all-features --all-targets clean; warning fix verified under both default and iceberg features.

commit_cypher_transaction returned only the committed t, and both write
staging helpers ran Tracker::disabled(), so multi-clause Cypher writes
driven through the CypherTransaction API could not be billed or trigger
background reindexing.

- begin_cypher_transaction takes TrackingOptions and pins a shared Tracker
  on the transaction; because the tracker accumulates atomically, fuel sums
  automatically across every write statement and every sequential clause.
- Replace both Tracker::disabled() sites with the transaction's tracker.
- commit_cypher_transaction returns TransactResultRef (receipt with the
  final commit id, t, and flake count summed across staged statements;
  indexing signals; fuel tally) — parity with the autocommit execute() path
  so callers map one result shape.
- Gate the format_binding_with_result re-export to the iceberg feature, its
  only consumer, clearing an unused-import warning under default features.
@bplatz
bplatz requested review from aaj3f and zonotope July 21, 2026 01:08

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

One suggestion, but lgm

Comment thread fluree-db-api/src/cypher_txn.rs Outdated
Comment on lines +423 to +433
// Head unchanged at the pinned base. `begin` requires a resolvable
// ledger, so a prior commit — hence a base head id — always exists.
let commit_id = txn
.base_head
.as_ref()
.and_then(|r| r.id.clone())
.ok_or_else(|| {
ApiError::internal(
"cannot commit an all-reads Cypher transaction on an uncommitted ledger",
)
})?;

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.

This is inconsistent with the rest of the system, and a regression. create_ledger just registers a new ledger with the nameservice but doesn't write a genesis commit and its commit_head_id is None. Users can then add data after the fact with no error. Plus, before this pr an all reads commit here just returned Ok(0) instead of erroring. I think it would be more consistent to go back to that behavior and allow commits on such a ledger after only reads to just return what the autocommit noop paths return with t == 0 instead of erroring.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the catch, addressed in 0e3cd74

Erroring when the transaction had no base head id was a regression:
create_ledger registers a nameservice record without writing a genesis
commit, so head_commit_id is legitimately None and data can still be
added after the fact. An all-reads COMMIT on such a ledger previously
succeeded, and must keep doing so.

Return the same no-op receipt the autocommit paths use — the
empty-content commit id with the base t (0 on such a ledger) and no
reindex signal — instead of erroring. Adds a regression test covering
begin/commit on a create_ledger ledger and a write after it.
@bplatz
bplatz merged commit 64de029 into main Jul 22, 2026
13 checks passed
@bplatz
bplatz deleted the feat/cypher-txn-commit-result-parity branch July 22, 2026 01:06
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