Skip to main content

qdrant_edge/shard/operations/
point_ops.rs

1use std::collections::{HashMap, HashSet};
2use std::fmt::{Debug, Formatter};
3use std::hash::{Hash, Hasher};
4use std::mem;
5
6#[cfg(feature = "api")]
7use api::grpc::RawPayload;
8use crate::common::validation::validate_multi_vector;
9use itertools::Itertools as _;
10use ordered_float::OrderedFloat;
11use schemars::JsonSchema;
12use crate::segment::common::operation_error::OperationError;
13use crate::segment::common::utils::unordered_hash_unique;
14use crate::segment::data_types::named_vectors::NamedVectors;
15use crate::segment::data_types::segment_record::{SegmentRecord, SegmentRecordRaw};
16use crate::segment::data_types::vectors::{
17    BatchVectorStructInternal, DEFAULT_VECTOR_NAME, DenseVector, MultiDenseVector,
18    MultiDenseVectorInternal, VectorInternal, VectorRef, VectorStructInternal,
19};
20use crate::segment::types::{Filter, Payload, PointIdType, VectorNameBuf};
21use serde::{Deserialize, Serialize};
22use smallvec::SmallVec;
23use crate::sparse::common::types::{DimId, DimWeight};
24use strum::{EnumDiscriminants, EnumIter};
25use validator::{Validate, ValidationErrors};
26
27/// Defines the mode of the upsert operation
28///
29/// * `Upsert` - default mode, insert new points, update existing points
30/// * `InsertOnly` - only insert new points, do not update existing points
31/// * `UpdateOnly` - only update existing points, do not insert new points
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize, Hash)]
33#[serde(rename_all = "snake_case")]
34pub enum UpdateMode {
35    // Default mode - insert new points, update existing points
36    #[default]
37    Upsert,
38    // Only insert new points, do not update existing points
39    InsertOnly,
40    // Only update existing points, do not insert new points
41    UpdateOnly,
42}
43
44#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema, Validate, Hash)]
45#[serde(rename_all = "snake_case")]
46pub struct PointIdsList {
47    pub points: Vec<PointIdType>,
48    #[cfg(feature = "api")]
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    pub shard_key: Option<api::rest::ShardKeySelector>,
51}
52
53impl From<Vec<PointIdType>> for PointIdsList {
54    fn from(points: Vec<PointIdType>) -> Self {
55        Self {
56            points,
57            #[cfg(feature = "api")]
58            shard_key: None,
59        }
60    }
61}
62
63// General idea of having an extra layer of data structures after REST and gRPC
64// is to ensure that all vectors are inferenced and validated before they are persisted.
65//
66// This separation allows to have a single point, enforced by the type system,
67// where all Documents and other inference-able objects are resolved into raw vectors.
68//
69// Separation between VectorStructPersisted and VectorStructInternal is only needed
70// for legacy reasons, as the previous implementations wrote VectorStruct to WAL,
71// so we need an ability to read it back. VectorStructPersisted reproduces the same
72// structure as VectorStruct had in the previous versions.
73//
74//
75//        gRPC              REST API           ┌───┐              WAL
76//          │                  │               │ I │               ▲
77//          │                  │               │ n │               │
78//          │                  │               │ f │               │
79//  ┌───────▼───────┐    ┌─────▼──────┐        │ e │     ┌─────────┴───────────┐
80//  │ grpc::Vectors ├───►│VectorStruct├───────►│ r ├────►│VectorStructPersisted├─────┐
81//  └───────────────┘    └────────────┘        │ e │     └─────────────────────┘     │
82//                        Vectors              │ n │      Only Vectors               │
83//                        + Documents          │ c │                                 │
84//                        + Images             │ e │                                 │
85//                        + Other inference    └───┘                                 │
86//                        Implement JsonSchema                                       │
87//                                                       ┌─────────────────────┐     │
88//                                                       │                     ◄─────┘
89//                                                       │   Storage           │
90//                                                       │                     │
91//                        REST API Response              └────────┬────────────┘
92//                             ▲                                  │
93//                             │                                  │
94//                      ┌──────┴──────────────┐         ┌─────────▼───────────┐
95//                      │ VectorStructOutput  ◄───┬─────┤VectorStructInternal │
96//                      └─────────────────────┘   │     └─────────────────────┘
97//                       Only Vectors             │      Only Vectors
98//                       Implement JsonSchema     │      Optimized for search
99//                                                │
100//                                                │
101//                      ┌─────────────────────┐   │
102//                      │ grpc::VectorsOutput ◄───┘
103//                      └───────────┬─────────┘
104//                                  │
105//                                  ▼
106//                              gPRC Response
107
108#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, EnumDiscriminants, Hash)]
109#[strum_discriminants(derive(EnumIter))]
110#[serde(rename_all = "snake_case")]
111pub enum PointOperations {
112    /// Insert or update points
113    UpsertPoints(PointInsertOperationsInternal),
114    /// Insert points, or update existing points if condition matches
115    UpsertPointsConditional(ConditionalInsertOperationInternal),
116    /// Delete point if exists
117    DeletePoints { ids: Vec<PointIdType> },
118    /// Delete points by given filter criteria
119    DeletePointsByFilter(Filter),
120    /// Points Sync
121    SyncPoints(PointSyncOperation),
122    /// Insert or update points with storage-native (raw bytes) vectors
123    UpsertPointsRaw(Vec<PointStructRawPersisted>),
124    /// Points sync with storage-native (raw bytes) vectors
125    SyncPointsRaw(PointSyncRawOperation),
126}
127
128impl PointOperations {
129    pub fn point_ids(&self) -> Option<Vec<PointIdType>> {
130        match self {
131            Self::UpsertPoints(op) => Some(op.point_ids()),
132            Self::UpsertPointsConditional(op) => Some(op.points_op.point_ids()),
133            Self::DeletePoints { ids } => Some(ids.clone()),
134            Self::DeletePointsByFilter(_) => None,
135            Self::SyncPoints(op) => Some(op.points.iter().map(|point| point.id).collect()),
136            Self::UpsertPointsRaw(points) => Some(points.iter().map(|point| point.id).collect()),
137            Self::SyncPointsRaw(op) => Some(op.points.iter().map(|point| point.id).collect()),
138        }
139    }
140
141    pub fn retain_point_ids<F>(&mut self, filter: F)
142    where
143        F: Fn(&PointIdType) -> bool,
144    {
145        match self {
146            Self::UpsertPoints(op) => op.retain_point_ids(filter),
147            Self::UpsertPointsConditional(op) => {
148                op.points_op.retain_point_ids(filter);
149            }
150            Self::DeletePoints { ids } => ids.retain(filter),
151            Self::DeletePointsByFilter(_) => (),
152            Self::SyncPoints(op) => op.points.retain(|point| filter(&point.id)),
153            Self::UpsertPointsRaw(points) => points.retain(|point| filter(&point.id)),
154            Self::SyncPointsRaw(op) => op.points.retain(|point| filter(&point.id)),
155        }
156    }
157
158    /// Drop named-vector references to names not in `valid`. See
159    /// [`CollectionUpdateOperations::retain_vector_names`].
160    pub fn retain_vector_names(&mut self, valid: &HashSet<VectorNameBuf>) {
161        match self {
162            Self::UpsertPoints(op) => op.retain_vector_names(valid),
163            Self::UpsertPointsConditional(op) => op.points_op.retain_vector_names(valid),
164            Self::SyncPoints(op) => {
165                for point in &mut op.points {
166                    point.vector.retain_vector_names(valid);
167                }
168            }
169            Self::UpsertPointsRaw(points) => {
170                for point in points {
171                    point.vectors.retain(|(name, _)| valid.contains(name));
172                }
173            }
174            Self::SyncPointsRaw(op) => {
175                for point in &mut op.points {
176                    point.vectors.retain(|(name, _)| valid.contains(name));
177                }
178            }
179            Self::DeletePoints { .. } | Self::DeletePointsByFilter(_) => (),
180        }
181    }
182}
183
184#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, EnumDiscriminants, Hash)]
185#[strum_discriminants(derive(EnumIter))]
186#[serde(rename_all = "snake_case")]
187pub enum PointInsertOperationsInternal {
188    /// Inset points from a batch.
189    #[serde(rename = "batch")]
190    PointsBatch(BatchPersisted),
191    /// Insert points from a list
192    #[serde(rename = "points")]
193    PointsList(Vec<PointStructPersisted>),
194}
195
196impl PointInsertOperationsInternal {
197    pub fn point_ids(&self) -> Vec<PointIdType> {
198        match self {
199            Self::PointsBatch(batch) => batch.ids.clone(),
200            Self::PointsList(points) => points.iter().map(|point| point.id).collect(),
201        }
202    }
203
204    pub fn into_point_vec(self) -> Vec<PointStructPersisted> {
205        match self {
206            PointInsertOperationsInternal::PointsBatch(batch) => {
207                let batch_vectors = BatchVectorStructInternal::from(batch.vectors);
208                let all_vectors = batch_vectors.into_all_vectors(batch.ids.len());
209                let vectors_iter = batch.ids.into_iter().zip(all_vectors);
210                match batch.payloads {
211                    None => vectors_iter
212                        .map(|(id, vectors)| PointStructPersisted {
213                            id,
214                            vector: VectorStructInternal::from(vectors).into(),
215                            payload: None,
216                        })
217                        .collect(),
218                    Some(payloads) => vectors_iter
219                        .zip(payloads)
220                        .map(|((id, vectors), payload)| PointStructPersisted {
221                            id,
222                            vector: VectorStructInternal::from(vectors).into(),
223                            payload,
224                        })
225                        .collect(),
226                }
227            }
228            PointInsertOperationsInternal::PointsList(points) => points,
229        }
230    }
231
232    /// Drop named-vector references to names not in `valid`. See
233    /// [`CollectionUpdateOperations::retain_vector_names`].
234    pub fn retain_vector_names(&mut self, valid: &HashSet<VectorNameBuf>) {
235        match self {
236            Self::PointsBatch(batch) => batch.vectors.retain_vector_names(valid),
237            Self::PointsList(points) => {
238                for point in points {
239                    point.vector.retain_vector_names(valid);
240                }
241            }
242        }
243    }
244
245    pub fn retain_point_ids<F>(&mut self, filter: F)
246    where
247        F: Fn(&PointIdType) -> bool,
248    {
249        match self {
250            Self::PointsBatch(batch) => {
251                let mut retain_indices = HashSet::new();
252
253                retain_with_index(&mut batch.ids, |index, id| {
254                    if filter(id) {
255                        retain_indices.insert(index);
256                        true
257                    } else {
258                        false
259                    }
260                });
261
262                match &mut batch.vectors {
263                    BatchVectorStructPersisted::Single(vectors) => {
264                        retain_with_index(vectors, |index, _| retain_indices.contains(&index));
265                    }
266
267                    BatchVectorStructPersisted::MultiDense(vectors) => {
268                        retain_with_index(vectors, |index, _| retain_indices.contains(&index));
269                    }
270
271                    BatchVectorStructPersisted::Named(vectors) => {
272                        for vectors in vectors.values_mut() {
273                            retain_with_index(vectors, |index, _| retain_indices.contains(&index));
274                        }
275                    }
276                }
277
278                if let Some(payload) = &mut batch.payloads {
279                    retain_with_index(payload, |index, _| retain_indices.contains(&index));
280                }
281            }
282
283            Self::PointsList(points) => points.retain(|point| filter(&point.id)),
284        }
285    }
286}
287
288impl From<BatchPersisted> for PointInsertOperationsInternal {
289    fn from(batch: BatchPersisted) -> Self {
290        PointInsertOperationsInternal::PointsBatch(batch)
291    }
292}
293
294impl From<Vec<PointStructPersisted>> for PointInsertOperationsInternal {
295    fn from(points: Vec<PointStructPersisted>) -> Self {
296        PointInsertOperationsInternal::PointsList(points)
297    }
298}
299
300#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Hash)]
301pub struct ConditionalInsertOperationInternal {
302    pub points_op: PointInsertOperationsInternal,
303    /// Condition to check, if the point already exists
304    pub condition: Filter,
305    /// Mode of the upsert operation. If None, defaults to Upsert behavior.
306    #[serde(default, skip_serializing_if = "Option::is_none")]
307    pub update_mode: Option<UpdateMode>,
308}
309
310#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Hash)]
311pub struct PointSyncOperation {
312    /// Minimal id of the sync range
313    pub from_id: Option<PointIdType>,
314    /// Maximal id og
315    pub to_id: Option<PointIdType>,
316    pub points: Vec<PointStructPersisted>,
317}
318
319#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Hash)]
320pub struct PointSyncRawOperation {
321    /// Minimal id of the sync range
322    pub from_id: Option<PointIdType>,
323    /// Maximal id of the sync range
324    pub to_id: Option<PointIdType>,
325    pub points: Vec<PointStructRawPersisted>,
326}
327
328pub type RawVectorsPersisted = SmallVec<[(VectorNameBuf, Vec<u8>); 1]>;
329
330/// A point with vectors as storage-native bytes, as it is persisted in WAL.
331#[derive(Clone, PartialEq, Deserialize, Serialize, Hash)]
332#[serde(rename_all = "snake_case")]
333pub struct PointStructRawPersisted {
334    /// Point id
335    pub id: PointIdType,
336    /// All named vectors of the point, storage-native bytes per vector name
337    #[serde(with = "raw_vectors_serde")]
338    pub vectors: RawVectorsPersisted,
339    /// Payload values (optional)
340    pub payload: Option<Payload>,
341}
342
343/// Serde helper for [`PointStructRawPersisted::vectors`].
344///
345/// By default serde serializes `Vec<u8>` as a sequence of integers, which in
346/// CBOR (used for the WAL) costs ~2x for high-entropy data such as raw vector
347/// bytes. This module forces each blob through `serialize_bytes` so it is
348/// encoded as a compact byte string (~1x overhead) instead.
349mod raw_vectors_serde {
350    use std::fmt;
351
352    use crate::segment::types::VectorNameBuf;
353    use serde::de::{self, Deserializer, SeqAccess, Visitor};
354    use serde::ser::{SerializeSeq, Serializer};
355
356    use super::RawVectorsPersisted;
357
358    /// Upper bound for the capacity we pre-allocate from an untrusted `size_hint`
359    /// when deserializing a single vector's raw bytes.
360    ///
361    /// Realistic sizes are far below this: a maximum-size dense vector is
362    /// 65536 dims x 4 bytes (f32) = 256 KiB. The 128 MiB headroom comfortably
363    /// covers large multivectors and sparse vectors while staying orders of
364    /// magnitude away from OOM territory.
365    const MAX_RAW_VECTOR_PREALLOC: usize = 128 * 1024 * 1024;
366
367    /// Reference wrapper that serializes a byte slice as a byte string.
368    struct BytesRef<'a>(&'a [u8]);
369
370    impl serde::Serialize for BytesRef<'_> {
371        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
372            serializer.serialize_bytes(self.0)
373        }
374    }
375
376    pub fn serialize<S: Serializer>(
377        vectors: &[(VectorNameBuf, Vec<u8>)],
378        serializer: S,
379    ) -> Result<S::Ok, S::Error> {
380        let mut seq = serializer.serialize_seq(Some(vectors.len()))?;
381        for (name, bytes) in vectors {
382            seq.serialize_element(&(name, BytesRef(bytes)))?;
383        }
384        seq.end()
385    }
386
387    /// Owned wrapper that deserializes a byte string into a `Vec<u8>`.
388    struct ByteVec(Vec<u8>);
389
390    impl<'de> serde::Deserialize<'de> for ByteVec {
391        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
392            struct ByteVecVisitor;
393
394            impl<'de> Visitor<'de> for ByteVecVisitor {
395                type Value = Vec<u8>;
396
397                fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
398                    formatter.write_str("a byte string")
399                }
400
401                fn visit_bytes<E: de::Error>(self, value: &[u8]) -> Result<Self::Value, E> {
402                    Ok(value.to_vec())
403                }
404
405                fn visit_byte_buf<E: de::Error>(self, value: Vec<u8>) -> Result<Self::Value, E> {
406                    Ok(value)
407                }
408
409                /// Formats that lack a native byte-string type (e.g. JSON) fall
410                /// back to a sequence of integers; accept those too.
411                fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
412                    let capacity = seq.size_hint().unwrap_or(0).min(MAX_RAW_VECTOR_PREALLOC);
413                    let mut bytes = Vec::with_capacity(capacity);
414                    while let Some(byte) = seq.next_element()? {
415                        bytes.push(byte);
416                    }
417                    Ok(bytes)
418                }
419            }
420
421            deserializer
422                .deserialize_byte_buf(ByteVecVisitor)
423                .map(ByteVec)
424        }
425    }
426
427    pub fn deserialize<'de, D: Deserializer<'de>>(
428        deserializer: D,
429    ) -> Result<RawVectorsPersisted, D::Error> {
430        let raw: Vec<(VectorNameBuf, ByteVec)> = serde::Deserialize::deserialize(deserializer)?;
431        Ok(raw
432            .into_iter()
433            .map(|(name, bytes)| (name, bytes.0))
434            .collect())
435    }
436}
437
438impl From<SegmentRecordRaw> for PointStructRawPersisted {
439    fn from(record: SegmentRecordRaw) -> Self {
440        let SegmentRecordRaw {
441            id,
442            vectors,
443            payload,
444        } = record;
445
446        Self {
447            id,
448            vectors: vectors.unwrap_or_default(),
449            payload,
450        }
451    }
452}
453
454impl PointStructRawPersisted {
455    pub fn is_equal_to(&self, segment_record: &SegmentRecordRaw) -> bool {
456        let SegmentRecordRaw {
457            id,
458            vectors,
459            payload,
460        } = segment_record;
461
462        if &self.id != id {
463            return false;
464        }
465
466        let segment_vectors = vectors.as_deref().unwrap_or(&[]);
467        if self.vectors.len() != segment_vectors.len() {
468            return false;
469        }
470        for (name, bytes) in segment_vectors {
471            let own_bytes = self
472                .vectors
473                .iter()
474                .find(|(own_name, _)| own_name == name)
475                .map(|(_, bytes)| bytes);
476            if own_bytes != Some(bytes) {
477                return false;
478            }
479        }
480
481        // Check if payloads are equal, empty and non-existent payloads are considered equal
482        let self_payload = self.payload.as_ref().filter(|p| !p.is_empty());
483        let segment_payload = payload.as_ref().filter(|p| !p.is_empty());
484        self_payload == segment_payload
485    }
486}
487
488#[cfg(feature = "api")]
489impl From<PointStructRawPersisted> for api::grpc::qdrant::PointStructRaw {
490    fn from(value: PointStructRawPersisted) -> Self {
491        let PointStructRawPersisted {
492            id,
493            vectors,
494            payload,
495        } = value;
496
497        Self {
498            id: Some(id.into()),
499            vectors: vectors.into_iter().collect(),
500            payload: payload
501                .map(api::conversions::json::payload_to_proto)
502                .unwrap_or_default(),
503            raw_payload: None,
504        }
505    }
506}
507
508#[cfg(feature = "api")]
509impl TryFrom<api::grpc::qdrant::PointStructRaw> for PointStructRawPersisted {
510    type Error = tonic::Status;
511
512    fn try_from(value: api::grpc::qdrant::PointStructRaw) -> Result<Self, Self::Error> {
513        let api::grpc::qdrant::PointStructRaw {
514            id,
515            vectors,
516            payload,
517            raw_payload,
518        } = value;
519
520        let id = id
521            .ok_or_else(|| tonic::Status::invalid_argument("Empty id is not allowed"))?
522            .try_into()?;
523
524        // Prefer the raw payload blob and fall back to the serialized payload otherwise.
525        // An empty payload is normalized to `None`.
526        let payload = match raw_payload {
527            Some(raw_payload) => decode_payload(raw_payload)?,
528            None => api::conversions::json::proto_to_payloads(payload)?,
529        };
530        let payload = (!payload.is_empty()).then_some(payload);
531
532        Ok(Self {
533            id,
534            vectors: vectors.into_iter().collect(),
535            payload,
536        })
537    }
538}
539
540/// Decodes the RawPayload according to its encoding.
541#[cfg(feature = "api")]
542fn decode_payload(raw_payload: RawPayload) -> Result<Payload, tonic::Status> {
543    match raw_payload.encoding() {
544        api::grpc::RawPayloadEncoding::JsonBytes => {
545            serde_json::from_slice(&raw_payload.payload_bytes).map_err(|err| {
546                tonic::Status::invalid_argument(format!("Malformed raw payload blob: {err}"))
547            })
548        }
549    }
550}
551
552impl Debug for PointStructRawPersisted {
553    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
554        let vectors = self
555            .vectors
556            .iter()
557            .map(|(name, bytes)| format!("{name}: {} bytes", bytes.len()))
558            .join(", ");
559        write!(
560            f,
561            "PointStructRawPersisted {{ id: {}, vectors: [{vectors}], payload: {:?} }}",
562            self.id, self.payload,
563        )
564    }
565}
566
567#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Hash)]
568#[serde(rename_all = "snake_case")]
569pub struct BatchPersisted {
570    pub ids: Vec<PointIdType>,
571    pub vectors: BatchVectorStructPersisted,
572    pub payloads: Option<Vec<Option<Payload>>>,
573}
574
575#[cfg(feature = "api")]
576impl TryFrom<BatchPersisted> for Vec<api::grpc::qdrant::PointStruct> {
577    type Error = tonic::Status;
578
579    fn try_from(batch: BatchPersisted) -> Result<Self, Self::Error> {
580        let BatchPersisted {
581            ids,
582            vectors,
583            payloads,
584        } = batch;
585        let mut points = Vec::with_capacity(ids.len());
586        let batch_vectors = BatchVectorStructInternal::from(vectors);
587        let all_vectors = batch_vectors.into_all_vectors(ids.len());
588        for (i, p_id) in ids.into_iter().enumerate() {
589            let id = Some(p_id.into());
590            let vector = all_vectors.get(i).cloned();
591            let payload = payloads.as_ref().and_then(|payloads| {
592                payloads.get(i).map(|payload| match payload {
593                    None => HashMap::new(),
594                    Some(payload) => api::conversions::json::payload_to_proto(payload.clone()),
595                })
596            });
597            let vectors: Option<VectorStructInternal> = vector.map(NamedVectors::into);
598
599            let point = api::grpc::qdrant::PointStruct {
600                id,
601                vectors: vectors.map(api::grpc::qdrant::Vectors::from),
602                payload: payload.unwrap_or_default(),
603            };
604            points.push(point);
605        }
606
607        Ok(points)
608    }
609}
610
611#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
612#[serde(untagged, rename_all = "snake_case")]
613pub enum BatchVectorStructPersisted {
614    Single(Vec<DenseVector>),
615    MultiDense(Vec<MultiDenseVector>),
616    Named(HashMap<VectorNameBuf, Vec<VectorPersisted>>),
617}
618
619impl BatchVectorStructPersisted {
620    /// Drop named-vector references to names not in `valid`. See
621    /// [`CollectionUpdateOperations::retain_vector_names`].
622    pub fn retain_vector_names(&mut self, valid: &HashSet<VectorNameBuf>) {
623        if let BatchVectorStructPersisted::Named(named) = self {
624            named.retain(|name, _| valid.contains(name));
625        }
626    }
627}
628
629impl Hash for BatchVectorStructPersisted {
630    fn hash<H: Hasher>(&self, state: &mut H) {
631        mem::discriminant(self).hash(state);
632        match self {
633            BatchVectorStructPersisted::Single(dense) => {
634                for vector in dense {
635                    for v in vector {
636                        OrderedFloat(*v).hash(state);
637                    }
638                }
639            }
640            BatchVectorStructPersisted::MultiDense(multidense) => {
641                for vector in multidense {
642                    for v in vector {
643                        for element in v {
644                            OrderedFloat(*element).hash(state);
645                        }
646                    }
647                }
648            }
649            BatchVectorStructPersisted::Named(named) => unordered_hash_unique(state, named.iter()),
650        }
651    }
652}
653
654impl From<BatchVectorStructPersisted> for BatchVectorStructInternal {
655    fn from(value: BatchVectorStructPersisted) -> Self {
656        match value {
657            BatchVectorStructPersisted::Single(vector) => BatchVectorStructInternal::Single(vector),
658            BatchVectorStructPersisted::MultiDense(vectors) => {
659                BatchVectorStructInternal::MultiDense(
660                    vectors
661                        .into_iter()
662                        .map(MultiDenseVectorInternal::new_unchecked)
663                        .collect(),
664                )
665            }
666            BatchVectorStructPersisted::Named(vectors) => BatchVectorStructInternal::Named(
667                vectors
668                    .into_iter()
669                    .map(|(k, v)| (k, v.into_iter().map(VectorInternal::from).collect()))
670                    .collect(),
671            ),
672        }
673    }
674}
675
676#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Validate, Hash)]
677#[serde(rename_all = "snake_case")]
678pub struct PointStructPersisted {
679    /// Point id
680    pub id: PointIdType,
681    /// Vectors
682    pub vector: VectorStructPersisted,
683    /// Payload values (optional)
684    pub payload: Option<Payload>,
685}
686
687impl PointStructPersisted {
688    pub fn get_vectors(&self) -> NamedVectors<'_> {
689        let mut named_vectors = NamedVectors::default();
690        match &self.vector {
691            VectorStructPersisted::Single(vector) => named_vectors.insert(
692                DEFAULT_VECTOR_NAME.to_owned(),
693                VectorInternal::from(vector.clone()),
694            ),
695            VectorStructPersisted::MultiDense(vector) => named_vectors.insert(
696                DEFAULT_VECTOR_NAME.to_owned(),
697                VectorInternal::from(MultiDenseVectorInternal::new_unchecked(vector.clone())),
698            ),
699            VectorStructPersisted::Named(vectors) => {
700                for (name, vector) in vectors {
701                    named_vectors.insert(name.clone(), VectorInternal::from(vector.clone()));
702                }
703            }
704        }
705        named_vectors
706    }
707
708    pub fn is_equal_to(&self, segment_record: &SegmentRecord) -> bool {
709        let SegmentRecord {
710            id,
711            vectors,
712            payload,
713        } = segment_record;
714
715        if &self.id != id {
716            return false;
717        }
718
719        let self_vectors = self.get_vectors();
720
721        if let Some(segment_vectors) = vectors {
722            if self_vectors.len() != segment_vectors.len() {
723                return false;
724            }
725            for (name, vec) in segment_vectors {
726                if self_vectors.get(name) != Some(VectorRef::from(vec)) {
727                    return false;
728                }
729            }
730        } else if !self_vectors.is_empty() {
731            return false;
732        }
733
734        // Check if payloads are equal, empty and non-existent payloads are considered equal
735        let self_payload = self.payload.as_ref().filter(|p| !p.is_empty());
736        let segment_payload = payload.as_ref().filter(|p| !p.is_empty());
737        self_payload == segment_payload
738    }
739}
740
741#[cfg(feature = "api")]
742impl TryFrom<api::rest::schema::Record> for PointStructPersisted {
743    type Error = String;
744
745    fn try_from(record: api::rest::schema::Record) -> Result<Self, Self::Error> {
746        let api::rest::schema::Record {
747            id,
748            payload,
749            vector,
750            shard_key: _,
751            order_value: _,
752        } = record;
753
754        if vector.is_none() {
755            return Err("Vector is empty".to_string());
756        }
757
758        Ok(Self {
759            id,
760            payload,
761            vector: VectorStructPersisted::from(vector.unwrap()),
762        })
763    }
764}
765
766#[cfg(feature = "api")]
767impl TryFrom<PointStructPersisted> for api::grpc::qdrant::PointStruct {
768    type Error = tonic::Status;
769
770    fn try_from(value: PointStructPersisted) -> Result<Self, Self::Error> {
771        let PointStructPersisted {
772            id,
773            vector,
774            payload,
775        } = value;
776
777        let vectors_internal = VectorStructInternal::try_from(vector).map_err(|e| {
778            tonic::Status::invalid_argument(format!("Failed to convert vectors: {e}"))
779        })?;
780
781        let vectors = api::grpc::qdrant::Vectors::from(vectors_internal);
782        let converted_payload = match payload {
783            None => HashMap::new(),
784            Some(payload) => api::conversions::json::payload_to_proto(payload),
785        };
786
787        Ok(Self {
788            id: Some(id.into()),
789            vectors: Some(vectors),
790            payload: converted_payload,
791        })
792    }
793}
794
795/// Data structure for point vectors, as it is persisted in WAL
796#[derive(Clone, PartialEq, Deserialize, Serialize)]
797#[serde(untagged, rename_all = "snake_case")]
798pub enum VectorStructPersisted {
799    Single(DenseVector),
800    MultiDense(MultiDenseVector),
801    Named(HashMap<VectorNameBuf, VectorPersisted>),
802}
803
804impl VectorStructPersisted {
805    /// Drop named-vector references to names not in `valid`. See
806    /// [`CollectionUpdateOperations::retain_vector_names`].
807    pub fn retain_vector_names(&mut self, valid: &HashSet<VectorNameBuf>) {
808        if let VectorStructPersisted::Named(named) = self {
809            named.retain(|name, _| valid.contains(name));
810        }
811    }
812}
813
814impl std::hash::Hash for VectorStructPersisted {
815    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
816        mem::discriminant(self).hash(state);
817        match self {
818            VectorStructPersisted::Single(vec) => {
819                for v in vec {
820                    OrderedFloat(*v).hash(state);
821                }
822            }
823            VectorStructPersisted::MultiDense(multi_vec) => {
824                for vec in multi_vec {
825                    for v in vec {
826                        OrderedFloat(*v).hash(state);
827                    }
828                }
829            }
830            VectorStructPersisted::Named(map) => {
831                unordered_hash_unique(state, map.iter());
832            }
833        }
834    }
835}
836
837impl Debug for VectorStructPersisted {
838    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
839        match self {
840            VectorStructPersisted::Single(vector) => {
841                let first_elements = vector.iter().take(4).join(", ");
842                write!(f, "Single([{}, ... x {}])", first_elements, vector.len())
843            }
844            VectorStructPersisted::MultiDense(vector) => {
845                let first_vectors = vector
846                    .iter()
847                    .take(4)
848                    .map(|v| {
849                        let first_elements = v.iter().take(4).join(", ");
850                        format!("[{}, ... x {}]", first_elements, v.len())
851                    })
852                    .join(", ");
853                write!(f, "MultiDense([{}, ... x {})", first_vectors, vector.len())
854            }
855            VectorStructPersisted::Named(vectors) => write!(f, "Named(( ")
856                .and_then(|_| {
857                    for (name, vector) in vectors {
858                        write!(f, "{name}: {vector:?}, ")?;
859                    }
860                    Ok(())
861                })
862                .and_then(|_| write!(f, "))")),
863        }
864    }
865}
866
867impl VectorStructPersisted {
868    /// Check if this vector struct is empty.
869    pub fn is_empty(&self) -> bool {
870        match self {
871            VectorStructPersisted::Single(vector) => vector.is_empty(),
872            VectorStructPersisted::MultiDense(vector) => vector.is_empty(),
873            VectorStructPersisted::Named(vectors) => vectors.values().all(|v| match v {
874                VectorPersisted::Dense(vector) => vector.is_empty(),
875                VectorPersisted::Sparse(vector) => vector.indices.is_empty(),
876                VectorPersisted::MultiDense(vector) => vector.is_empty(),
877            }),
878        }
879    }
880}
881
882impl Validate for VectorStructPersisted {
883    fn validate(&self) -> Result<(), ValidationErrors> {
884        match self {
885            VectorStructPersisted::Single(_) => Ok(()),
886            VectorStructPersisted::MultiDense(v) => validate_multi_vector(v),
887            VectorStructPersisted::Named(v) => crate::common::validation::validate_iter(v.values()),
888        }
889    }
890}
891
892impl From<DenseVector> for VectorStructPersisted {
893    fn from(value: DenseVector) -> Self {
894        VectorStructPersisted::Single(value)
895    }
896}
897
898impl From<VectorStructInternal> for VectorStructPersisted {
899    fn from(value: VectorStructInternal) -> Self {
900        match value {
901            VectorStructInternal::Single(vector) => VectorStructPersisted::Single(vector),
902            VectorStructInternal::MultiDense(vector) => {
903                VectorStructPersisted::MultiDense(vector.into_multi_vectors())
904            }
905            VectorStructInternal::Named(vectors) => VectorStructPersisted::Named(
906                vectors
907                    .into_iter()
908                    .map(|(k, v)| (k, VectorPersisted::from(v)))
909                    .collect(),
910            ),
911        }
912    }
913}
914
915#[cfg(feature = "api")]
916impl From<api::rest::VectorStructOutput> for VectorStructPersisted {
917    fn from(value: api::rest::VectorStructOutput) -> Self {
918        match value {
919            api::rest::VectorStructOutput::Single(vector) => VectorStructPersisted::Single(vector),
920            api::rest::VectorStructOutput::MultiDense(vector) => {
921                VectorStructPersisted::MultiDense(vector)
922            }
923            api::rest::VectorStructOutput::Named(vectors) => VectorStructPersisted::Named(
924                vectors
925                    .into_iter()
926                    .map(|(k, v)| (k, VectorPersisted::from(v)))
927                    .collect(),
928            ),
929        }
930    }
931}
932
933impl TryFrom<VectorStructPersisted> for VectorStructInternal {
934    type Error = OperationError;
935    fn try_from(value: VectorStructPersisted) -> Result<Self, Self::Error> {
936        let vector_struct = match value {
937            VectorStructPersisted::Single(vector) => VectorStructInternal::Single(vector),
938            VectorStructPersisted::MultiDense(vector) => {
939                VectorStructInternal::MultiDense(MultiDenseVectorInternal::try_from(vector)?)
940            }
941            VectorStructPersisted::Named(vectors) => VectorStructInternal::Named(
942                vectors
943                    .into_iter()
944                    .map(|(k, v)| (k, VectorInternal::from(v)))
945                    .collect(),
946            ),
947        };
948        Ok(vector_struct)
949    }
950}
951
952impl From<VectorStructPersisted> for NamedVectors<'_> {
953    fn from(value: VectorStructPersisted) -> Self {
954        match value {
955            VectorStructPersisted::Single(vector) => {
956                NamedVectors::from_pairs([(DEFAULT_VECTOR_NAME.to_owned(), vector)])
957            }
958            VectorStructPersisted::MultiDense(vector) => {
959                let mut named_vector = NamedVectors::default();
960                let multivec = MultiDenseVectorInternal::new_unchecked(vector);
961
962                named_vector.insert(
963                    DEFAULT_VECTOR_NAME.to_owned(),
964                    crate::segment::data_types::vectors::VectorInternal::from(multivec),
965                );
966                named_vector
967            }
968            VectorStructPersisted::Named(vectors) => {
969                let mut named_vector = NamedVectors::default();
970                for (name, vector) in vectors {
971                    named_vector.insert(
972                        name,
973                        crate::segment::data_types::vectors::VectorInternal::from(vector),
974                    );
975                }
976                named_vector
977            }
978        }
979    }
980}
981
982/// Single vector data, as it is persisted in WAL
983/// Unlike [`api::rest::Vector`], this struct only stores raw vectors, inferenced or resolved.
984/// Unlike [`VectorInternal`], is not optimized for search
985#[derive(Clone, PartialEq, Deserialize, Serialize)]
986#[serde(untagged, rename_all = "snake_case")]
987pub enum VectorPersisted {
988    Dense(DenseVector),
989    Sparse(crate::sparse::common::sparse_vector::SparseVector),
990    MultiDense(MultiDenseVector),
991}
992
993impl Hash for VectorPersisted {
994    fn hash<H: Hasher>(&self, state: &mut H) {
995        mem::discriminant(self).hash(state);
996        match self {
997            VectorPersisted::Dense(vec) => {
998                for v in vec {
999                    OrderedFloat(*v).hash(state);
1000                }
1001            }
1002            VectorPersisted::Sparse(sparse) => {
1003                sparse.hash(state);
1004            }
1005            VectorPersisted::MultiDense(multi_vec) => {
1006                for vec in multi_vec {
1007                    for v in vec {
1008                        OrderedFloat(*v).hash(state);
1009                    }
1010                }
1011            }
1012        }
1013    }
1014}
1015
1016impl VectorPersisted {
1017    pub fn new_sparse(indices: Vec<DimId>, values: Vec<DimWeight>) -> Self {
1018        Self::Sparse(crate::sparse::common::sparse_vector::SparseVector { indices, values })
1019    }
1020
1021    pub fn empty_sparse() -> Self {
1022        Self::new_sparse(vec![], vec![])
1023    }
1024}
1025
1026impl Debug for VectorPersisted {
1027    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1028        match self {
1029            VectorPersisted::Dense(vector) => {
1030                let first_elements = vector.iter().take(4).join(", ");
1031                write!(f, "Dense([{}, ... x {}])", first_elements, vector.len())
1032            }
1033            VectorPersisted::Sparse(vector) => {
1034                let first_elements = vector
1035                    .indices
1036                    .iter()
1037                    .zip(vector.values.iter())
1038                    .take(4)
1039                    .map(|(k, v)| format!("{k}->{v}"))
1040                    .join(", ");
1041                write!(
1042                    f,
1043                    "Sparse([{}, ... x {})",
1044                    first_elements,
1045                    vector.indices.len()
1046                )
1047            }
1048            VectorPersisted::MultiDense(vector) => {
1049                let first_vectors = vector
1050                    .iter()
1051                    .take(4)
1052                    .map(|v| {
1053                        let first_elements = v.iter().take(4).join(", ");
1054                        format!("[{}, ... x {}]", first_elements, v.len())
1055                    })
1056                    .join(", ");
1057                write!(f, "MultiDense([{}, ... x {})", first_vectors, vector.len())
1058            }
1059        }
1060    }
1061}
1062
1063impl Validate for VectorPersisted {
1064    fn validate(&self) -> Result<(), ValidationErrors> {
1065        match self {
1066            VectorPersisted::Dense(_) => Ok(()),
1067            VectorPersisted::Sparse(v) => v.validate(),
1068            VectorPersisted::MultiDense(m) => validate_multi_vector(m),
1069        }
1070    }
1071}
1072
1073impl From<VectorInternal> for VectorPersisted {
1074    fn from(value: VectorInternal) -> Self {
1075        match value {
1076            VectorInternal::Dense(vector) => VectorPersisted::Dense(vector),
1077            VectorInternal::Sparse(vector) => VectorPersisted::Sparse(vector),
1078            VectorInternal::MultiDense(vector) => {
1079                VectorPersisted::MultiDense(vector.into_multi_vectors())
1080            }
1081        }
1082    }
1083}
1084
1085#[cfg(feature = "api")]
1086impl From<api::rest::VectorOutput> for VectorPersisted {
1087    fn from(value: api::rest::VectorOutput) -> Self {
1088        match value {
1089            api::rest::VectorOutput::Dense(vector) => VectorPersisted::Dense(vector),
1090            api::rest::VectorOutput::Sparse(vector) => VectorPersisted::Sparse(vector),
1091            api::rest::VectorOutput::MultiDense(vector) => VectorPersisted::MultiDense(vector),
1092        }
1093    }
1094}
1095
1096impl From<VectorPersisted> for VectorInternal {
1097    fn from(value: VectorPersisted) -> Self {
1098        match value {
1099            VectorPersisted::Dense(vector) => VectorInternal::Dense(vector),
1100            VectorPersisted::Sparse(vector) => VectorInternal::Sparse(vector),
1101            VectorPersisted::MultiDense(vector) => {
1102                // the REST vectors have been validated already
1103                // we can use an internal constructor
1104                VectorInternal::MultiDense(MultiDenseVectorInternal::new_unchecked(vector))
1105            }
1106        }
1107    }
1108}
1109
1110fn retain_with_index<T, F>(vec: &mut Vec<T>, mut filter: F)
1111where
1112    F: FnMut(usize, &T) -> bool,
1113{
1114    let mut index = 0;
1115
1116    vec.retain(|item| {
1117        let retain = filter(index, item);
1118        index += 1;
1119        retain
1120    });
1121}
1122
1123#[cfg(test)]
1124mod tests {
1125    use super::*;
1126
1127    #[test]
1128    fn raw_persisted_vectors_use_compact_byte_string() {
1129        // High-entropy payload: byte value == (index % 256), most bytes >= 24.
1130        let blob: Vec<u8> = (0..4096u32).map(|i| i as u8).collect();
1131        let point = PointStructRawPersisted {
1132            id: 1.into(),
1133            vectors: vec![("dense".to_string(), blob.clone())].into(),
1134            payload: None,
1135        };
1136
1137        let encoded = serde_cbor::to_vec(&point).unwrap();
1138        // A byte string is ~1x; an integer array would be ~1.9x for this data.
1139        // Guard well below the naive-array size (>7800 bytes for 4096 bytes).
1140        assert!(
1141            encoded.len() < blob.len() + 128,
1142            "expected compact byte-string encoding, got {} bytes for a {}-byte blob",
1143            encoded.len(),
1144            blob.len(),
1145        );
1146
1147        // Round-trips losslessly.
1148        let decoded: PointStructRawPersisted = serde_cbor::from_slice(&encoded).unwrap();
1149        assert!(decoded == point, "round-trip mismatch");
1150    }
1151
1152    fn dense(v: f32) -> VectorPersisted {
1153        VectorPersisted::Dense(vec![v])
1154    }
1155
1156    #[test]
1157    fn retain_vector_names_strips_list_batch_and_delete() {
1158        let valid: HashSet<VectorNameBuf> = ["a".to_string()].into_iter().collect();
1159
1160        // PointsList: the deleted name `b` is stripped, `a` survives.
1161        let mut list =
1162            PointOperations::UpsertPoints(PointInsertOperationsInternal::PointsList(vec![
1163                PointStructPersisted {
1164                    id: 1.into(),
1165                    vector: VectorStructPersisted::Named(
1166                        [("a".to_string(), dense(0.1)), ("b".to_string(), dense(0.2))]
1167                            .into_iter()
1168                            .collect(),
1169                    ),
1170                    payload: None,
1171                },
1172            ]));
1173        list.retain_vector_names(&valid);
1174        let PointOperations::UpsertPoints(PointInsertOperationsInternal::PointsList(points)) =
1175            &list
1176        else {
1177            unreachable!()
1178        };
1179        let VectorStructPersisted::Named(named) = &points[0].vector else {
1180            unreachable!()
1181        };
1182        assert_eq!(named.keys().cloned().collect::<Vec<_>>(), vec!["a"]);
1183
1184        // PointsBatch: per-name entries are dropped, ids/payloads length untouched.
1185        let mut batch = PointOperations::UpsertPoints(PointInsertOperationsInternal::PointsBatch(
1186            BatchPersisted {
1187                ids: vec![1.into(), 2.into()],
1188                vectors: BatchVectorStructPersisted::Named(
1189                    [
1190                        ("a".to_string(), vec![dense(0.1), dense(0.2)]),
1191                        ("b".to_string(), vec![dense(0.3), dense(0.4)]),
1192                    ]
1193                    .into_iter()
1194                    .collect(),
1195                ),
1196                payloads: None,
1197            },
1198        ));
1199        batch.retain_vector_names(&valid);
1200        let PointOperations::UpsertPoints(PointInsertOperationsInternal::PointsBatch(batch)) =
1201            &batch
1202        else {
1203            unreachable!()
1204        };
1205        let BatchVectorStructPersisted::Named(named) = &batch.vectors else {
1206            unreachable!()
1207        };
1208        assert_eq!(named.keys().cloned().collect::<Vec<_>>(), vec!["a"]);
1209        assert_eq!(batch.ids.len(), 2);
1210    }
1211}