Skip to main content

qdrant_edge/edge/update_only/batch/
mutation.rs

1//! What operations do to a single point, and how a point's mutations fold
2//! onto its stored form.
3
4use crate::segment::common::operation_error::{OperationError, OperationResult};
5use crate::segment::data_types::fully_qualified_point::{FullyQualifiedPoint, StoredPoint};
6use crate::segment::data_types::named_vectors::NamedVectors;
7use crate::segment::data_types::segment_record::NamedVectorBytesOwned;
8use crate::segment::json_path::JsonPath;
9use crate::segment::types::{Payload, PayloadKeyType, PointIdType, SeqNumberType, VectorNameBuf};
10
11/// The vectors an operation carries, in the form the operation carried them:
12/// storage-native bytes travel to the new slot untouched, decoded vectors are
13/// encoded by the storage. Keeping the two apart avoids a decode/re-encode
14/// round-trip a quantized storage would not survive losslessly.
15pub enum OperationVectors {
16    Decoded(NamedVectors<'static>),
17    Raw(NamedVectorBytesOwned),
18}
19
20/// What a single operation does to a single point; one variant per accepted
21/// operation.
22pub enum PointMutation {
23    /// Whole-point replacement (an upsert): both vectors and payload come from
24    /// the operation, and nothing of a previously stored point survives.
25    Replace {
26        vectors: OperationVectors,
27        payload: Payload,
28    },
29    /// The point is removed.
30    Delete,
31    /// Replace the named vectors, leaving the rest of the point alone.
32    UpdateVectors(NamedVectors<'static>),
33    /// Drop the named vectors, leaving the rest of the point alone.
34    DeleteVectors(Vec<VectorNameBuf>),
35    /// Merge into the stored payload, at `key` when given.
36    SetPayload {
37        payload: Payload,
38        key: Option<JsonPath>,
39    },
40    /// Replace the whole payload.
41    OverwritePayload(Payload),
42    /// Drop the listed payload keys.
43    DeletePayload(Vec<PayloadKeyType>),
44    /// Drop the whole payload.
45    ClearPayload,
46}
47
48impl PointMutation {
49    /// Whether this mutation makes every mutation before it irrelevant:
50    /// nothing of the point as it stood survives, so neither the earlier
51    /// mutations nor the stored point itself need to be looked at.
52    fn discards_stored_point(&self) -> bool {
53        match self {
54            Self::Replace { .. } | Self::Delete => true,
55            Self::UpdateVectors(_)
56            | Self::DeleteVectors(_)
57            | Self::SetPayload { .. }
58            | Self::OverwritePayload(_)
59            | Self::DeletePayload(_)
60            | Self::ClearPayload => false,
61        }
62    }
63}
64
65/// Everything a batch does to one point, in operation order.
66pub struct PointUpdates {
67    /// Operation number of the last operation folded in — the version the
68    /// rewritten point is stored at.
69    version: SeqNumberType,
70    /// Mutations to fold onto the stored point, oldest first. Never empty.
71    mutations: Vec<PointMutation>,
72}
73
74impl PointUpdates {
75    pub(super) fn new(version: SeqNumberType, mutation: PointMutation) -> Self {
76        Self {
77            version,
78            mutations: vec![mutation],
79        }
80    }
81
82    pub(super) fn push(&mut self, version: SeqNumberType, mutation: PointMutation) {
83        if mutation.discards_stored_point() {
84            self.mutations.clear();
85        }
86        self.version = self.version.max(version);
87        self.mutations.push(mutation);
88    }
89
90    /// Version the rewritten point is stored at.
91    pub fn version(&self) -> SeqNumberType {
92        self.version
93    }
94
95    /// Whether applying these mutations requires reading the point as it is
96    /// stored today. False exactly when the first surviving mutation replaces
97    /// or removes the point.
98    pub fn needs_stored_point(&self) -> bool {
99        self.mutations
100            .first()
101            .is_none_or(|mutation| !mutation.discards_stored_point())
102    }
103
104    /// Fold the mutations onto `stored` — the point as it stands, absent when
105    /// no segment holds it — into the point to store.
106    ///
107    /// `Ok(None)` means the batch leaves nothing to store: the point ends up
108    /// deleted, or an operation that can only modify an existing point named
109    /// one that does not exist.
110    pub fn materialize(
111        self,
112        id: PointIdType,
113        stored: Option<StoredPoint>,
114    ) -> OperationResult<Option<FullyQualifiedPoint>> {
115        let Self { version, mutations } = self;
116
117        let mut exists = stored.is_some();
118        let (mut stored_vectors, mut payload) = match stored {
119            Some(stored) => {
120                let StoredPoint {
121                    internal_id: _,
122                    vectors,
123                    payload,
124                } = stored;
125                (vectors, payload)
126            }
127            None => (NamedVectorBytesOwned::new(), Payload::default()),
128        };
129        // Vectors the batch supplied, which override `stored_vectors` by name
130        // (see `FullyQualifiedPoint`), so replacing one does not require
131        // removing its carried-over counterpart.
132        let mut updated_vectors = NamedVectors::default();
133
134        for mutation in mutations {
135            match mutation {
136                PointMutation::Replace {
137                    vectors,
138                    payload: replacement,
139                } => {
140                    exists = true;
141                    stored_vectors.clear();
142                    updated_vectors = NamedVectors::default();
143                    match vectors {
144                        OperationVectors::Decoded(vectors) => updated_vectors = vectors,
145                        OperationVectors::Raw(vectors) => stored_vectors = vectors,
146                    }
147                    payload = replacement;
148                }
149                PointMutation::Delete => {
150                    exists = false;
151                    stored_vectors.clear();
152                    updated_vectors = NamedVectors::default();
153                    payload = Payload::default();
154                }
155                PointMutation::UpdateVectors(vectors) => {
156                    if !exists {
157                        return Err(OperationError::PointIdError {
158                            missed_point_id: id,
159                        });
160                    }
161                    updated_vectors.merge(vectors);
162                }
163                PointMutation::DeleteVectors(names) => {
164                    for name in &names {
165                        stored_vectors.retain(|(stored_name, _)| stored_name != name);
166                        updated_vectors.remove_ref(name.as_str());
167                    }
168                }
169                PointMutation::SetPayload {
170                    payload: values,
171                    key,
172                } => match key {
173                    Some(key) => payload.merge_by_key(&values, &key),
174                    None => payload.merge(&values),
175                },
176                PointMutation::OverwritePayload(values) => payload = values,
177                PointMutation::DeletePayload(keys) => {
178                    for key in &keys {
179                        payload.remove(key);
180                    }
181                }
182                PointMutation::ClearPayload => payload = Payload::default(),
183            }
184        }
185
186        if !exists {
187            return Ok(None);
188        }
189
190        Ok(Some(FullyQualifiedPoint {
191            id,
192            version,
193            stored_vectors,
194            updated_vectors,
195            payload,
196        }))
197    }
198}