qdrant_edge/edge/update_only/batch/
plan.rs1use ahash::AHashMap;
4use crate::segment::common::operation_error::{OperationError, OperationResult};
5use crate::segment::data_types::named_vectors::NamedVectors;
6use crate::segment::types::{PointIdType, SeqNumberType};
7use crate::shard::operations::CollectionUpdateOperations;
8use crate::shard::operations::payload_ops::PayloadOps;
9use crate::shard::operations::point_ops::{
10 PointOperations, PointStructPersisted, PointStructRawPersisted,
11};
12use crate::shard::operations::vector_ops::{PointVectorsPersisted, VectorOperations};
13
14use super::mutation::{OperationVectors, PointMutation, PointUpdates};
15
16pub struct UpdateBatchPlan {
18 order: Vec<PointIdType>,
21 updates: AHashMap<PointIdType, PointUpdates>,
22}
23
24impl UpdateBatchPlan {
25 pub fn build(
33 operations: impl IntoIterator<Item = (SeqNumberType, CollectionUpdateOperations)>,
34 ) -> OperationResult<Self> {
35 let mut plan = Self {
36 order: Vec::new(),
37 updates: AHashMap::new(),
38 };
39
40 for (op_num, operation) in operations {
41 match operation {
42 CollectionUpdateOperations::PointOperation(operation) => {
43 plan.push_point_operation(op_num, operation)?;
44 }
45 CollectionUpdateOperations::VectorOperation(operation) => {
46 plan.push_vector_operation(op_num, operation)?;
47 }
48 CollectionUpdateOperations::PayloadOperation(operation) => {
49 plan.push_payload_operation(op_num, operation)?;
50 }
51 CollectionUpdateOperations::FieldIndexOperation(_) => {
52 return Err(unsupported("payload index operations"));
53 }
54 CollectionUpdateOperations::VectorNameOperation(_) => {
55 return Err(unsupported("vector name operations"));
56 }
57 #[cfg(feature = "staging")]
58 CollectionUpdateOperations::StagingOperation(_) => {
59 return Err(unsupported("staging operations"));
60 }
61 }
62 }
63
64 Ok(plan)
65 }
66
67 fn push(&mut self, id: PointIdType, version: SeqNumberType, mutation: PointMutation) {
68 match self.updates.entry(id) {
69 std::collections::hash_map::Entry::Occupied(mut entry) => {
70 entry.get_mut().push(version, mutation);
71 }
72 std::collections::hash_map::Entry::Vacant(entry) => {
73 entry.insert(PointUpdates::new(version, mutation));
74 self.order.push(id);
75 }
76 }
77 }
78
79 fn push_point_operation(
80 &mut self,
81 op_num: SeqNumberType,
82 operation: PointOperations,
83 ) -> OperationResult<()> {
84 match operation {
85 PointOperations::UpsertPoints(operation) => {
86 for point in operation.into_point_vec() {
87 let vectors = OperationVectors::Decoded(point.get_vectors().into_owned());
91 let PointStructPersisted {
92 id,
93 vector: _,
94 payload,
95 } = point;
96 self.push(
97 id,
98 op_num,
99 PointMutation::Replace {
100 vectors,
101 payload: payload.unwrap_or_default(),
102 },
103 );
104 }
105 }
106 PointOperations::UpsertPointsRaw(points) => {
107 for point in points {
108 let PointStructRawPersisted {
109 id,
110 vectors,
111 payload,
112 } = point;
113 self.push(
114 id,
115 op_num,
116 PointMutation::Replace {
117 vectors: OperationVectors::Raw(vectors),
118 payload: payload.unwrap_or_default(),
119 },
120 );
121 }
122 }
123 PointOperations::DeletePoints { ids } => {
124 for id in ids {
125 self.push(id, op_num, PointMutation::Delete);
126 }
127 }
128 PointOperations::UpsertPointsConditional(_) => {
129 return Err(unsupported("conditional upserts"));
130 }
131 PointOperations::DeletePointsByFilter(_) => {
132 return Err(unsupported("deleting points by filter"));
133 }
134 PointOperations::SyncPoints(_) | PointOperations::SyncPointsRaw(_) => {
135 return Err(unsupported("point sync"));
136 }
137 }
138 Ok(())
139 }
140
141 fn push_vector_operation(
142 &mut self,
143 op_num: SeqNumberType,
144 operation: VectorOperations,
145 ) -> OperationResult<()> {
146 match operation {
147 VectorOperations::UpdateVectors(operation) => {
148 if operation.update_filter.is_some() {
149 return Err(unsupported("conditional vector updates"));
150 }
151 for point in operation.points {
152 let PointVectorsPersisted { id, vector } = point;
153 let vectors = NamedVectors::from(vector).into_owned();
154 self.push(id, op_num, PointMutation::UpdateVectors(vectors));
155 }
156 }
157 VectorOperations::DeleteVectors(points, vector_names) => {
158 for id in points.points {
159 self.push(
160 id,
161 op_num,
162 PointMutation::DeleteVectors(vector_names.clone()),
163 );
164 }
165 }
166 VectorOperations::DeleteVectorsByFilter(_, _) => {
167 return Err(unsupported("deleting vectors by filter"));
168 }
169 }
170 Ok(())
171 }
172
173 fn push_payload_operation(
174 &mut self,
175 op_num: SeqNumberType,
176 operation: PayloadOps,
177 ) -> OperationResult<()> {
178 match operation {
179 PayloadOps::SetPayload(operation) => {
180 let points = require_points(operation.points, operation.filter.is_some())?;
181 for id in points {
182 self.push(
183 id,
184 op_num,
185 PointMutation::SetPayload {
186 payload: operation.payload.clone(),
187 key: operation.key.clone(),
188 },
189 );
190 }
191 }
192 PayloadOps::OverwritePayload(operation) => {
193 let points = require_points(operation.points, operation.filter.is_some())?;
194 for id in points {
195 self.push(
196 id,
197 op_num,
198 PointMutation::OverwritePayload(operation.payload.clone()),
199 );
200 }
201 }
202 PayloadOps::DeletePayload(operation) => {
203 let points = require_points(operation.points, operation.filter.is_some())?;
204 for id in points {
205 self.push(
206 id,
207 op_num,
208 PointMutation::DeletePayload(operation.keys.clone()),
209 );
210 }
211 }
212 PayloadOps::ClearPayload { points } => {
213 for id in points {
214 self.push(id, op_num, PointMutation::ClearPayload);
215 }
216 }
217 PayloadOps::ClearPayloadByFilter(_) => {
218 return Err(unsupported("clearing payload by filter"));
219 }
220 }
221 Ok(())
222 }
223
224 pub fn is_empty(&self) -> bool {
225 self.order.is_empty()
226 }
227
228 pub fn len(&self) -> usize {
229 self.order.len()
230 }
231
232 pub fn point_ids(&self) -> impl Iterator<Item = PointIdType> + '_ {
234 self.order.iter().copied()
235 }
236
237 pub fn point_ids_needing_stored_point(&self) -> impl Iterator<Item = PointIdType> + '_ {
240 self.order
241 .iter()
242 .copied()
243 .filter(|id| self.updates[id].needs_stored_point())
244 }
245
246 pub fn into_point_updates(mut self) -> impl Iterator<Item = (PointIdType, PointUpdates)> {
248 let order = std::mem::take(&mut self.order);
249 order.into_iter().filter_map(move |id| {
250 let updates = self.updates.remove(&id)?;
251 Some((id, updates))
252 })
253 }
254}
255
256fn require_points(
259 points: Option<Vec<PointIdType>>,
260 has_filter: bool,
261) -> OperationResult<Vec<PointIdType>> {
262 match points {
263 Some(points) => Ok(points),
264 None if has_filter => Err(unsupported("selecting points by filter")),
265 None => Err(OperationError::validation_error(
266 "No points or filter specified",
267 )),
268 }
269}
270
271fn unsupported(what: &str) -> OperationError {
272 OperationError::validation_error(format!("The update-only writer does not support {what}"))
273}