Skip to content
Open
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
25 changes: 23 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
updating a referenced element propagates the new hash along every chain,
and deleting/overwriting it cascades the chains away (each affected
reference must opt in via `cascade_on_update`). Opt-in per call through
the new `propagate_backward_references` flag on `InsertOptions` /
the new `propagate_backward_references_when_unsure` flag on `InsertOptions` /
`DeleteOptions`. The referrer list is stored on the element itself under a
two-layer hash (`combine(inner, backrefs)`), so registering a referrer
never re-hashes what existing referrers committed to; public reads return
Expand All @@ -26,7 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
verifier recomputes. Requires `GROVE_V4`; earlier versions, V0 proofs, and
`Provable*` aggregate parents reject the new variants (fail closed).
`apply_batch` supports the whole family when the batch opts in via
`BatchApplyOptions::propagate_backward_references`: a preprocessing pass
`BatchApplyOptions::propagate_backward_references_when_unsure`: a preprocessing pass
expands the batch into the derived registration/propagation/cascade
operations the live flagged flow performs (shared semantic core, so batch
and non-batch execution produce byte-identical root hashes), including
Expand All @@ -44,6 +44,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
displace, ≤10-hop chains, 1 referrer per reference) while pre-V4
estimation stays byte-stable for replay. See
`adr/bidirectional_references.md`.
- Per-op backward-references deletes for batches: `GroveOp::DeleteWithCascade`
reads the deleted element and cascades away every bidirectional reference
registered on it (each affected reference must allow `cascade_on_update`; a
deleted `BidirectionalReference` is de-registered from its target), and
`GroveOp::DeleteWithNoBackwardsReferenceCheck` deletes without the read,
leaving any registered references dangling — each whatever the batch's
`propagate_backward_references_when_unsure` says, which plain `Delete` keeps
following. An unflagged batch carrying a cascading delete runs the
backward-references expansion in per-op mode: the other ops are not read
and pay nothing extra. Constructors `delete_with_cascade_op` /
`delete_with_no_backwards_reference_check_op` (plus `_estimated_op`
twins); sort tags 21 / 22; the estimators charge the fan-out per op rather
than per flag. `GROVE_V4`+ full batches only: pre-V4 and partial batches
refuse both with `NotSupported`.
- **BREAKING**: Added `add_parent_tree_on_subquery` feature to PathQuery (#379)
- New field in `Query` struct: `add_parent_tree_on_subquery: bool`
- When set to `true`, parent tree elements (like CountTree or SumTree) are included in query results when performing subqueries
Expand All @@ -52,6 +66,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated proof verification logic to handle parent tree inclusion

### Changed
- **BREAKING**: renamed the `propagate_backward_references` field of
`InsertOptions`, `DeleteOptions` and `BatchApplyOptions` to
`propagate_backward_references_when_unsure`. The name says what the flag
buys: when the caller does not know whether the element it displaces
carries backward references, GroveDB reads it and finds out (and
propagates or cascades accordingly). Callers that do know say so per op
through the typed batch deletes above. Semantics are unchanged.
- Bumped the GroveDB workspace crates and their internal dependency requirements
to **6.0.0** for the public API changes since 5.0.1. This package version is
independent of the existing `GroveVersion` runtime compatibility versions.
Expand Down
30 changes: 24 additions & 6 deletions adr/bidirectional_references.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,13 @@ chain origin. When such behavior is required, a different type of element should
Moreover, these types are incompatible, which will be discussed in the "Rules" section.

Additionally, a new flag was added to `InsertOptions` and `DeleteOptions`
called `propagate_backward_references` (`ClearOptions` support is deferred —
called `propagate_backward_references_when_unsure` (`ClearOptions` support is deferred —
see the limitations below). Since propagation incurs a cost, starting with the
checks required to determine whether it should be performed, bidirectional references are
optional and must be explicitly enabled.
optional and must be explicitly enabled. The name says what the flag buys: when the
caller does not know whether the element it displaces carries backward references,
GroveDB reads it and finds out. A caller that does know can say so per batch op
instead — see the typed deletes under Batching below.

Even when a user inserts something unrelated to the bidirectional references feature,
a check must still be performed to determine whether the insertion overwrites an item
Expand All @@ -78,7 +81,7 @@ Current limitations (fail closed, lift as needed):
- The four variants may not be wrapped in the aggregation wrappers
(`NonCounted` / `NotSummed` / `NotCountedOrSummed`).
- `apply_batch` supports the family when the batch opts in via
`BatchApplyOptions::propagate_backward_references` (see the batching
`BatchApplyOptions::propagate_backward_references_when_unsure` (see the batching
section under Implementation); batches without the flag — and partial
batches, which have no expansion support — reject ops carrying the
family. A flagged batch also refuses to delete a NON-EMPTY subtree:
Expand All @@ -93,7 +96,7 @@ Current limitations (fail closed, lift as needed):
propagations and cascades skip it and lazily clear its slot — but
`verify_grovedb` reports the affected references until the chain is
rewritten through flagged operations.
- `clear_subtree` has no `propagate_backward_references` option yet; use
- `clear_subtree` has no `propagate_backward_references_when_unsure` option yet; use
`delete` with the flag for cascade-aware removal.
- Under the flag, insert supports items, references, and empty plain-Merk
trees; delete supports plain Merk subtrees. The specialized data trees
Expand All @@ -106,7 +109,7 @@ Current limitations (fail closed, lift as needed):

Next, we’ll go over the rules and limitations for using bidirectional references.

Note that for the rules to apply, the `propagate_backward_references` flag needs to be
Note that for the rules to apply, the `propagate_backward_references_when_unsure` flag needs to be
set.

An 'Element with backward references' refers to `ItemWithBackwardsReferences`,
Expand Down Expand Up @@ -165,7 +168,7 @@ preventing the operation from completing successfully.
### Batching

`apply_batch` supports the whole family when the batch sets
`BatchApplyOptions::propagate_backward_references` (GROVE_V4+, riding the
`BatchApplyOptions::propagate_backward_references_when_unsure` (GROVE_V4+, riding the
same activation as the live flagged flow). A preprocessing pass
(`batch::backward_references`) expands the user's operations into the
derived operations the live flow would perform, planned by the SAME
Expand Down Expand Up @@ -194,6 +197,21 @@ same batch that deletes its target; a cascade deleting a position another
op touches; a propagation rewrite hitting a user delete; and
`RefreshReference` on a position holding a bidirectional reference.

Two typed deletes pin the decision per op, whatever the batch flag says
(GROVE_V4+, full batches only): `GroveOp::DeleteWithCascade` reads the
element and runs exactly the flagged delete's bookkeeping (cascade of every
referrer chain, consent required, de-registration of a deleted reference),
and `GroveOp::DeleteWithNoBackwardsReferenceCheck` deletes without the read,
leaving whatever was registered on the element dangling — exactly the
unflagged delete. Plain `Delete` keeps following the flag. An unflagged
batch carrying a cascading delete runs the expansion in per-op mode: only
the cascading deletes are read and planned, the other ops stay ordinary
unflagged ops (no read, no bookkeeping) and only their certain effects are
staged into the overlay, so the cascade resolves against the batch's
outcome and the M4 conflict rules apply unchanged. Partial batches and
pre-V4 versions refuse both typed ops with `NotSupported` rather than
silently degrading them to a plain delete.

Estimated costs (average and worst case) model the derived fan-out on
GROVE_V4+ under the batch flag, bounded by the budgets above (a written
item's DECLARED referrer capacity, the 256 ceiling for writes that cannot
Expand Down
13 changes: 13 additions & 0 deletions docs/book/src/batch-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub enum GroveOp {
Patch { element: Element, change_in_bytes: i32 },
RefreshReference { reference_path_type, max_reference_hop, flags, trust_refresh_reference },
Delete,
DeleteWithCascade, // Read + cascade bidirectional references, whatever the batch flag says (GROVE_V4+)
DeleteWithNoBackwardsReferenceCheck, // Never read for backward references, whatever the batch flag says (GROVE_V4+)
DeleteTree(TreeType, SubelementsDeletionBehavior), // Per-op deletion policy

// Non-Merk tree append operations (user-facing):
Expand Down Expand Up @@ -40,6 +42,17 @@ pub enum NonMerkTreeMeta {
}
```

**Typed deletes.** A plain `Delete` is read for backward-references
bookkeeping only when the batch sets
`BatchApplyOptions::propagate_backward_references_when_unsure`. The two
typed variants pin that decision for one op: `DeleteWithCascade` always
reads the element and cascades away every bidirectional reference registered
on it (each must allow `cascade_on_update`, and a deleted reference is
de-registered from its target), while `DeleteWithNoBackwardsReferenceCheck`
never reads it and leaves registered references dangling. Both require
`GROVE_V4`+ and a full (non-partial) batch. See
`adr/bidirectional_references.md`.

**SubelementsDeletionBehavior** controls how a `DeleteTree` handles non-empty subtrees:

```rust
Expand Down
2 changes: 2 additions & 0 deletions docs/crates/grovedb.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ pub enum GroveDbOp {
InsertOrReplace { element: Element },
Replace { element: Element },
Delete,
DeleteWithCascade, // GROVE_V4+: read + cascade bidirectional references, whatever the batch flag says
DeleteWithNoBackwardsReferenceCheck, // GROVE_V4+: never read for backward references
DeleteTree,
DeleteUpTree { stop_path_height: Option<u16> },
TransientInsertTreeWithRootHash { hash: [u8; 32], .. },
Expand Down
2 changes: 1 addition & 1 deletion grovedb-version/src/version/grovedb_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ pub struct GroveDBOperationsAverageCaseVersions {
/// family in batches, so historical admission decisions replay
/// byte-identically.
/// - `1` (V4+): family-carrying ops and (under
/// `BatchApplyOptions::propagate_backward_references`) deletes charge
/// `BatchApplyOptions::propagate_backward_references_when_unsure`) deletes charge
/// the derived registration / propagation / cascade fan-out, bounded
/// by the apply path's budgets (≤32 referrers per item, ≤10-hop
/// chains, 1 referrer per reference), and the derived op itself gets
Expand Down
2 changes: 1 addition & 1 deletion grovedb-version/src/version/v4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ pub const GROVE_V4: GroveVersion = GroveVersion {
insert: 0,
// v1: backward-references router. Calls that neither insert a
// BidirectionalReference nor set
// propagate_backward_references run the exact v0 body.
// propagate_backward_references_when_unsure run the exact v0 body.
insert_on_transaction: 1,
// v2: a directly inserted Reference binds the value hash of its
// terminal's STORED bytes (wrapper included for a NonCounted
Expand Down
Loading
Loading