Skip to main content

nodedb_physical/physical_plan/vector/
resolved_mutation.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! The decided mutation set a governed vector-primary write resolved to.
4//!
5//! A vector-primary `DELETE` / `UPDATE` / conflict-patching `UPSERT` on a
6//! collection with a row-level write policy cannot cross the Raft wire
7//! carrying the live predicate: a follower has no writing identity to decide
8//! it against. The Control Plane resolves the write against the rows the
9//! Data Plane holds, decides the policy there, and ships the row mutations
10//! themselves.
11
12use nodedb_types::Surrogate;
13
14/// One row mutation a resolved vector-primary write applies.
15///
16/// Every variant carries the sidecar bytes the resolve read for its row —
17/// the apply's drift check. A row's sidecar is its `zerompk` TAGGED payload
18/// map and is never empty once written, so `Vec::new()` on `Delete` /
19/// `Update` names a bound node with no sidecar row behind it, and the apply
20/// compares it against an absent sidecar.
21#[derive(
22    Debug,
23    Clone,
24    PartialEq,
25    serde::Serialize,
26    serde::Deserialize,
27    zerompk::ToMessagePack,
28    zerompk::FromMessagePack,
29)]
30pub enum VectorResolvedMutation {
31    /// Remove `surrogate`'s HNSW node, bitmap entries, and sidecar.
32    Delete {
33        surrogate: Surrogate,
34        /// Sidecar bytes the resolve read; the node must still be bound and
35        /// hold exactly these bytes.
36        old_payload: Vec<u8>,
37    },
38    /// Rewrite `surrogate`'s row: a `new_vector` rebuilds the HNSW node
39    /// under the same surrogate, `merged_payload` replaces the sidecar and
40    /// moves the bitmap entries.
41    Update {
42        surrogate: Surrogate,
43        new_vector: Option<Vec<f32>>,
44        /// The full post-image sidecar, already merged and lower-cased,
45        /// in the sidecar's own `zerompk` TAGGED encoding.
46        merged_payload: Vec<u8>,
47        /// Sidecar bytes the resolve read; see `Delete::old_payload`.
48        old_payload: Vec<u8>,
49    },
50    /// Store a whole row under `surrogate`, replacing any existing one.
51    Upsert {
52        surrogate: Surrogate,
53        /// UTF-8 of the declared primary-key value. Followers bind the
54        /// leader-assigned surrogate to this exact key.
55        pk_bytes: Vec<u8>,
56        vector: Vec<f32>,
57        /// The sidecar to store, in its own `zerompk` TAGGED encoding.
58        payload: Vec<u8>,
59        /// `None` requires the surrogate to still be unbound; `Some(bytes)`
60        /// requires a bound node whose sidecar holds exactly `bytes`.
61        old_payload: Option<Vec<u8>>,
62    },
63}
64
65impl VectorResolvedMutation {
66    /// The surrogate this one mutation targets.
67    pub fn surrogate(&self) -> Surrogate {
68        match self {
69            VectorResolvedMutation::Delete { surrogate, .. }
70            | VectorResolvedMutation::Update { surrogate, .. }
71            | VectorResolvedMutation::Upsert { surrogate, .. } => *surrogate,
72        }
73    }
74
75    /// The vector this mutation stores, when it stores one.
76    pub fn stored_vector(&self) -> Option<&[f32]> {
77        match self {
78            VectorResolvedMutation::Delete { .. } => None,
79            VectorResolvedMutation::Update { new_vector, .. } => new_vector.as_deref(),
80            VectorResolvedMutation::Upsert { vector, .. } => Some(vector.as_slice()),
81        }
82    }
83}
84
85/// What `VectorOp::ResolveDirectWrite` reports back: every row mutation the
86/// intercepted write applies, plus the exact response payload the statement
87/// returns once they all apply cleanly.
88///
89/// An empty `mutations` list is a legitimate outcome — a predicate that
90/// matches no row writes nothing and still owes its `{"affected": 0}` reply.
91#[derive(
92    Debug,
93    Clone,
94    PartialEq,
95    serde::Serialize,
96    serde::Deserialize,
97    zerompk::ToMessagePack,
98    zerompk::FromMessagePack,
99)]
100pub struct VectorResolveOutcome {
101    pub mutations: Vec<VectorResolvedMutation>,
102    pub response_payload: Vec<u8>,
103}