Skip to main content

qdrant_edge/segment/
types.rs

1// Deprecated storage placement params (`on_disk`, `always_ram`, `on_disk_payload`) are still
2// handled here for backward compatibility with the new `memory` parameter
3#![allow(deprecated)]
4
5use std::borrow::Cow;
6use std::cmp::Ordering;
7use std::collections::{BTreeMap, HashMap, HashSet};
8use std::fmt::{self, Display, Formatter};
9use std::hash::{self, Hash, Hasher};
10use std::mem;
11use std::num::NonZeroU32;
12use std::ops::Deref;
13use std::rc::Rc;
14use std::str::FromStr;
15use std::sync::Arc;
16
17use ahash::AHashSet;
18use bytemuck::{Pod, Zeroable};
19use crate::common::stable_hash::StableHash;
20use crate::common::types::{PointOffsetType, ScoreType};
21use ecow::EcoString;
22use fnv::FnvBuildHasher;
23use geo::{Contains, Coord, Distance as GeoDistance, Haversine, LineString, Point, Polygon};
24use indexmap::IndexSet;
25use itertools::Itertools;
26use num_derive::FromPrimitive;
27use ordered_float::OrderedFloat;
28use schemars::JsonSchema;
29use serde::{Deserialize, Deserializer, Serialize};
30use serde_json::{Map, Value};
31use strum::{EnumIter, EnumString};
32use uuid::Uuid;
33use validator::{Validate, ValidationError, ValidationErrors};
34use zerocopy::native_endian::U64;
35
36use crate::segment::common::anonymize::Anonymize;
37use crate::segment::common::operation_error::{OperationError, OperationResult};
38use crate::segment::common::utils::{self, MaybeOneOrMany, MultiValue};
39use crate::segment::data_types::index::{
40    BoolIndexParams, DatetimeIndexParams, FloatIndexParams, GeoIndexParams, IntegerIndexParams,
41    KeywordIndexParams, TextIndexParams, UuidIndexParams,
42};
43use crate::segment::data_types::modifier::Modifier;
44use crate::segment::data_types::order_by::OrderValue;
45use crate::segment::data_types::primitive::PrimitiveVectorElement;
46use crate::segment::data_types::tiny_map::TinyMap;
47use crate::segment::data_types::vectors::{DenseVector, VectorStructInternal};
48use crate::segment::index::field_index::CardinalityEstimation;
49use crate::segment::index::sparse_index::sparse_index_config::SparseIndexConfig;
50use crate::segment::json_path::JsonPath;
51use crate::segment::spaces::metric::{Metric, MetricPostProcessing};
52use crate::segment::spaces::simple::{CosineMetric, DotProductMetric, EuclidMetric, ManhattanMetric};
53use crate::segment::types::utils::unordered_hash_unique;
54use crate::segment::utils::maybe_arc::MaybeArc;
55
56pub type PayloadKeyType = JsonPath;
57pub type PayloadKeyTypeRef<'a> = &'a JsonPath;
58/// Sequential number of modification, applied to segment
59pub type SeqNumberType = u64;
60/// Type of float point payload
61pub type FloatPayloadType = f64;
62/// Type of integer point payload
63pub type IntPayloadType = i64;
64/// Type of datetime point payload
65pub type DateTimePayloadType = DateTimeWrapper;
66/// Type of Uuid point payload
67pub type UuidPayloadType = Uuid;
68/// Type of Uuid point payload key
69pub type UuidIntType = u128;
70/// Name of a vector
71pub type VectorName = str;
72/// Name of a vector (owned variant)
73pub type VectorNameBuf = String;
74
75/// Wraps `DateTime<Utc>` to allow more flexible deserialization
76#[derive(Clone, Copy, Serialize, JsonSchema, Debug, PartialEq, Eq, PartialOrd, Hash)]
77#[serde(transparent)]
78pub struct DateTimeWrapper(pub chrono::DateTime<chrono::Utc>);
79
80impl DateTimeWrapper {
81    /// Qdrant's representation of datetime as timestamp is an i64 of microseconds
82    pub fn timestamp(&self) -> i64 {
83        self.0.timestamp_micros()
84    }
85
86    pub fn from_timestamp(ts: i64) -> Option<Self> {
87        Some(Self(chrono::DateTime::from_timestamp_micros(ts)?))
88    }
89}
90
91impl<'de> Deserialize<'de> for DateTimePayloadType {
92    /// Parses RFC3339 datetime strings used in REST/JSON `datetime_range` filters.
93    /// Returns a clear user-facing error when the format is invalid.
94    /// Example accepted value: `2014-01-01T00:00:00Z`.
95    ///
96    /// Binary formats (CBOR/MessagePack/WAL) also serialize as RFC3339 strings, so we reuse
97    /// the same parsing path everywhere.
98    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
99    where
100        D: Deserializer<'de>,
101    {
102        let str_datetime: Cow<'de, str> = Cow::deserialize(deserializer)?;
103
104        match DateTimePayloadType::from_str(str_datetime.as_ref()) {
105            Ok(datetime) => Ok(datetime),
106            Err(_) => Err(serde::de::Error::custom(format!(
107                "'{str_datetime}' does not match accepted datetime format (RFC3339). Example: 2014-01-01T00:00:00Z"
108            ))),
109        }
110    }
111}
112
113impl FromStr for DateTimePayloadType {
114    type Err = chrono::ParseError;
115
116    fn from_str(s: &str) -> Result<Self, Self::Err> {
117        // Attempt to parse the input string in RFC 3339 format
118        if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(s)
119            // Attempt to parse default to-string format
120            .or_else(|_| chrono::DateTime::from_str(s))
121            // Attempt to parse the input string in the specified formats:
122            // - YYYY-MM-DD'T'HH:MM:SS-HHMM (timezone without colon)
123            // - YYYY-MM-DD HH:MM:SS-HHMM (timezone without colon)
124            .or_else(|_| chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f%#z"))
125            .or_else(|_| chrono::DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f%#z"))
126            .map(|dt| chrono::DateTime::<chrono::Utc>::from(dt).into())
127        {
128            return Ok(datetime);
129        }
130
131        // Attempt to parse the input string in the specified formats:
132        // - YYYY-MM-DD'T'HH:MM:SS (without timezone or Z)
133        // - YYYY-MM-DD HH:MM:SS (without timezone or Z)
134        // - YYYY-MM-DD'T'HH:MM (without timezone and seconds)
135        // - YYYY-MM-DD HH:MM (without timezone and seconds)
136        // - YYYY-MM-DD
137        // See: <https://github.com/qdrant/qdrant/issues/3529>
138        // See: <https://github.com/qdrant/qdrant/issues/8718>
139        let datetime = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f")
140            .or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f"))
141            .or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M"))
142            .or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M"))
143            .or_else(|_| chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").map(Into::into))?;
144
145        // Convert the parsed NaiveDateTime to a DateTime<Utc>
146        let datetime_utc = datetime.and_utc().into();
147        Ok(datetime_utc)
148    }
149}
150
151impl Display for DateTimePayloadType {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        write!(f, "{}", self.0)
154    }
155}
156
157impl From<chrono::DateTime<chrono::Utc>> for DateTimePayloadType {
158    fn from(dt: chrono::DateTime<chrono::Utc>) -> Self {
159        DateTimeWrapper(dt)
160    }
161}
162
163fn id_num_example() -> u64 {
164    42
165}
166
167fn id_uuid_example() -> String {
168    "550e8400-e29b-41d4-a716-446655440000".to_string()
169}
170
171/// Type, used for specifying point ID in user interface
172#[derive(Debug, Serialize, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, JsonSchema)]
173#[serde(untagged)]
174pub enum ExtendedPointId {
175    #[schemars(example = "id_num_example")]
176    NumId(u64),
177    #[schemars(example = "id_uuid_example")]
178    Uuid(Uuid),
179}
180
181impl StableHash for ExtendedPointId {
182    fn stable_hash<W: FnMut(&[u8])>(&self, write: &mut W) {
183        match self {
184            ExtendedPointId::NumId(num) => {
185                0u64.stable_hash(write); // discriminant for NumId
186                num.stable_hash(write);
187            }
188            ExtendedPointId::Uuid(uuid) => {
189                1u64.stable_hash(write); // discriminant for Uuid
190
191                uuid.as_bytes().len().stable_hash(write); // compatibility with uuid <= v1.16.0
192                write(uuid.as_bytes());
193            }
194        }
195    }
196}
197
198impl ExtendedPointId {
199    #[cfg(any(test, feature = "testing"))]
200    pub fn as_u64(&self) -> u64 {
201        match self {
202            ExtendedPointId::NumId(num) => *num,
203            ExtendedPointId::Uuid(_) => panic!("Cannot convert UUID to u64"),
204        }
205    }
206
207    pub fn is_uuid(&self) -> bool {
208        matches!(self, ExtendedPointId::Uuid(..))
209    }
210}
211
212impl std::fmt::Display for ExtendedPointId {
213    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
214        match self {
215            ExtendedPointId::NumId(idx) => write!(f, "{idx}"),
216            ExtendedPointId::Uuid(uuid) => write!(f, "{uuid}"),
217        }
218    }
219}
220
221impl From<u64> for ExtendedPointId {
222    fn from(idx: u64) -> Self {
223        ExtendedPointId::NumId(idx)
224    }
225}
226
227impl FromStr for ExtendedPointId {
228    type Err = ();
229
230    fn from_str(s: &str) -> Result<Self, Self::Err> {
231        let try_num: Result<u64, _> = s.parse();
232        if let Ok(num) = try_num {
233            return Ok(Self::NumId(num));
234        }
235        let try_uuid = Uuid::from_str(s);
236        if let Ok(uuid) = try_uuid {
237            return Ok(Self::Uuid(uuid));
238        }
239        Err(())
240    }
241}
242
243impl<'de> serde::Deserialize<'de> for ExtendedPointId {
244    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
245    where
246        D: serde::Deserializer<'de>,
247    {
248        let value = serde_value::Value::deserialize(deserializer)?;
249
250        if let Ok(num) = value.clone().deserialize_into() {
251            return Ok(ExtendedPointId::NumId(num));
252        }
253
254        if let Ok(uuid) = value.clone().deserialize_into() {
255            return Ok(ExtendedPointId::Uuid(uuid));
256        }
257
258        let value = crate::segment::utils::fmt::SerdeValue(&value);
259
260        Err(serde::de::Error::custom(format!(
261            "value {value} is not a valid point ID, \
262                 valid values are either an unsigned integer or a UUID",
263        )))
264    }
265}
266
267/// Type of point index across all segments
268pub type PointIdType = ExtendedPointId;
269
270/// Compact representation of [`ExtendedPointId`].
271/// Unlike [`ExtendedPointId`], this type is 17 bytes long vs 24 bytes.
272#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
273pub enum CompactExtendedPointId {
274    NumId(U64),
275    Uuid(Uuid),
276}
277
278impl From<ExtendedPointId> for CompactExtendedPointId {
279    fn from(id: ExtendedPointId) -> Self {
280        match id {
281            ExtendedPointId::NumId(num) => CompactExtendedPointId::NumId(U64::new(num)),
282            ExtendedPointId::Uuid(uuid) => CompactExtendedPointId::Uuid(uuid),
283        }
284    }
285}
286
287impl From<CompactExtendedPointId> for ExtendedPointId {
288    fn from(id: CompactExtendedPointId) -> Self {
289        match id {
290            CompactExtendedPointId::NumId(num) => ExtendedPointId::NumId(num.get()),
291            CompactExtendedPointId::Uuid(uuid) => ExtendedPointId::Uuid(uuid),
292        }
293    }
294}
295
296/// Type of internal tags, build from payload
297#[derive(
298    Debug,
299    Deserialize,
300    Serialize,
301    JsonSchema,
302    
303    Clone,
304    Copy,
305    FromPrimitive,
306    PartialEq,
307    Eq,
308    Hash,
309    EnumString,
310    EnumIter,
311)]
312/// Distance function types used to compare vectors
313pub enum Distance {
314    // <https://en.wikipedia.org/wiki/Cosine_similarity>
315    Cosine,
316    // <https://en.wikipedia.org/wiki/Euclidean_distance>
317    Euclid,
318    // <https://en.wikipedia.org/wiki/Dot_product>
319    Dot,
320    // <https://simple.wikipedia.org/wiki/Manhattan_distance>
321    Manhattan,
322}
323
324impl Distance {
325    pub fn postprocess_score(&self, score: ScoreType) -> ScoreType {
326        match self {
327            Distance::Cosine => CosineMetric::postprocess(score),
328            Distance::Euclid => EuclidMetric::postprocess(score),
329            Distance::Dot => DotProductMetric::postprocess(score),
330            Distance::Manhattan => ManhattanMetric::postprocess(score),
331        }
332    }
333
334    pub fn preprocess_vector<T: PrimitiveVectorElement>(&self, vector: DenseVector) -> DenseVector
335    where
336        CosineMetric: Metric<T>,
337        EuclidMetric: Metric<T>,
338        DotProductMetric: Metric<T>,
339        ManhattanMetric: Metric<T>,
340    {
341        match self {
342            Distance::Cosine => CosineMetric::preprocess(vector),
343            Distance::Euclid => EuclidMetric::preprocess(vector),
344            Distance::Dot => DotProductMetric::preprocess(vector),
345            Distance::Manhattan => ManhattanMetric::preprocess(vector),
346        }
347    }
348
349    pub fn distance_order(&self) -> Order {
350        match self {
351            Distance::Cosine | Distance::Dot => Order::LargeBetter,
352            Distance::Euclid | Distance::Manhattan => Order::SmallBetter,
353        }
354    }
355
356    pub fn is_ordered(&self, left: ScoreType, right: ScoreType) -> bool {
357        match self.distance_order() {
358            Order::LargeBetter => left >= right,
359            Order::SmallBetter => left <= right,
360        }
361    }
362
363    /// Checks if score satisfies threshold condition
364    pub fn check_threshold(&self, score: ScoreType, threshold: ScoreType) -> bool {
365        match self.distance_order() {
366            Order::LargeBetter => score > threshold,
367            Order::SmallBetter => score < threshold,
368        }
369    }
370}
371
372/// Map a segment [`Distance`] to the TurboQuant [`DistanceType`].
373///
374/// Uses the true Cosine mapping (`Cosine → Cosine`); the legacy quantizers fold
375/// Cosine into Dot for backwards-compat, but do so with an explicit match rather
376/// than this conversion.
377impl From<Distance> for crate::quantization::DistanceType {
378    fn from(distance: Distance) -> Self {
379        match distance {
380            Distance::Cosine => crate::quantization::DistanceType::Cosine,
381            Distance::Euclid => crate::quantization::DistanceType::L2,
382            Distance::Dot => crate::quantization::DistanceType::Dot,
383            Distance::Manhattan => crate::quantization::DistanceType::L1,
384        }
385    }
386}
387
388#[derive(Debug, PartialEq, Clone, Copy)]
389pub enum Order {
390    LargeBetter,
391    SmallBetter,
392}
393
394/// Search result
395#[derive(Clone, Debug)]
396pub struct ScoredPoint {
397    /// Point id
398    pub id: PointIdType,
399    /// Point version
400    pub version: SeqNumberType,
401    /// Points vector distance to the query vector
402    pub score: ScoreType,
403    /// Payload - values assigned to the point
404    pub payload: Option<Payload>,
405    /// Vector of the point
406    pub vector: Option<VectorStructInternal>,
407    /// Shard Key
408    pub shard_key: Option<ShardKey>,
409    /// Order-by value
410    pub order_value: Option<OrderValue>,
411}
412
413impl Eq for ScoredPoint {}
414
415impl Ord for ScoredPoint {
416    /// Compare two scored points by score, unless they have `order_value`, in that case compare by `order_value`.
417    fn cmp(&self, other: &Self) -> Ordering {
418        match (&self.order_value, &other.order_value) {
419            (None, None) => OrderedFloat(self.score).cmp(&OrderedFloat(other.score)),
420            (Some(_), None) => Ordering::Greater,
421            (None, Some(_)) => Ordering::Less,
422            (Some(self_order), Some(other_order)) => self_order.cmp(other_order),
423        }
424    }
425}
426
427impl PartialOrd for ScoredPoint {
428    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
429        Some(self.cmp(other))
430    }
431}
432
433impl PartialEq for ScoredPoint {
434    fn eq(&self, other: &Self) -> bool {
435        (self.id, &self.score) == (other.id, &other.score)
436    }
437}
438
439/// Type of segment
440#[derive(Debug, Serialize, JsonSchema,  Clone, Copy, PartialEq, Eq)]
441#[serde(rename_all = "snake_case")]
442pub enum SegmentType {
443    // There are no index built for the segment, all operations are available
444    Plain,
445    // Segment with some sort of index built. Optimized for search, appending new points will require reindexing
446    Indexed,
447    // Some index which you better don't touch
448    Special,
449}
450
451/// Display payload field type & index information
452#[derive(Debug, Serialize, JsonSchema,  Clone, PartialEq, Eq)]
453#[serde(rename_all = "snake_case")]
454pub struct PayloadIndexInfo {
455    pub data_type: PayloadSchemaType,
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub params: Option<PayloadSchemaParams>,
458    /// Number of points indexed with this index
459    pub points: usize,
460}
461
462impl PayloadIndexInfo {
463    pub fn new(field_type: PayloadFieldSchema, points_count: usize) -> Self {
464        match field_type {
465            PayloadFieldSchema::FieldType(data_type) => PayloadIndexInfo {
466                data_type,
467                params: None,
468                points: points_count,
469            },
470            PayloadFieldSchema::FieldParams(schema_params) => PayloadIndexInfo {
471                data_type: schema_params.kind(),
472                params: Some(schema_params),
473                points: points_count,
474            },
475        }
476    }
477}
478
479/// Universal I/O backend that is used to read files.
480///
481/// Decided when the component is opened based on `storage.performance.io_uring` option,
482/// component memory placement and kernel io_uring support.
483///
484/// Options:
485///
486/// * `Mmap` - Reads are served by the page cache through a memory mapping.
487///
488/// * `IoUring` - Reads are submitted to the kernel with io_uring.
489#[derive(Debug, Serialize, Deserialize, JsonSchema,  Clone, Copy, PartialEq, Eq)]
490#[serde(rename_all = "snake_case")]
491pub enum IoBackend {
492    // Reads are served by the page cache through a memory mapping.
493    Mmap,
494    // Reads are submitted to the kernel with io_uring.
495    IoUring,
496}
497
498impl IoBackend {
499    /// `None` for a kind that is neither of the two a component can be opened on.
500    pub fn from_universal_kind(kind: crate::common::universal_io::UniversalKind) -> Option<Self> {
501        match kind {
502            crate::common::universal_io::UniversalKind::Mmap => Some(Self::Mmap),
503            crate::common::universal_io::UniversalKind::IoUring => Some(Self::IoUring),
504            crate::common::universal_io::UniversalKind::DiskCache
505            | crate::common::universal_io::UniversalKind::SimpleDiskCache
506            | crate::common::universal_io::UniversalKind::S3
507            | crate::common::universal_io::UniversalKind::Gcs
508            | crate::common::universal_io::UniversalKind::Azure
509            | crate::common::universal_io::UniversalKind::UioGrpc => None,
510        }
511    }
512}
513
514#[derive(Debug, Serialize, JsonSchema,  Clone, PartialEq, Eq)]
515#[serde(rename_all = "snake_case")]
516pub struct VectorDataInfo {
517    pub num_vectors: usize,
518    pub num_indexed_vectors: usize,
519    pub num_deleted_vectors: usize,
520    /// Universal I/O backend that this vector storage reads files with. Absent if vector storage
521    /// does not support configurable backends or only supports a single backend type.
522    #[serde(skip_serializing_if = "Option::is_none")]
523    
524    pub io_backend: Option<IoBackend>,
525}
526
527/// Aggregated information about segment
528#[derive(Debug, Serialize, JsonSchema,  Clone, PartialEq, Eq)]
529#[serde(rename_all = "snake_case")]
530pub struct SegmentInfo {
531    pub uuid: Uuid,
532    pub segment_type: SegmentType,
533    pub num_vectors: usize,
534    pub num_points: usize,
535    pub num_deferred_points: Option<usize>,
536    pub num_deleted_deferred_points: Option<usize>,
537    pub num_indexed_vectors: usize,
538    pub num_deleted_vectors: usize,
539    /// An ESTIMATION of effective amount of bytes used for vectors
540    /// Do NOT rely on this number unless you know what you are doing
541    pub vectors_size_bytes: usize,
542    /// An estimation of the effective amount of bytes used for payloads
543    pub payloads_size_bytes: usize,
544    pub ram_usage_bytes: usize,
545    pub disk_usage_bytes: usize,
546    pub is_appendable: bool,
547    pub index_schema: HashMap<PayloadKeyType, PayloadIndexInfo>,
548    pub vector_data: HashMap<String, VectorDataInfo>,
549    /// Universal I/O backend that payload storage reads files with. Absent if payload storage
550    /// does not support configurable backends or only supports a single backend type.
551    #[serde(skip_serializing_if = "Option::is_none")]
552    
553    pub payload_storage_io_backend: Option<IoBackend>,
554    /// Internal ID from which points are deferred (hidden from reads).
555    /// Only set for appendable segments.
556    #[serde(skip_serializing_if = "Option::is_none")]
557    
558    pub deferred_internal_id: Option<PointOffsetType>,
559}
560
561#[derive(Debug, Default)]
562pub struct SizeStats {
563    pub num_vectors: usize,
564    pub num_vectors_by_name: TinyMap<VectorNameBuf, usize>,
565    pub vectors_size_bytes: usize,
566    pub payloads_size_bytes: usize,
567    pub num_points: usize,
568}
569
570/// Additional parameters of the search
571#[derive(Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, Copy, PartialEq, Default)]
572#[serde(rename_all = "snake_case")]
573pub struct QuantizationSearchParams {
574    /// If true, quantized vectors are ignored. Default is false.
575    #[serde(default = "default_quantization_ignore_value")]
576    pub ignore: bool,
577
578    /// If true, use original vectors to re-score top-k results.
579    /// Might require more time in case if original vectors are stored on disk.
580    /// If not set, qdrant decides automatically apply rescoring or not.
581    #[serde(default)]
582    #[serde(skip_serializing_if = "Option::is_none")]
583    pub rescore: Option<bool>,
584
585    /// Oversampling factor for quantization. Default is 1.0.
586    ///
587    /// Defines how many extra vectors should be preselected using quantized index,
588    /// and then re-scored using original vectors.
589    ///
590    /// For example, if `oversampling` is 2.4 and `limit` is 100, then 240 vectors will be preselected using quantized index,
591    /// and then top-100 will be returned after re-scoring.
592    #[serde(default = "default_quantization_oversampling_value")]
593    #[validate(range(min = 1.0))]
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub oversampling: Option<f64>,
596}
597
598impl Hash for QuantizationSearchParams {
599    fn hash<H: Hasher>(&self, state: &mut H) {
600        let Self {
601            ignore,
602            rescore,
603            oversampling,
604        } = self;
605        ignore.hash(state);
606        rescore.hash(state);
607        oversampling.map(OrderedFloat).hash(state);
608    }
609}
610
611pub const fn default_quantization_ignore_value() -> bool {
612    false
613}
614
615pub const fn default_quantization_oversampling_value() -> Option<f64> {
616    None
617}
618
619/// Default value for [`AcornSearchParams::max_selectivity`].
620///
621/// After change, update docs for GRPC and REST API.
622pub const ACORN_MAX_SELECTIVITY_DEFAULT: f64 = 0.4;
623
624/// ACORN-related search parameters
625#[derive(
626    Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, Copy, PartialEq, Default, Hash,
627)]
628#[serde(rename_all = "snake_case")]
629pub struct AcornSearchParams {
630    /// If true, then ACORN may be used for the HNSW search based on filters
631    /// selectivity.
632
633    /// Improves search recall for searches with multiple low-selectivity
634    /// payload filters, at cost of performance.
635    #[serde(default)]
636    pub enable: bool,
637
638    /// Maximum selectivity of filters to enable ACORN.
639    ///
640    /// If estimated filters selectivity is higher than this value,
641    /// ACORN will not be used. Selectivity is estimated as:
642    /// `estimated number of points satisfying the filters / total number of points`.
643    ///
644    /// 0.0 for never, 1.0 for always. Default is 0.4.
645    #[serde(default)]
646    #[serde(skip_serializing_if = "Option::is_none")]
647    #[validate(range(min = 0.0, max = 1.0))]
648    pub max_selectivity: Option<OrderedFloat<f64>>,
649}
650
651/// Additional parameters of the search
652#[derive(Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, PartialEq, Default, Hash)]
653#[serde(rename_all = "snake_case")]
654pub struct SearchParams {
655    /// Params relevant to HNSW index
656    /// Size of the beam in a beam-search. Larger the value - more accurate the result, more time required for search.
657    #[serde(skip_serializing_if = "Option::is_none")]
658    #[validate(range(min = 1))]
659    pub hnsw_ef: Option<usize>,
660
661    /// Search without approximation. If set to true, search may run long but with exact results.
662    #[serde(default)]
663    pub exact: bool,
664
665    /// Quantization params
666    #[serde(default)]
667    #[validate(nested)]
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub quantization: Option<QuantizationSearchParams>,
670
671    /// If enabled, the engine will only perform search among indexed or small segments.
672    /// Using this option prevents slow searches in case of delayed index, but does not
673    /// guarantee that all uploaded vectors will be included in search results
674    #[serde(default)]
675    pub indexed_only: bool,
676
677    /// ACORN search params
678    #[serde(default)]
679    #[validate(nested)]
680    #[serde(skip_serializing_if = "Option::is_none")]
681    pub acorn: Option<AcornSearchParams>,
682
683    /// Which population sparse vector IDF statistics are computed over.
684    /// By default (or with explicit `"global"`) statistics are collection-wide.
685    /// Only applicable to sparse vectors with the IDF modifier enabled.
686    #[serde(default)]
687    #[validate(nested)]
688    #[serde(skip_serializing_if = "Option::is_none")]
689    pub idf: Option<IdfParams>,
690}
691
692/// Population over which sparse vector IDF statistics are computed for scoring —
693/// the *IDF corpus*.
694///
695/// - `"global"` — collection-wide statistics, same as omitting the parameter.
696/// - `{ "corpus": <filter> }` — document count and per-term document frequencies
697///   are computed over the points matching the corpus filter only. The corpus is
698///   independent of the retrieval filter and is usually broader than it.
699#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
700#[serde(untagged)]
701pub enum IdfParams {
702    Scope(IdfScope),
703    Corpus(IdfCorpusParams),
704}
705
706/// Named IDF scope without a corpus filter.
707#[derive(Debug, Deserialize, Serialize, JsonSchema, Copy, Clone, PartialEq, Eq, Hash)]
708#[serde(rename_all = "snake_case")]
709pub enum IdfScope {
710    // Collection-wide statistics. This is the default behavior.
711    Global,
712}
713
714/// IDF statistics computed over the points matching a corpus filter.
715#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
716#[serde(rename_all = "snake_case")]
717pub struct IdfCorpusParams {
718    /// Filter defining the corpus: IDF statistics are computed over the points
719    /// matching this filter.
720    pub corpus: Filter,
721}
722
723impl IdfParams {
724    /// Corpus filter defining the IDF population, `None` for global statistics.
725    pub fn corpus(&self) -> Option<&Filter> {
726        match self {
727            IdfParams::Scope(IdfScope::Global) => None,
728            IdfParams::Corpus(IdfCorpusParams { corpus }) => Some(corpus),
729        }
730    }
731}
732
733impl Validate for IdfParams {
734    fn validate(&self) -> Result<(), ValidationErrors> {
735        match self {
736            IdfParams::Scope(IdfScope::Global) => Ok(()),
737            IdfParams::Corpus(corpus_params) => corpus_params.validate(),
738        }
739    }
740}
741
742impl Validate for IdfCorpusParams {
743    fn validate(&self) -> Result<(), ValidationErrors> {
744        let IdfCorpusParams { corpus } = self;
745        corpus.validate()
746    }
747}
748
749/// Configuration for vectors.
750#[derive(Debug, Deserialize, Validate, Clone, PartialEq, Eq)]
751pub struct VectorsConfigDefaults {
752    /// Deprecated: use `memory` instead.
753    #[serde(default)]
754    #[deprecated(since = "1.19.0", note = "Use `memory` instead")]
755    pub on_disk: Option<bool>,
756    /// Default memory placement of the original vector storage for newly created collections.
757    /// Overrides the deprecated `on_disk` flag if both are set. `pinned` is not supported for
758    /// dense vector storage.
759    #[serde(default)]
760    #[validate(custom(function = "validate_dense_vector_memory"))]
761    pub memory: Option<Memory>,
762}
763
764/// Reject memory placements not supported by dense vector storage.
765/// `validator` unwraps `Option<Memory>` before calling, so we receive `&Memory`.
766fn validate_dense_vector_memory(memory: &Memory) -> Result<(), ValidationError> {
767    match memory {
768        Memory::Cold | Memory::Cached => Ok(()),
769        Memory::Pinned => {
770            let mut error = ValidationError::new("unsupported_memory_placement");
771            error.message = Some(Cow::from(
772                "`pinned` memory placement is not supported for dense vector storage",
773            ));
774            Err(error)
775        }
776    }
777}
778
779/// Vector index configuration
780#[derive(Debug, Deserialize, Serialize, JsonSchema,  Clone, PartialEq, Eq)]
781#[serde(rename_all = "snake_case")]
782#[serde(tag = "type", content = "options")]
783pub enum Indexes {
784    /// Do not use any index, scan whole vector collection during search.
785    /// Guarantee 100% precision, but may be time consuming on large collections.
786    Plain {},
787    /// Use filterable HNSW index for approximate search. Is very fast even on a very huge collections,
788    /// but require additional space to store index and additional time to build it.
789    Hnsw(HnswConfig),
790}
791
792impl Indexes {
793    pub fn is_indexed(&self) -> bool {
794        match self {
795            Indexes::Plain {} => false,
796            Indexes::Hnsw(_) => true,
797        }
798    }
799
800    pub fn is_on_disk(&self) -> bool {
801        match self {
802            Indexes::Plain {} => false,
803            Indexes::Hnsw(config) => config.memory_placement().is_on_disk(),
804        }
805    }
806}
807
808/// Config of HNSW index
809#[derive(
810    Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize, JsonSchema, Validate, 
811)]
812#[serde(rename_all = "snake_case")]
813
814pub struct HnswConfig {
815    /// Number of edges per node in the index graph. Larger the value - more accurate the search, more space required.
816    pub m: usize,
817    /// Number of neighbours to consider during the index building. Larger the value - more accurate the search, more time required to build index.
818    #[validate(range(min = 4))]
819    pub ef_construct: usize,
820    /// Minimal size threshold (in KiloBytes) below which full-scan is preferred over HNSW search.
821    /// This measures the total size of vectors being queried against.
822    /// When the maximum estimated amount of points that a condition satisfies is smaller than
823    /// `full_scan_threshold_kb`, the query planner will use full-scan search instead of HNSW index
824    /// traversal for better performance.
825    /// Note: 1Kb = 1 vector of size 256
826    #[serde(alias = "full_scan_threshold_kb")]
827    pub full_scan_threshold: usize,
828    /// Number of parallel threads used for background index building.
829    /// If 0 - automatically select from 8 to 16.
830    /// Best to keep between 8 and 16 to prevent likelihood of slow building or broken/inefficient HNSW graphs.
831    /// On small CPUs, less threads are used.
832    #[serde(default = "default_max_indexing_threads")]
833    pub max_indexing_threads: usize,
834    /// Deprecated: use `memory` instead.
835    /// Store HNSW index on disk. If set to false, index will be stored in RAM. Default: false
836    #[serde(default, skip_serializing_if = "Option::is_none")] // Better backward compatibility
837    #[deprecated(since = "1.19.0", note = "Use `memory` instead")]
838    pub on_disk: Option<bool>,
839    /// Memory placement of the HNSW graph. Overrides the deprecated `on_disk` flag if both are
840    /// set. Default: `cached` (`cold` if `on_disk` is set to true).
841    #[serde(default, skip_serializing_if = "Option::is_none")] // Better backward compatibility
842    pub memory: Option<Memory>,
843    /// Custom M param for hnsw graph built for payload index. If not set, default M will be used.
844    #[serde(default, skip_serializing_if = "Option::is_none")] // Better backward compatibility
845    pub payload_m: Option<usize>,
846    /// Store copies of original and quantized vectors within the HNSW index file. Default: false.
847    /// Enabling this option will trade the search speed for disk usage by reducing amount of
848    /// random seeks during the search.
849    /// Requires quantized vectors to be enabled. Multi-vectors are not supported.
850    #[serde(default, skip_serializing_if = "Option::is_none")]
851    pub inline_storage: Option<bool>,
852}
853
854impl HnswConfig {
855    /// Detect configuration mismatch against `other` that requires rebuilding
856    ///
857    /// Returns true only if both conditions are met:
858    /// - this configuration does not match `other`
859    /// - to effectively change the configuration, a HNSW rebuild is required
860    ///
861    /// For example, a change in `max_indexing_threads` will not require rebuilding because it
862    /// doesn't affect the final index, and thus this would return false.
863    pub fn mismatch_requires_rebuild(&self, other: &Self) -> bool {
864        let HnswConfig {
865            m,
866            ef_construct,
867            full_scan_threshold,
868            max_indexing_threads: _,
869            payload_m,
870            // Compared through the effective placement below, so that expressing the same
871            // placement through the new `memory` parameter does not trigger a rebuild
872            on_disk: _,
873            memory: _,
874            inline_storage,
875        } = *self;
876
877        m != other.m
878            || ef_construct != other.ef_construct
879            || full_scan_threshold != other.full_scan_threshold
880            || payload_m != other.payload_m
881            // Data on disk is the same, we have a unit test for that. We can eventually optimize
882            // this to just reload the collection rather than optimizing it again as a whole just
883            // to flip this flag
884            || self.memory_placement() != other.memory_placement()
885            || inline_storage != other.inline_storage
886    }
887
888    /// Effective memory placement of the HNSW graph, resolving the new `memory` parameter
889    /// against the deprecated `on_disk` flag. Defaults to [`Memory::Cached`].
890    pub fn memory_placement(&self) -> Memory {
891        Memory::resolve(self.memory, self.on_disk.map(Memory::from_on_disk))
892            .unwrap_or(Memory::Cached)
893    }
894}
895
896#[derive(Debug, Deserialize, Serialize, JsonSchema, Validate,  Clone)]
897#[serde(rename_all = "snake_case", default)]
898
899pub struct HnswGlobalConfig {
900    /// Enable HNSW healing if the ratio of missing points is no more than this value.
901    /// To disable healing completely, set this value to `0.0`.
902    #[validate(range(min = 0.0, max = 1.0))]
903    pub healing_threshold: f64,
904}
905
906impl Default for HnswGlobalConfig {
907    fn default() -> Self {
908        Self {
909            healing_threshold: 0.3,
910        }
911    }
912}
913
914const fn default_max_indexing_threads() -> usize {
915    0
916}
917
918#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Copy, PartialEq, Eq, Hash)]
919#[serde(rename_all = "lowercase")]
920pub enum CompressionRatio {
921    X4,
922    X8,
923    X16,
924    X32,
925    X64,
926}
927
928#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, Deserialize, Serialize, JsonSchema)]
929#[serde(rename_all = "lowercase")]
930pub enum ScalarType {
931    #[default]
932    Int8,
933}
934
935#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema, Validate)]
936#[serde(rename_all = "snake_case")]
937pub struct ScalarQuantizationConfig {
938    /// Type of quantization to use
939    /// If `int8` - 8 bit quantization will be used
940    pub r#type: ScalarType,
941    /// Quantile for quantization. Expected value range in [0.5, 1.0]. If not set - use the whole range of values
942    #[serde(skip_serializing_if = "Option::is_none")]
943    #[validate(range(min = 0.5, max = 1.0))]
944    pub quantile: Option<f32>,
945    /// Deprecated: use `memory` instead.
946    /// If true - quantized vectors always will be stored in RAM, ignoring the config of main storage
947    #[serde(skip_serializing_if = "Option::is_none")]
948    #[deprecated(since = "1.19.0", note = "Use `memory` instead")]
949    pub always_ram: Option<bool>,
950    /// Memory placement of quantized vectors. Overrides the deprecated `always_ram` flag if
951    /// both are set. Default: follow the memory placement of the original vector storage.
952    #[serde(default, skip_serializing_if = "Option::is_none")]
953    pub memory: Option<Memory>,
954}
955
956impl ScalarQuantizationConfig {
957    /// Detect configuration mismatch against `other` that requires rebuilding
958    ///
959    /// Returns true only if both conditions are met:
960    /// - this configuration does not match `other`
961    /// - to effectively change the configuration, a quantization rebuild is required
962    pub fn mismatch_requires_rebuild(&self, other: &Self) -> bool {
963        self != other
964    }
965
966    /// Requested memory placement, resolving the new `memory` parameter against the deprecated
967    /// `always_ram` flag. `None` means following the original vector storage placement.
968    pub fn memory_placement(&self) -> Option<Memory> {
969        Memory::resolve(self.memory, legacy_always_ram_placement(self.always_ram))
970    }
971}
972
973/// `always_ram=true` used to force quantized vectors into RAM, never evicted by placement
974/// config: the pinned placement. `always_ram=false` means "follow storage", same as unset.
975fn legacy_always_ram_placement(always_ram: Option<bool>) -> Option<Memory> {
976    always_ram.and_then(|always_ram| always_ram.then_some(Memory::Pinned))
977}
978
979#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize, JsonSchema, Validate)]
980pub struct ScalarQuantization {
981    #[validate(nested)]
982    pub scalar: ScalarQuantizationConfig,
983}
984
985#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize, JsonSchema, Validate)]
986#[serde(rename_all = "snake_case")]
987pub struct ProductQuantizationConfig {
988    pub compression: CompressionRatio,
989
990    /// Deprecated: use `memory` instead.
991    #[serde(skip_serializing_if = "Option::is_none")]
992    #[deprecated(since = "1.19.0", note = "Use `memory` instead")]
993    pub always_ram: Option<bool>,
994
995    /// Memory placement of quantized vectors. Overrides the deprecated `always_ram` flag if
996    /// both are set. Default: follow the memory placement of the original vector storage.
997    #[serde(default, skip_serializing_if = "Option::is_none")]
998    pub memory: Option<Memory>,
999}
1000
1001impl ProductQuantizationConfig {
1002    /// Detect configuration mismatch against `other` that requires rebuilding
1003    ///
1004    /// Returns true only if both conditions are met:
1005    /// - this configuration does not match `other`
1006    /// - to effectively change the configuration, a quantization rebuild is required
1007    pub fn mismatch_requires_rebuild(&self, other: &Self) -> bool {
1008        self != other
1009    }
1010
1011    /// Requested memory placement, resolving the new `memory` parameter against the deprecated
1012    /// `always_ram` flag. `None` means following the original vector storage placement.
1013    pub fn memory_placement(&self) -> Option<Memory> {
1014        Memory::resolve(self.memory, legacy_always_ram_placement(self.always_ram))
1015    }
1016}
1017
1018#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize, JsonSchema, Validate)]
1019pub struct ProductQuantization {
1020    #[validate(nested)]
1021    pub product: ProductQuantizationConfig,
1022}
1023
1024impl Hash for ScalarQuantizationConfig {
1025    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1026        self.always_ram.hash(state);
1027        self.memory.hash(state);
1028        self.r#type.hash(state);
1029    }
1030}
1031
1032impl Eq for ScalarQuantizationConfig {}
1033
1034#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Copy, PartialEq, Eq, Hash, Default)]
1035#[serde(rename_all = "snake_case")]
1036pub enum BinaryQuantizationEncoding {
1037    #[default]
1038    OneBit,
1039    TwoBits,
1040    OneAndHalfBits,
1041}
1042
1043#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize, JsonSchema, Validate)]
1044#[serde(rename_all = "snake_case")]
1045pub struct BinaryQuantizationConfig {
1046    /// Deprecated: use `memory` instead.
1047    #[serde(skip_serializing_if = "Option::is_none")]
1048    #[deprecated(since = "1.19.0", note = "Use `memory` instead")]
1049    pub always_ram: Option<bool>,
1050    /// Memory placement of quantized vectors. Overrides the deprecated `always_ram` flag if
1051    /// both are set. Default: follow the memory placement of the original vector storage.
1052    #[serde(default, skip_serializing_if = "Option::is_none")]
1053    pub memory: Option<Memory>,
1054    #[serde(default)]
1055    #[serde(skip_serializing_if = "Option::is_none")]
1056    pub encoding: Option<BinaryQuantizationEncoding>,
1057
1058    /// Asymmetric quantization configuration allows a query to have different quantization than stored vectors.
1059    /// It can increase the accuracy of search at the cost of performance.
1060    #[serde(default)]
1061    #[serde(skip_serializing_if = "Option::is_none")]
1062    pub query_encoding: Option<BinaryQuantizationQueryEncoding>,
1063}
1064
1065impl BinaryQuantizationConfig {
1066    /// Requested memory placement, resolving the new `memory` parameter against the deprecated
1067    /// `always_ram` flag. `None` means following the original vector storage placement.
1068    pub fn memory_placement(&self) -> Option<Memory> {
1069        Memory::resolve(self.memory, legacy_always_ram_placement(self.always_ram))
1070    }
1071}
1072
1073#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize, JsonSchema, Validate)]
1074pub struct BinaryQuantization {
1075    #[validate(nested)]
1076    pub binary: BinaryQuantizationConfig,
1077}
1078
1079#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Copy, PartialEq, Eq, Hash, Default)]
1080#[serde(rename_all = "snake_case")]
1081pub enum TurboQuantBitSize {
1082    Bits1,
1083    Bits1_5,
1084    Bits2,
1085    #[default]
1086    Bits4,
1087}
1088
1089#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize, JsonSchema, Validate)]
1090#[serde(rename_all = "snake_case")]
1091pub struct TurboQuantQuantizationConfig {
1092    /// Deprecated: use `memory` instead.
1093    #[serde(skip_serializing_if = "Option::is_none")]
1094    #[deprecated(since = "1.19.0", note = "Use `memory` instead")]
1095    pub always_ram: Option<bool>,
1096    /// Memory placement of quantized vectors. Overrides the deprecated `always_ram` flag if
1097    /// both are set. Default: follow the memory placement of the original vector storage.
1098    #[serde(default, skip_serializing_if = "Option::is_none")]
1099    pub memory: Option<Memory>,
1100
1101    #[serde(default)]
1102    #[serde(skip_serializing_if = "Option::is_none")]
1103    pub bits: Option<TurboQuantBitSize>,
1104}
1105
1106impl TurboQuantQuantizationConfig {
1107    /// Requested memory placement, resolving the new `memory` parameter against the deprecated
1108    /// `always_ram` flag. `None` means following the original vector storage placement.
1109    pub fn memory_placement(&self) -> Option<Memory> {
1110        Memory::resolve(self.memory, legacy_always_ram_placement(self.always_ram))
1111    }
1112}
1113
1114#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize, JsonSchema, Validate)]
1115pub struct TurboQuantization {
1116    #[validate(nested)]
1117    pub turbo: TurboQuantQuantizationConfig,
1118}
1119
1120#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize, JsonSchema, )]
1121#[serde(untagged, rename_all = "snake_case")]
1122
1123pub enum QuantizationConfig {
1124    Scalar(ScalarQuantization),
1125    Product(ProductQuantization),
1126    Binary(BinaryQuantization),
1127    Turbo(TurboQuantization),
1128}
1129
1130impl QuantizationConfig {
1131    /// If appendable_quantization feature is enabled and config supports appendable segments,
1132    /// returns the config for use in appendable segment; otherwise `None`.
1133    pub fn for_appendable_segment(opt: Option<&Self>) -> Option<Self> {
1134        let appendable = crate::common::flags::feature_flags().appendable_quantization;
1135        opt.filter(|q| appendable && q.supports_appendable())
1136            .cloned()
1137    }
1138
1139    /// Detect configuration mismatch against `other` that requires rebuilding
1140    ///
1141    /// Returns true only if both conditions are met:
1142    /// - this configuration does not match `other`
1143    /// - to effectively change the configuration, a quantization rebuild is required
1144    pub fn mismatch_requires_rebuild(&self, other: &Self) -> bool {
1145        self != other
1146    }
1147
1148    pub fn supports_appendable(&self) -> bool {
1149        matches!(
1150            self,
1151            QuantizationConfig::Binary(_) | QuantizationConfig::Turbo(_)
1152        )
1153    }
1154
1155    pub fn always_ram(&self) -> bool {
1156        match self {
1157            QuantizationConfig::Scalar(s) => s.scalar.always_ram == Some(true),
1158            QuantizationConfig::Product(p) => p.product.always_ram == Some(true),
1159            QuantizationConfig::Binary(b) => b.binary.always_ram == Some(true),
1160            QuantizationConfig::Turbo(t) => t.turbo.always_ram == Some(true),
1161        }
1162    }
1163
1164    /// Requested memory placement of quantized vectors, resolving the new `memory` parameter
1165    /// against the deprecated `always_ram` flag.
1166    ///
1167    /// `None` means the placement follows the original vector storage.
1168    pub fn memory_placement(&self) -> Option<Memory> {
1169        match self {
1170            QuantizationConfig::Scalar(s) => s.scalar.memory_placement(),
1171            QuantizationConfig::Product(p) => p.product.memory_placement(),
1172            QuantizationConfig::Binary(b) => b.binary.memory_placement(),
1173            QuantizationConfig::Turbo(t) => t.turbo.memory_placement(),
1174        }
1175    }
1176}
1177
1178impl Validate for QuantizationConfig {
1179    fn validate(&self) -> Result<(), ValidationErrors> {
1180        match self {
1181            QuantizationConfig::Scalar(scalar) => scalar.validate(),
1182            QuantizationConfig::Product(product) => product.validate(),
1183            QuantizationConfig::Binary(binary) => binary.validate(),
1184            QuantizationConfig::Turbo(turbo) => turbo.validate(),
1185        }
1186    }
1187}
1188
1189#[derive(
1190    Default, Debug, Deserialize, Serialize, JsonSchema,  Clone, Copy, PartialEq, Eq, Hash,
1191)]
1192#[serde(rename_all = "lowercase")]
1193
1194pub enum BinaryQuantizationQueryEncoding {
1195    #[default]
1196    Default,
1197    Binary,
1198    Scalar4Bits,
1199    Scalar8Bits,
1200}
1201
1202impl From<ScalarQuantizationConfig> for QuantizationConfig {
1203    fn from(config: ScalarQuantizationConfig) -> Self {
1204        QuantizationConfig::Scalar(ScalarQuantization { scalar: config })
1205    }
1206}
1207
1208impl From<ProductQuantizationConfig> for QuantizationConfig {
1209    fn from(config: ProductQuantizationConfig) -> Self {
1210        QuantizationConfig::Product(ProductQuantization { product: config })
1211    }
1212}
1213
1214impl From<BinaryQuantizationConfig> for QuantizationConfig {
1215    fn from(config: BinaryQuantizationConfig) -> Self {
1216        QuantizationConfig::Binary(BinaryQuantization { binary: config })
1217    }
1218}
1219
1220#[derive(Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, PartialEq, Default, Hash)]
1221pub struct StrictModeSparse {
1222    /// Max length of sparse vector
1223    #[serde(skip_serializing_if = "Option::is_none")]
1224    #[validate(range(min = 1))]
1225    pub max_length: Option<usize>,
1226}
1227
1228#[derive(Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, PartialEq, Default, Hash)]
1229#[schemars(deny_unknown_fields)]
1230pub struct StrictModeSparseConfig {
1231    #[validate(nested)]
1232    #[serde(flatten)]
1233    pub config: BTreeMap<VectorNameBuf, StrictModeSparse>,
1234}
1235
1236#[derive(Debug, Deserialize, Serialize, JsonSchema,  Clone, PartialEq, Default)]
1237#[schemars(deny_unknown_fields)]
1238pub struct StrictModeSparseConfigOutput {
1239    #[serde(flatten)]
1240    pub config: BTreeMap<VectorNameBuf, StrictModeSparseOutput>,
1241}
1242
1243#[derive(Debug, Deserialize, Serialize, JsonSchema,  Clone, PartialEq, Default)]
1244pub struct StrictModeSparseOutput {
1245    /// Max length of sparse vector
1246    #[serde(skip_serializing_if = "Option::is_none")]
1247    
1248    pub max_length: Option<usize>,
1249}
1250
1251impl From<StrictModeSparseConfig> for StrictModeSparseConfigOutput {
1252    fn from(config: StrictModeSparseConfig) -> Self {
1253        let StrictModeSparseConfig { config } = config;
1254        let mut new_config = StrictModeSparseConfigOutput::default();
1255        for (key, value) in config {
1256            new_config
1257                .config
1258                .insert(key, StrictModeSparseOutput::from(value));
1259        }
1260        new_config
1261    }
1262}
1263
1264impl From<StrictModeSparse> for StrictModeSparseOutput {
1265    fn from(config: StrictModeSparse) -> Self {
1266        let StrictModeSparse { max_length } = config;
1267        StrictModeSparseOutput { max_length }
1268    }
1269}
1270
1271#[derive(Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, PartialEq, Default, Hash)]
1272pub struct StrictModeMultivector {
1273    /// Max number of vectors in a multivector
1274    #[serde(skip_serializing_if = "Option::is_none")]
1275    #[validate(range(min = 1))]
1276    pub max_vectors: Option<usize>,
1277}
1278
1279#[derive(Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, PartialEq, Default, Hash)]
1280#[schemars(deny_unknown_fields)]
1281pub struct StrictModeMultivectorConfig {
1282    #[validate(nested)]
1283    #[serde(flatten)]
1284    pub config: BTreeMap<VectorNameBuf, StrictModeMultivector>,
1285}
1286
1287#[derive(Debug, Deserialize, Serialize, JsonSchema,  Clone, PartialEq, Default)]
1288#[schemars(deny_unknown_fields)]
1289pub struct StrictModeMultivectorConfigOutput {
1290    #[serde(flatten)]
1291    pub config: BTreeMap<VectorNameBuf, StrictModeMultivectorOutput>,
1292}
1293
1294impl From<StrictModeMultivectorConfig> for StrictModeMultivectorConfigOutput {
1295    fn from(config: StrictModeMultivectorConfig) -> Self {
1296        let StrictModeMultivectorConfig { config } = config;
1297        let mut new_config = StrictModeMultivectorConfigOutput::default();
1298        for (key, value) in config {
1299            new_config
1300                .config
1301                .insert(key, StrictModeMultivectorOutput::from(value));
1302        }
1303        new_config
1304    }
1305}
1306
1307#[derive(Debug, Deserialize, Serialize, JsonSchema,  Clone, PartialEq, Default)]
1308pub struct StrictModeMultivectorOutput {
1309    /// Max number of vectors in a multivector
1310    #[serde(skip_serializing_if = "Option::is_none")]
1311    
1312    pub max_vectors: Option<usize>,
1313}
1314
1315impl From<StrictModeMultivector> for StrictModeMultivectorOutput {
1316    fn from(config: StrictModeMultivector) -> Self {
1317        let StrictModeMultivector { max_vectors } = config;
1318        StrictModeMultivectorOutput { max_vectors }
1319    }
1320}
1321
1322#[derive(Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, PartialEq, Default)]
1323pub struct StrictModeConfig {
1324    // Global
1325    /// Whether strict mode is enabled for a collection or not.
1326    #[serde(skip_serializing_if = "Option::is_none")]
1327    pub enabled: Option<bool>,
1328
1329    /// Max allowed `limit` parameter for all APIs that don't have their own max limit.
1330    #[serde(skip_serializing_if = "Option::is_none")]
1331    #[validate(range(min = 1))]
1332    pub max_query_limit: Option<usize>,
1333
1334    /// Max allowed `timeout` parameter.
1335    #[serde(skip_serializing_if = "Option::is_none")]
1336    #[validate(range(min = 1))]
1337    pub max_timeout: Option<usize>,
1338
1339    /// Allow usage of unindexed fields in retrieval based (e.g. search) filters.
1340    #[serde(skip_serializing_if = "Option::is_none")]
1341    pub unindexed_filtering_retrieve: Option<bool>,
1342
1343    /// Allow usage of unindexed fields in filtered updates (e.g. delete by payload).
1344    #[serde(skip_serializing_if = "Option::is_none")]
1345    pub unindexed_filtering_update: Option<bool>,
1346
1347    // Search
1348    /// Max HNSW ef value allowed in search parameters.
1349    #[serde(skip_serializing_if = "Option::is_none")]
1350    pub search_max_hnsw_ef: Option<usize>,
1351
1352    /// Whether exact search is allowed.
1353    #[serde(skip_serializing_if = "Option::is_none")]
1354    pub search_allow_exact: Option<bool>,
1355
1356    /// Max oversampling value allowed in search.
1357    #[serde(skip_serializing_if = "Option::is_none")]
1358    pub search_max_oversampling: Option<f64>,
1359
1360    /// Max batchsize when upserting
1361    #[serde(skip_serializing_if = "Option::is_none")]
1362    pub upsert_max_batchsize: Option<usize>,
1363
1364    /// Max batchsize when searching
1365    #[serde(skip_serializing_if = "Option::is_none")]
1366    pub search_max_batchsize: Option<usize>,
1367
1368    /// Max size of a collections vector storage in bytes, ignoring replicas.
1369    #[serde(skip_serializing_if = "Option::is_none")]
1370    pub max_collection_vector_size_bytes: Option<usize>,
1371
1372    /// Max number of read operations per minute per replica
1373    #[serde(skip_serializing_if = "Option::is_none")]
1374    #[validate(range(min = 1))]
1375    pub read_rate_limit: Option<usize>,
1376
1377    /// Max number of write operations per minute per replica
1378    #[serde(skip_serializing_if = "Option::is_none")]
1379    #[validate(range(min = 1))]
1380    pub write_rate_limit: Option<usize>,
1381
1382    /// Max size of a collections payload storage in bytes
1383    #[serde(skip_serializing_if = "Option::is_none")]
1384    pub max_collection_payload_size_bytes: Option<usize>,
1385
1386    /// Max number of points estimated in a collection
1387    #[serde(skip_serializing_if = "Option::is_none")]
1388    #[validate(range(min = 1))]
1389    pub max_points_count: Option<usize>,
1390
1391    /// Max conditions a filter can have.
1392    #[serde(skip_serializing_if = "Option::is_none")]
1393    pub filter_max_conditions: Option<usize>,
1394
1395    /// Max size of a condition, eg. items in `MatchAny`.
1396    #[serde(skip_serializing_if = "Option::is_none")]
1397    pub condition_max_size: Option<usize>,
1398
1399    /// Multivector strict mode configuration
1400    #[serde(skip_serializing_if = "Option::is_none")]
1401    #[validate(nested)]
1402    pub multivector_config: Option<StrictModeMultivectorConfig>,
1403
1404    /// Sparse vector strict mode configuration
1405    #[serde(skip_serializing_if = "Option::is_none")]
1406    #[validate(nested)]
1407    pub sparse_config: Option<StrictModeSparseConfig>,
1408
1409    /// Max number of payload indexes in a collection
1410    #[serde(skip_serializing_if = "Option::is_none")]
1411    #[validate(range(min = 0))]
1412    pub max_payload_index_count: Option<usize>,
1413
1414    /// Deprecated: use the node-wide quota config (`PUT /quotas`) instead, which
1415    /// caps the same resource for every collection. Scheduled for removal in
1416    /// 1.21.
1417    ///
1418    /// Reject memory-consuming update operations (e.g. upsert, set payload)
1419    /// when the process resident memory exceeds this percentage of total system
1420    /// memory (or cgroup limit). Value in [1, 100]. Memory is a node-wide
1421    /// resource, so this only tightens the quota for one collection; it cannot
1422    /// lift it. Delete operations are not affected, so callers can still free
1423    /// memory.
1424    #[deprecated(
1425        since = "1.19.0",
1426        note = "memory is node-wide: use the global quota config instead. Removal planned for 1.21"
1427    )]
1428    #[serde(skip_serializing_if = "Option::is_none")]
1429    #[validate(range(min = 1, max = 100))]
1430    pub max_resident_memory_percent: Option<u8>,
1431}
1432
1433impl Eq for StrictModeConfig {}
1434
1435impl Hash for StrictModeConfig {
1436    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1437        let Self {
1438            enabled,
1439            max_query_limit,
1440            max_timeout,
1441            unindexed_filtering_retrieve,
1442            unindexed_filtering_update,
1443            search_max_hnsw_ef,
1444            search_allow_exact,
1445            // We skip hashing this field because we cannot reliably hash a float
1446            search_max_oversampling: _,
1447            upsert_max_batchsize,
1448            search_max_batchsize,
1449            max_collection_vector_size_bytes,
1450            read_rate_limit,
1451            write_rate_limit,
1452            max_collection_payload_size_bytes,
1453            max_points_count,
1454            filter_max_conditions,
1455            condition_max_size,
1456            multivector_config,
1457            sparse_config,
1458            max_payload_index_count,
1459            max_resident_memory_percent,
1460        } = self;
1461        enabled.hash(state);
1462        max_query_limit.hash(state);
1463        max_timeout.hash(state);
1464        unindexed_filtering_retrieve.hash(state);
1465        unindexed_filtering_update.hash(state);
1466        search_max_hnsw_ef.hash(state);
1467        search_allow_exact.hash(state);
1468        upsert_max_batchsize.hash(state);
1469        search_max_batchsize.hash(state);
1470        max_collection_vector_size_bytes.hash(state);
1471        read_rate_limit.hash(state);
1472        write_rate_limit.hash(state);
1473        max_collection_payload_size_bytes.hash(state);
1474        max_points_count.hash(state);
1475        filter_max_conditions.hash(state);
1476        condition_max_size.hash(state);
1477        multivector_config.hash(state);
1478        sparse_config.hash(state);
1479        max_payload_index_count.hash(state);
1480        max_resident_memory_percent.hash(state);
1481    }
1482}
1483
1484// Version of the strict mode config we can present to the user
1485#[derive(Debug, Deserialize, Serialize, JsonSchema,  Clone, PartialEq, Default)]
1486pub struct StrictModeConfigOutput {
1487    // Global
1488    /// Whether strict mode is enabled for a collection or not.
1489    #[serde(skip_serializing_if = "Option::is_none")]
1490    pub enabled: Option<bool>,
1491
1492    /// Max allowed `limit` parameter for all APIs that don't have their own max limit.
1493    #[serde(skip_serializing_if = "Option::is_none")]
1494    
1495    pub max_query_limit: Option<usize>,
1496
1497    /// Max allowed `timeout` parameter.
1498    #[serde(skip_serializing_if = "Option::is_none")]
1499    
1500    pub max_timeout: Option<usize>,
1501
1502    /// Allow usage of unindexed fields in retrieval based (e.g. search) filters.
1503    #[serde(skip_serializing_if = "Option::is_none")]
1504    pub unindexed_filtering_retrieve: Option<bool>,
1505
1506    /// Allow usage of unindexed fields in filtered updates (e.g. delete by payload).
1507    #[serde(skip_serializing_if = "Option::is_none")]
1508    pub unindexed_filtering_update: Option<bool>,
1509
1510    // Search
1511    /// Max HNSW value allowed in search parameters.
1512    #[serde(skip_serializing_if = "Option::is_none")]
1513    
1514    pub search_max_hnsw_ef: Option<usize>,
1515
1516    /// Whether exact search is allowed or not.
1517    #[serde(skip_serializing_if = "Option::is_none")]
1518    pub search_allow_exact: Option<bool>,
1519
1520    /// Max oversampling value allowed in search.
1521    #[serde(skip_serializing_if = "Option::is_none")]
1522    
1523    pub search_max_oversampling: Option<f64>,
1524
1525    /// Max batchsize when upserting
1526    #[serde(skip_serializing_if = "Option::is_none")]
1527    
1528    pub upsert_max_batchsize: Option<usize>,
1529    /// Max batchsize when searching
1530    #[serde(skip_serializing_if = "Option::is_none")]
1531    
1532    pub search_max_batchsize: Option<usize>,
1533
1534    /// Max size of a collections vector storage in bytes, ignoring replicas.
1535    #[serde(skip_serializing_if = "Option::is_none")]
1536    
1537    pub max_collection_vector_size_bytes: Option<usize>,
1538
1539    /// Max number of read operations per minute per replica
1540    #[serde(skip_serializing_if = "Option::is_none")]
1541    
1542    pub read_rate_limit: Option<usize>,
1543
1544    /// Max number of write operations per minute per replica
1545    #[serde(skip_serializing_if = "Option::is_none")]
1546    
1547    pub write_rate_limit: Option<usize>,
1548
1549    /// Max size of a collections payload storage in bytes
1550    #[serde(skip_serializing_if = "Option::is_none")]
1551    
1552    pub max_collection_payload_size_bytes: Option<usize>,
1553
1554    /// Max number of points estimated in a collection
1555    #[serde(skip_serializing_if = "Option::is_none")]
1556    
1557    pub max_points_count: Option<usize>,
1558
1559    /// Max conditions a filter can have.
1560    #[serde(skip_serializing_if = "Option::is_none")]
1561    
1562    pub filter_max_conditions: Option<usize>,
1563
1564    /// Max size of a condition, eg. items in `MatchAny`.
1565    #[serde(skip_serializing_if = "Option::is_none")]
1566    
1567    pub condition_max_size: Option<usize>,
1568
1569    /// Multivector configuration
1570    #[serde(skip_serializing_if = "Option::is_none")]
1571    pub multivector_config: Option<StrictModeMultivectorConfigOutput>,
1572
1573    /// Sparse vector configuration
1574    #[serde(skip_serializing_if = "Option::is_none")]
1575    pub sparse_config: Option<StrictModeSparseConfigOutput>,
1576
1577    /// Max number of payload indexes in a collection
1578    #[serde(skip_serializing_if = "Option::is_none")]
1579    pub max_payload_index_count: Option<usize>,
1580
1581    /// Deprecated: use the node-wide quota config instead. Reject memory-consuming update operations when resident memory exceeds this percentage of total RAM (1-100)
1582    #[deprecated(
1583        since = "1.19.0",
1584        note = "memory is node-wide: use the global quota config instead. Removal planned for 1.21"
1585    )]
1586    #[serde(skip_serializing_if = "Option::is_none")]
1587    
1588    pub max_resident_memory_percent: Option<u8>,
1589}
1590
1591impl From<StrictModeConfig> for StrictModeConfigOutput {
1592    fn from(config: StrictModeConfig) -> Self {
1593        let StrictModeConfig {
1594            enabled,
1595            max_query_limit,
1596            max_timeout,
1597            unindexed_filtering_retrieve,
1598            unindexed_filtering_update,
1599            search_max_hnsw_ef,
1600            search_allow_exact,
1601            search_max_oversampling,
1602            upsert_max_batchsize,
1603            search_max_batchsize,
1604            max_collection_vector_size_bytes,
1605            read_rate_limit,
1606            write_rate_limit,
1607            max_collection_payload_size_bytes,
1608            max_points_count,
1609            filter_max_conditions,
1610            condition_max_size,
1611            multivector_config,
1612            sparse_config,
1613            max_payload_index_count,
1614            max_resident_memory_percent,
1615        } = config;
1616
1617        Self {
1618            enabled,
1619            max_query_limit,
1620            max_timeout,
1621            unindexed_filtering_retrieve,
1622            unindexed_filtering_update,
1623            search_max_hnsw_ef,
1624            search_allow_exact,
1625            search_max_oversampling,
1626            upsert_max_batchsize,
1627            search_max_batchsize,
1628            max_collection_vector_size_bytes,
1629            read_rate_limit,
1630            write_rate_limit,
1631            max_collection_payload_size_bytes,
1632            max_points_count,
1633            filter_max_conditions,
1634            condition_max_size,
1635            multivector_config: multivector_config.map(StrictModeMultivectorConfigOutput::from),
1636            sparse_config: sparse_config.map(StrictModeSparseConfigOutput::from),
1637            max_payload_index_count,
1638            max_resident_memory_percent,
1639        }
1640    }
1641}
1642
1643pub const DEFAULT_HNSW_EF_CONSTRUCT: usize = 100;
1644
1645impl Default for HnswConfig {
1646    fn default() -> Self {
1647        HnswConfig {
1648            m: 16,
1649            ef_construct: DEFAULT_HNSW_EF_CONSTRUCT,
1650            full_scan_threshold: DEFAULT_FULL_SCAN_THRESHOLD,
1651            max_indexing_threads: 0,
1652            on_disk: Some(false),
1653            memory: None,
1654            payload_m: None,
1655            inline_storage: None,
1656        }
1657    }
1658}
1659
1660impl Default for Indexes {
1661    fn default() -> Self {
1662        Indexes::Plain {}
1663    }
1664}
1665
1666/// Type of payload storage
1667#[derive( Debug, Deserialize, Serialize, JsonSchema, Copy, Clone, PartialEq, Eq)]
1668#[serde(tag = "type", content = "options", rename_all = "snake_case")]
1669pub enum PayloadStorageType {
1670    // Store payload on disk and in memory, read from memory if possible
1671    Mmap,
1672    // Store payload on disk and in memory, populate on load
1673    InRamMmap,
1674}
1675
1676#[cfg(any(test, feature = "testing"))]
1677#[allow(clippy::derivable_impls)]
1678impl Default for PayloadStorageType {
1679    fn default() -> Self {
1680        PayloadStorageType::Mmap
1681    }
1682}
1683
1684impl PayloadStorageType {
1685    /// Convert user-facing `on_disk_payload` (true = store on disk) to storage type.
1686    /// Returns `Mmap` or `InRamMmap`; for RocksDB-backed variants use collection config.
1687    pub fn from_on_disk_payload(on_disk: bool) -> Self {
1688        if on_disk { Self::Mmap } else { Self::InRamMmap }
1689    }
1690
1691    /// Convert memory placement to payload storage type.
1692    ///
1693    /// `Pinned` is not supported for payload storage and is rejected by API validation;
1694    /// it defensively maps to the closest supported placement.
1695    pub fn from_memory(memory: Memory) -> Self {
1696        match memory {
1697            Memory::Cold => Self::Mmap,
1698            Memory::Cached | Memory::Pinned => Self::InRamMmap,
1699        }
1700    }
1701
1702    /// Memory placement this storage type provides.
1703    pub fn memory(&self) -> Memory {
1704        match self {
1705            PayloadStorageType::Mmap => Memory::Cold,
1706            PayloadStorageType::InRamMmap => Memory::Cached,
1707        }
1708    }
1709
1710    pub fn is_on_disk(&self) -> bool {
1711        match self {
1712            PayloadStorageType::Mmap => true,
1713            PayloadStorageType::InRamMmap => false,
1714        }
1715    }
1716}
1717
1718#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema, )]
1719#[serde(rename_all = "snake_case")]
1720pub struct SegmentConfig {
1721    #[serde(default)]
1722    pub vector_data: HashMap<VectorNameBuf, VectorDataConfig>,
1723    #[serde(default)]
1724    #[serde(skip_serializing_if = "HashMap::is_empty")]
1725    pub sparse_vector_data: HashMap<VectorNameBuf, SparseVectorDataConfig>,
1726    /// Defines payload storage type
1727    pub payload_storage_type: PayloadStorageType,
1728}
1729
1730impl SegmentConfig {
1731    /// Helper to get vector specific quantization config.
1732    ///
1733    /// This grabs the quantization config for the given vector name if it exists.
1734    ///
1735    /// If no quantization is configured, `None` is returned.
1736    pub fn quantization_config(&self, vector_name: &VectorName) -> Option<&QuantizationConfig> {
1737        self.vector_data
1738            .get(vector_name)
1739            .and_then(|v| v.quantization_config.as_ref())
1740    }
1741
1742    /// Check if any vector storages are indexed
1743    pub fn is_any_vector_indexed(&self) -> bool {
1744        self.vector_data
1745            .values()
1746            .any(|config| config.index.is_indexed())
1747            || self
1748                .sparse_vector_data
1749                .values()
1750                .any(|config| config.is_indexed())
1751    }
1752
1753    /// Check if any vector storage is on-disk
1754    pub fn is_any_on_disk(&self) -> bool {
1755        self.vector_data
1756            .values()
1757            .any(|config| config.storage_type.is_on_disk())
1758            || self
1759                .sparse_vector_data
1760                .values()
1761                .any(|config| config.index.index_type.is_on_disk())
1762    }
1763
1764    pub fn is_appendable(&self) -> bool {
1765        self.vector_data
1766            .values()
1767            .map(|vector_config| vector_config.is_appendable())
1768            .chain(
1769                self.sparse_vector_data
1770                    .values()
1771                    .map(|sparse_vector_config| {
1772                        sparse_vector_config.index.index_type.is_appendable()
1773                    }),
1774            )
1775            .all(|v| v)
1776    }
1777
1778    pub fn check_compatible(&self, other: &Self) -> Result<(), String> {
1779        // Vector data have to be compatible between two segments.
1780        // Sparse vector data can be different, but a placeholder check is implemented to catch
1781        // and enforce compatibility check for future changes.
1782        // Payload storage type can be different.
1783
1784        // Assert segment config fields
1785        let Self {
1786            vector_data: _,
1787            sparse_vector_data: _,
1788            payload_storage_type: _,
1789        } = self;
1790
1791        check_vectors_map_compatible(
1792            &self.vector_data,
1793            &other.vector_data,
1794            VectorDataConfig::check_compatible,
1795        )?;
1796
1797        check_vectors_map_compatible(
1798            &self.sparse_vector_data,
1799            &other.sparse_vector_data,
1800            SparseVectorDataConfig::check_compatible,
1801        )?;
1802
1803        Ok(())
1804    }
1805}
1806
1807fn check_vectors_map_compatible<C, F>(
1808    this: &HashMap<String, C>,
1809    other: &HashMap<String, C>,
1810    check: F,
1811) -> Result<(), String>
1812where
1813    F: Fn(&C, &C) -> Result<(), String>,
1814{
1815    if this.len() != other.len() {
1816        let expected_keys: Vec<String> = this.keys().map(|k| format!("{k:?}")).collect();
1817        let actual_keys: Vec<String> = other.keys().map(|k| format!("{k:?}")).collect();
1818        return Err(format!(
1819            "Incompatible configs: expected vector storages with keys {expected_keys:?}, but got {actual_keys:?}"
1820        ));
1821    }
1822
1823    for (vector_name, config) in this {
1824        let Some(other_config) = other.get(vector_name) else {
1825            return Err(format!(
1826                "Incompatible configs: expected vector storage with key {vector_name:?} not found in other config"
1827            ));
1828        };
1829
1830        check(config, other_config)
1831            .map_err(|err| format!("Incompatible config for vector {vector_name:?}: {err}"))?;
1832    }
1833
1834    Ok(())
1835}
1836
1837/// Memory placement of a component's data.
1838///
1839/// Data is always persisted on disk regardless of this setting; it only controls
1840/// how the data is held in RAM.
1841///
1842/// Options:
1843///
1844/// * `Cold` - Data is not pre-loaded from disk to RAM. Preferred for rarely queried components or
1845///   components larger than RAM size. First request might be slow, but data is cached with
1846///   usage.
1847///
1848/// * `Cached` - Data is pre-loaded into disk-cache RAM on start. First request is fast, but data may be
1849///   evicted if there is not enough memory and some other component's data is used more
1850///   frequently.
1851///
1852/// * `Pinned` - Data is loaded in RAM and never evicted. First request is fast, but the component must
1853///   fit in RAM at all times. Recommended for frequently queried small components like
1854///   quantized vectors or primary indexes.
1855#[derive(
1856    Debug, Deserialize, Serialize, JsonSchema,  Eq, PartialEq, Copy, Clone, Hash,
1857)]
1858#[serde(rename_all = "snake_case")]
1859pub enum Memory {
1860    // Data is not pre-loaded from disk to RAM. Preferred for rarely queried components or
1861    // components larger than RAM size. First request might be slow, but data is cached with
1862    // usage.
1863    Cold,
1864    // Data is pre-loaded into disk-cache RAM on start. First request is fast, but data may be
1865    // evicted if there is not enough memory and some other component's data is used more
1866    // frequently.
1867    Cached,
1868    // Data is loaded in RAM and never evicted. First request is fast, but the component must
1869    // fit in RAM at all times. Recommended for frequently queried small components like
1870    // quantized vectors or primary indexes.
1871    Pinned,
1872}
1873
1874impl Memory {
1875    /// Convert legacy `on_disk`-style flag (true = store on disk) into the memory placement it
1876    /// used to mean: on-disk data is loaded lazily, in-RAM data is populated but evictable.
1877    pub fn from_on_disk(on_disk: bool) -> Self {
1878        if on_disk { Self::Cold } else { Self::Cached }
1879    }
1880
1881    /// Convert legacy `on_disk`-style flag for components whose in-RAM variant is a heap
1882    /// structure (payload field indexes, sparse vector index): on-disk data is loaded lazily,
1883    /// in-RAM data is fully materialized on heap and never evicted.
1884    pub fn from_on_disk_heap(on_disk: bool) -> Self {
1885        if on_disk { Self::Cold } else { Self::Pinned }
1886    }
1887
1888    /// Whether this placement corresponds to `on_disk = true` in the legacy options.
1889    pub fn is_on_disk(self) -> bool {
1890        match self {
1891            Self::Cold => true,
1892            Self::Cached | Self::Pinned => false,
1893        }
1894    }
1895
1896    /// Whether data is left on disk and paged in on demand, rather than held in RAM. Reads of
1897    /// a cold component hit the disk, which is what makes an async IO backend worth using.
1898    pub fn is_cold(self) -> bool {
1899        match self {
1900            Self::Cold => true,
1901            Self::Cached | Self::Pinned => false,
1902        }
1903    }
1904
1905    /// Whether this placement is backed by a heap structure rather than an mmap file, for
1906    /// components that have both heap and mmap variants (payload field indexes, sparse index).
1907    pub fn is_heap(self) -> bool {
1908        match self {
1909            Self::Pinned => true,
1910            Self::Cold | Self::Cached => false,
1911        }
1912    }
1913
1914    /// Whether the backing mmap should be populated on open: the cached placement primes the
1915    /// page cache, and the pinned placement reads the whole file into heap right after anyway.
1916    pub fn populate_on_open(self) -> bool {
1917        match self {
1918            Self::Cold => false,
1919            Self::Cached | Self::Pinned => true,
1920        }
1921    }
1922
1923    /// Resolve the effective memory placement from the new explicit `memory` parameter and a
1924    /// legacy parameter translated into placement terms. The explicit parameter always wins.
1925    pub fn resolve(memory: Option<Self>, legacy: Option<Self>) -> Option<Self> {
1926        memory.or(legacy)
1927    }
1928
1929    /// Same as [`Memory::resolve`], but logs a warning if both parameters are set and disagree.
1930    ///
1931    /// Use at configuration resolution points (e.g. the optimizer); use the silent
1932    /// [`Memory::resolve`] in repeatedly called accessors.
1933    pub fn resolve_or_warn(
1934        memory: Option<Self>,
1935        legacy: Option<Self>,
1936        component: &dyn std::fmt::Display,
1937    ) -> Option<Self> {
1938        if let (Some(memory), Some(legacy)) = (memory, legacy)
1939            && memory != legacy
1940        {
1941            log::warn!(
1942                "Component {component} has both `memory={memory:?}` and a deprecated storage \
1943                 placement parameter implying {legacy:?} configured; using `memory={memory:?}`"
1944            );
1945        }
1946        Self::resolve(memory, legacy)
1947    }
1948
1949    /// Apply the node-wide low-memory mode to this placement at load time.
1950    ///
1951    /// Mirrors the legacy behavior: `NoResident` downgrades pinned components to their on-disk
1952    /// variants (`prefer_disk`), `NoPopulate` additionally skips cache population, so every
1953    /// placement degrades to [`Memory::Cold`].
1954    ///
1955    /// Never affects the persisted configuration.
1956    pub fn clamp_to_low_memory(self) -> Self {
1957        let mode = crate::common::low_memory::low_memory_mode();
1958        if mode.skip_populate() {
1959            return Self::Cold;
1960        }
1961        if mode.prefer_disk() {
1962            return match self {
1963                Self::Pinned => Self::Cold,
1964                Self::Cold | Self::Cached => self,
1965            };
1966        }
1967        self
1968    }
1969
1970    /// Apply a request-specific populate override (from a
1971    /// [`LoadProfile`](crate::data_types::load_profile::LoadProfile)) to this placement at
1972    /// load time: a cold-ward override parks any placement cold — like `clamp_to_low_memory`,
1973    /// even a pinned one, whose components all support a lazy on-disk open over the same
1974    /// files — and a warm-ward one primes the page cache of an otherwise cold placement
1975    /// (never materializing on heap: `Pinned` only ever comes from the config).
1976    ///
1977    /// Never affects the persisted configuration.
1978    pub fn with_populate_override(
1979        self,
1980        populate_override: Option<crate::common::universal_io::Populate>,
1981    ) -> Self {
1982        use crate::common::universal_io::Populate;
1983
1984        let Some(populate) = populate_override else {
1985            return self;
1986        };
1987        match populate {
1988            Populate::No | Populate::Auto | Populate::Partial(_) => Self::Cold,
1989            Populate::Blocking | Populate::PreferBackground => match self {
1990                Self::Cold | Self::Cached => Self::Cached,
1991                Self::Pinned => Self::Pinned,
1992            },
1993        }
1994    }
1995}
1996
1997/// Storage types for vectors
1998#[derive(Debug, Deserialize, Serialize, JsonSchema,  Eq, PartialEq, Copy, Clone)]
1999pub enum VectorStorageType {
2000    /// Storage in memory (RAM)
2001    ///
2002    /// Will be very fast at the cost of consuming a lot of memory.
2003    Memory,
2004    /// Storage in mmap file, not appendable
2005    ///
2006    /// Search performance is defined by disk speed and the fraction of vectors that fit in memory.
2007    Mmap,
2008    /// Storage in chunked mmap files, appendable
2009    ///
2010    /// Search performance is defined by disk speed and the fraction of vectors that fit in memory.
2011    ChunkedMmap,
2012    /// Same as `ChunkedMmap`, but vectors are forced to be locked in RAM
2013    /// In this way we avoid cold requests to disk, but risk to run out of memory
2014    ///
2015    /// Designed as a replacement for `Memory`, which doesn't depend on RocksDB
2016    InRamChunkedMmap,
2017    /// Storage in a single mmap file, not appendable
2018    /// Pre-fetched into RAM on load
2019    InRamMmap,
2020    /// Placeholder storage: contains no data, all vectors reported as deleted.
2021    /// Used for newly created named vectors on immutable segments.
2022    /// No files on disk, reconstructed from config on load.
2023    Empty,
2024}
2025
2026#[cfg(any(test, feature = "testing"))]
2027#[allow(clippy::derivable_impls)]
2028impl Default for VectorStorageType {
2029    fn default() -> Self {
2030        VectorStorageType::InRamChunkedMmap
2031    }
2032}
2033
2034/// Storage types for vectors
2035#[derive(
2036    Default, Debug, Deserialize, Serialize, JsonSchema,  Eq, PartialEq, Copy, Clone, Hash,
2037)]
2038#[serde(rename_all = "snake_case")]
2039pub enum VectorStorageDatatype {
2040    // Single-precision floating point
2041    #[default]
2042    Float32,
2043    // Half-precision floating point
2044    Float16,
2045    // Unsigned 8-bit integer
2046    Uint8,
2047    // TurboQuant 4-bit compressed storage
2048    Turbo4,
2049}
2050
2051#[derive(
2052    Debug, Default, Deserialize, Serialize, JsonSchema,  Eq, PartialEq, Copy, Clone, Hash,
2053)]
2054#[serde(rename_all = "snake_case")]
2055pub struct MultiVectorConfig {
2056    /// How to compare multivector points
2057    pub comparator: MultiVectorComparator,
2058}
2059
2060impl MultiVectorConfig {
2061    fn check_compatible(&self, other: &Self) -> Result<(), String> {
2062        // Assert multi-vector config fields
2063        let Self { comparator } = self;
2064
2065        if *comparator != other.comparator {
2066            return Err(format!(
2067                "Incompatible configs: expected multi-vector comparator {comparator:?}, but got {other_comparator:?}",
2068                other_comparator = other.comparator
2069            ));
2070        }
2071
2072        Ok(())
2073    }
2074}
2075
2076#[derive(
2077    Debug, Default, Deserialize, Serialize, JsonSchema,  Eq, PartialEq, Copy, Clone, Hash,
2078)]
2079#[serde(rename_all = "snake_case")]
2080pub enum MultiVectorComparator {
2081    #[default]
2082    MaxSim,
2083}
2084
2085impl VectorStorageType {
2086    /// Convert user-facing `on_disk` (true = store on disk) to appendable vector storage type.
2087    /// Returns `ChunkedMmap` or `InRamChunkedMmap`.
2088    pub fn from_on_disk(on_disk: bool) -> Self {
2089        if on_disk {
2090            Self::ChunkedMmap
2091        } else {
2092            Self::InRamChunkedMmap
2093        }
2094    }
2095
2096    /// Convert memory placement to appendable vector storage type.
2097    ///
2098    /// `Pinned` is not supported for dense vector storage and is rejected by API validation;
2099    /// it defensively maps to the closest supported placement.
2100    pub fn appendable_from_memory(memory: Memory) -> Self {
2101        match memory {
2102            Memory::Cold => Self::ChunkedMmap,
2103            Memory::Cached | Memory::Pinned => Self::InRamChunkedMmap,
2104        }
2105    }
2106
2107    /// Convert memory placement to non-appendable single-file vector storage type.
2108    ///
2109    /// `Pinned` is not supported for dense vector storage and is rejected by API validation;
2110    /// it defensively maps to the closest supported placement.
2111    pub fn immutable_from_memory(memory: Memory) -> Self {
2112        match memory {
2113            Memory::Cold => Self::Mmap,
2114            Memory::Cached | Memory::Pinned => Self::InRamMmap,
2115        }
2116    }
2117
2118    /// Memory placement this storage type provides.
2119    pub fn memory(&self) -> Memory {
2120        match self {
2121            // Legacy true-heap storage: pinned by construction
2122            Self::Memory => Memory::Pinned,
2123            Self::Mmap | Self::ChunkedMmap => Memory::Cold,
2124            Self::InRamChunkedMmap | Self::InRamMmap => Memory::Cached,
2125            // Empty storage has no data; report the safe placement, consistent with
2126            // `is_on_disk`
2127            Self::Empty => Memory::Cold,
2128        }
2129    }
2130
2131    /// Whether this storage type is a mmap on disk
2132    pub fn is_on_disk(&self) -> bool {
2133        match self {
2134            Self::Memory | Self::InRamChunkedMmap | Self::InRamMmap => false,
2135            Self::Mmap | Self::ChunkedMmap => true,
2136            // Empty storage has no actual data; report based on what the
2137            // runtime EmptyDenseVectorStorage was configured with.
2138            // This fallback returns true to be safe, but callers that need
2139            // the real on-disk status should check the storage instance.
2140            Self::Empty => true,
2141        }
2142    }
2143
2144    /// Whether this is a placeholder empty storage type
2145    pub fn is_empty(&self) -> bool {
2146        matches!(self, Self::Empty)
2147    }
2148}
2149
2150/// Config of single vector data storage
2151#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema, )]
2152#[serde(rename_all = "snake_case")]
2153pub struct VectorDataConfig {
2154    /// Size/dimensionality of the vectors used
2155    pub size: usize,
2156    /// Type of distance function used for measuring distance between vectors
2157    pub distance: Distance,
2158    /// Type of storage this vector uses
2159    pub storage_type: VectorStorageType,
2160    /// Type of index used for search
2161    pub index: Indexes,
2162    /// Vector specific quantization config that overrides collection config
2163    pub quantization_config: Option<QuantizationConfig>,
2164    /// Vector specific configuration to enable multiple vectors per point
2165    #[serde(default, skip_serializing_if = "Option::is_none")]
2166    pub multivector_config: Option<MultiVectorConfig>,
2167    /// Vector specific configuration to set specific storage element type
2168    #[serde(default, skip_serializing_if = "Option::is_none")]
2169    pub datatype: Option<VectorStorageDatatype>,
2170}
2171
2172impl VectorDataConfig {
2173    /// Whether this vector data can be appended to
2174    ///
2175    /// This requires an index and storage type that both support appending.
2176    pub fn is_appendable(&self) -> bool {
2177        let is_index_appendable = match self.index {
2178            Indexes::Plain {} => true,
2179            Indexes::Hnsw(_) => false,
2180        };
2181        let is_storage_appendable = match self.storage_type {
2182            VectorStorageType::Memory => true,
2183            VectorStorageType::Mmap => false,
2184            VectorStorageType::ChunkedMmap => true,
2185            VectorStorageType::InRamChunkedMmap => true,
2186            VectorStorageType::InRamMmap => false,
2187            VectorStorageType::Empty => false,
2188        };
2189        is_index_appendable && is_storage_appendable
2190    }
2191
2192    pub fn check_compatible(&self, other: &Self) -> Result<(), String> {
2193        // Size and distance have to be the same for both segments.
2194        // Storage type, index and quantization config can be different.
2195        //
2196        // Assert vector data config fields
2197        let Self {
2198            size,
2199            distance,
2200            storage_type: _,
2201            index: _,
2202            quantization_config: _,
2203            multivector_config,
2204            datatype,
2205        } = self;
2206
2207        if *size != other.size {
2208            return Err(format!(
2209                "Incompatible configs: expected vector size {size}, but got {other_size}",
2210                other_size = other.size
2211            ));
2212        }
2213
2214        if *distance != other.distance {
2215            return Err(format!(
2216                "Incompatible configs: expected distance {distance:?}, but got {other_distance:?}",
2217                other_distance = other.distance
2218            ));
2219        }
2220
2221        let left_datatype = datatype.unwrap_or(VectorStorageDatatype::Float32);
2222        let right_datatype = other.datatype.unwrap_or(VectorStorageDatatype::Float32);
2223        if left_datatype != right_datatype {
2224            return Err(format!(
2225                "Incompatible configs: expected vector storage datatype {left_datatype:?}, but got {right_datatype:?}",
2226            ));
2227        }
2228
2229        match (multivector_config, &other.multivector_config) {
2230            (None, None) => {}
2231            (Some(this), Some(other)) => {
2232                MultiVectorConfig::check_compatible(this, other)?;
2233            }
2234            _ => {
2235                return Err(format!(
2236                    "Incompatible configs: expected multivector config {this_multivector_config:?}, but got {other_multivector_config:?}",
2237                    this_multivector_config = multivector_config,
2238                    other_multivector_config = other.multivector_config
2239                ));
2240            }
2241        }
2242        Ok(())
2243    }
2244}
2245
2246#[derive(
2247    Copy, Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, JsonSchema, 
2248)]
2249#[serde(rename_all = "snake_case")]
2250pub enum SparseVectorStorageType {
2251    /// Storage in memory maps (gridstore storage)
2252    #[default]
2253    Mmap,
2254    /// Placeholder storage: contains no data, all vectors reported as deleted.
2255    /// Used for newly created sparse named vectors on immutable segments.
2256    Empty,
2257}
2258
2259impl SparseVectorStorageType {
2260    /// Whether this storage type is a mmap on disk
2261    pub fn is_on_disk(&self) -> bool {
2262        match self {
2263            // Both options are on disk, but we keep it explicit for the case if someone adds a new
2264            // storage type in the future
2265            Self::Mmap | Self::Empty => true,
2266        }
2267    }
2268}
2269
2270/// Config of single sparse vector data storage
2271#[derive(
2272    Copy, Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema, Validate, 
2273)]
2274#[serde(rename_all = "snake_case")]
2275pub struct SparseVectorDataConfig {
2276    /// Sparse inverted index config
2277    pub index: SparseIndexConfig,
2278
2279    /// Type of storage this sparse vector uses
2280    #[serde(default = "default_sparse_vector_storage_type_when_not_in_config")]
2281    pub storage_type: SparseVectorStorageType,
2282
2283    /// Configures addition value modifications for sparse vectors.
2284    /// Default: none
2285    #[serde(default, skip_serializing_if = "Option::is_none")]
2286    pub modifier: Option<Modifier>,
2287}
2288
2289/// If the storage type is not in config, it means it is the OnDisk variant
2290fn default_sparse_vector_storage_type_when_not_in_config() -> SparseVectorStorageType {
2291    SparseVectorStorageType::default()
2292}
2293
2294impl SparseVectorDataConfig {
2295    pub fn is_indexed(&self) -> bool {
2296        true
2297    }
2298
2299    pub fn check_compatible(&self, other: &Self) -> Result<(), String> {
2300        // Both index and storage type can be different for two segments to be compatible
2301
2302        // Assert sparse vector config fields
2303        let Self {
2304            index: _,
2305            storage_type: _,
2306            modifier,
2307        } = self;
2308
2309        if modifier != &other.modifier {
2310            return Err(format!(
2311                "Incompatible configs: expected sparse vector modifier {modifier:?}, but got {other_modifier:?}",
2312                other_modifier = other.modifier
2313            ));
2314        }
2315
2316        Ok(())
2317    }
2318}
2319
2320/// Default value based on experiments and observations
2321pub const DEFAULT_FULL_SCAN_THRESHOLD: usize = 10_000;
2322
2323pub const DEFAULT_SPARSE_FULL_SCAN_THRESHOLD: usize = 5_000;
2324
2325/// Persistable state of segment configuration
2326#[derive(Debug, Deserialize, Serialize, Clone)]
2327#[serde(rename_all = "snake_case")]
2328pub struct SegmentState {
2329    #[serde(default)]
2330    pub initial_version: Option<SeqNumberType>,
2331    pub version: Option<SeqNumberType>,
2332    pub config: SegmentConfig,
2333}
2334
2335pub type RawGeoPoint = (f64, f64);
2336
2337/// Geo point payload schema
2338#[derive(
2339    Debug,
2340    Deserialize,
2341    Serialize,
2342    JsonSchema,
2343    Clone,
2344    Copy,
2345    PartialEq,
2346    Eq,
2347    Hash,
2348    Default,
2349    PartialOrd,
2350    Ord,
2351    Pod,
2352    Zeroable,
2353)]
2354#[serde(try_from = "GeoPointShadow")]
2355#[repr(C)]
2356pub struct GeoPoint {
2357    pub lon: OrderedFloat<f64>,
2358    pub lat: OrderedFloat<f64>,
2359}
2360
2361/// Ordered sequence of GeoPoints representing the line
2362#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
2363pub struct GeoLineString {
2364    pub points: Vec<GeoPoint>,
2365}
2366
2367#[derive(Deserialize)]
2368struct GeoPointShadow {
2369    pub lon: f64,
2370    pub lat: f64,
2371}
2372
2373#[derive(Debug)]
2374pub struct GeoPointValidationError {
2375    pub lon: f64,
2376    pub lat: f64,
2377}
2378
2379// The error type has to implement Display
2380impl std::fmt::Display for GeoPointValidationError {
2381    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2382        write!(
2383            formatter,
2384            "Wrong format of GeoPoint payload: expected `lat` = {} within [-90;90] and `lon` = {} within [-180;180]",
2385            self.lat, self.lon,
2386        )
2387    }
2388}
2389
2390impl GeoPoint {
2391    pub fn validate(lon: f64, lat: f64) -> Result<(), GeoPointValidationError> {
2392        let max_lon = 180f64;
2393        let min_lon = -180f64;
2394        let max_lat = 90f64;
2395        let min_lat = -90f64;
2396
2397        if !(min_lon..=max_lon).contains(&lon) || !(min_lat..=max_lat).contains(&lat) {
2398            return Err(GeoPointValidationError { lon, lat });
2399        }
2400        Ok(())
2401    }
2402
2403    pub fn new(lon: f64, lat: f64) -> Result<Self, GeoPointValidationError> {
2404        Self::validate(lon, lat)?;
2405        Ok(Self::new_unchecked(lon, lat))
2406    }
2407
2408    pub const fn new_unchecked(lon: f64, lat: f64) -> Self {
2409        GeoPoint {
2410            lon: OrderedFloat(lon),
2411            lat: OrderedFloat(lat),
2412        }
2413    }
2414}
2415
2416impl TryFrom<GeoPointShadow> for GeoPoint {
2417    type Error = GeoPointValidationError;
2418
2419    fn try_from(value: GeoPointShadow) -> Result<Self, Self::Error> {
2420        let GeoPointShadow { lon, lat } = value;
2421        GeoPoint::validate(lon, lat)?;
2422
2423        Ok(Self::new_unchecked(lon, lat))
2424    }
2425}
2426
2427impl From<GeoPoint> for geo::Point {
2428    fn from(
2429        GeoPoint {
2430            lon: OrderedFloat(lon),
2431            lat: OrderedFloat(lat),
2432        }: GeoPoint,
2433    ) -> Self {
2434        Self::new(lon, lat)
2435    }
2436}
2437
2438impl From<RawGeoPoint> for GeoPoint {
2439    fn from((lon, lat): RawGeoPoint) -> Self {
2440        GeoPoint::new(lon, lat).expect("invalid GeoPoint coordinates")
2441    }
2442}
2443
2444impl From<GeoPoint> for RawGeoPoint {
2445    fn from(geo_point: GeoPoint) -> Self {
2446        (geo_point.lon.0, geo_point.lat.0)
2447    }
2448}
2449
2450pub trait PayloadContainer {
2451    /// Return value from payload by path.
2452    /// If value is not present in the payload, returns empty vector.
2453    fn get_value(&self, path: &JsonPath) -> MultiValue<&Value>;
2454
2455    fn get_value_cloned(&self, path: &JsonPath) -> MultiValue<Value> {
2456        self.get_value(path).into_iter().cloned().collect()
2457    }
2458}
2459
2460/// Construct a [`Payload`] value from a JSON literal.
2461///
2462/// Similar to [`serde_json::json!`] but only allows objects (aka maps).
2463macro_rules! payload_json {
2464    ($($tt:tt)*) => {
2465        match ::serde_json::json!( { $($tt)* } ) {
2466            ::serde_json::Value::Object(map) => $crate::segment::types::Payload(map),
2467            _ => unreachable!(),
2468        }
2469    };
2470}
2471
2472#[allow(clippy::unnecessary_wraps)] // Used as schemars example
2473fn payload_example() -> Option<Payload> {
2474    Some(payload_json! {
2475        "city": "London",
2476        "color": "green",
2477    })
2478}
2479
2480#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, JsonSchema, Hash)]
2481#[schemars(example = "payload_example")]
2482pub struct Payload(pub Map<String, Value>);
2483
2484impl Payload {
2485    pub fn merge(&mut self, value: &Payload) {
2486        utils::merge_map(&mut self.0, &value.0)
2487    }
2488
2489    pub fn merge_by_key(&mut self, value: &Payload, key: &JsonPath) {
2490        JsonPath::value_set(Some(key), &mut self.0, &value.0);
2491    }
2492
2493    pub fn remove(&mut self, path: &JsonPath) -> Vec<Value> {
2494        path.value_remove(&mut self.0).to_vec()
2495    }
2496
2497    pub fn len(&self) -> usize {
2498        self.0.len()
2499    }
2500
2501    pub fn is_empty(&self) -> bool {
2502        self.0.is_empty()
2503    }
2504
2505    pub fn contains_key(&self, key: &str) -> bool {
2506        self.0.contains_key(key)
2507    }
2508
2509    pub fn keys(&self) -> impl Iterator<Item = &String> {
2510        self.0.keys()
2511    }
2512}
2513
2514impl PayloadContainer for Map<String, Value> {
2515    fn get_value(&self, path: &JsonPath) -> MultiValue<&Value> {
2516        path.value_get(self)
2517    }
2518}
2519
2520impl PayloadContainer for Payload {
2521    fn get_value(&self, path: &JsonPath) -> MultiValue<&Value> {
2522        path.value_get(&self.0)
2523    }
2524}
2525
2526impl PayloadContainer for OwnedPayloadRef<'_> {
2527    fn get_value(&self, path: &JsonPath) -> MultiValue<&Value> {
2528        path.value_get(self.as_ref())
2529    }
2530}
2531
2532impl Default for Payload {
2533    fn default() -> Self {
2534        Payload(Map::new())
2535    }
2536}
2537
2538impl IntoIterator for Payload {
2539    type Item = (String, Value);
2540    type IntoIter = serde_json::map::IntoIter;
2541
2542    fn into_iter(self) -> serde_json::map::IntoIter {
2543        self.0.into_iter()
2544    }
2545}
2546
2547impl From<Map<String, Value>> for Payload {
2548    fn from(value: serde_json::Map<String, Value>) -> Self {
2549        Payload(value)
2550    }
2551}
2552
2553#[derive(Clone, Debug)]
2554pub enum OwnedPayloadRef<'a> {
2555    Ref(&'a Map<String, Value>),
2556    Owned(Rc<Map<String, Value>>),
2557}
2558
2559impl Deref for OwnedPayloadRef<'_> {
2560    type Target = Map<String, Value>;
2561
2562    fn deref(&self) -> &Self::Target {
2563        match self {
2564            OwnedPayloadRef::Ref(reference) => reference,
2565            OwnedPayloadRef::Owned(owned) => owned.deref(),
2566        }
2567    }
2568}
2569
2570impl AsRef<Map<String, Value>> for OwnedPayloadRef<'_> {
2571    fn as_ref(&self) -> &Map<String, Value> {
2572        match self {
2573            OwnedPayloadRef::Ref(reference) => reference,
2574            OwnedPayloadRef::Owned(owned) => owned.deref(),
2575        }
2576    }
2577}
2578
2579impl From<Payload> for OwnedPayloadRef<'_> {
2580    fn from(payload: Payload) -> Self {
2581        OwnedPayloadRef::Owned(Rc::new(payload.0))
2582    }
2583}
2584
2585impl From<Map<String, Value>> for OwnedPayloadRef<'_> {
2586    fn from(payload: Map<String, Value>) -> Self {
2587        OwnedPayloadRef::Owned(Rc::new(payload))
2588    }
2589}
2590
2591impl<'a> From<&'a Payload> for OwnedPayloadRef<'a> {
2592    fn from(payload: &'a Payload) -> Self {
2593        OwnedPayloadRef::Ref(&payload.0)
2594    }
2595}
2596
2597impl<'a> From<&'a Map<String, Value>> for OwnedPayloadRef<'a> {
2598    fn from(payload: &'a Map<String, Value>) -> Self {
2599        OwnedPayloadRef::Ref(payload)
2600    }
2601}
2602
2603/// All possible names of payload types
2604#[derive(
2605    Debug, Deserialize, Serialize, JsonSchema,  Clone, Copy, PartialEq, Hash, Eq, EnumIter,
2606)]
2607#[serde(rename_all = "snake_case")]
2608pub enum PayloadSchemaType {
2609    Keyword,
2610    Integer,
2611    Float,
2612    Geo,
2613    Text,
2614    Bool,
2615    Datetime,
2616    Uuid,
2617}
2618
2619impl PayloadSchemaType {
2620    /// Human-readable type name
2621    pub fn name(&self) -> &'static str {
2622        serde_variant::to_variant_name(&self).unwrap_or("unknown")
2623    }
2624
2625    pub fn expand(&self) -> PayloadSchemaParams {
2626        match self {
2627            Self::Keyword => PayloadSchemaParams::Keyword(KeywordIndexParams::default()),
2628            Self::Integer => PayloadSchemaParams::Integer(IntegerIndexParams::default()),
2629            Self::Float => PayloadSchemaParams::Float(FloatIndexParams::default()),
2630            Self::Geo => PayloadSchemaParams::Geo(GeoIndexParams::default()),
2631            Self::Text => PayloadSchemaParams::Text(TextIndexParams::default()),
2632            Self::Bool => PayloadSchemaParams::Bool(BoolIndexParams::default()),
2633            Self::Datetime => PayloadSchemaParams::Datetime(DatetimeIndexParams::default()),
2634            Self::Uuid => PayloadSchemaParams::Uuid(UuidIndexParams::default()),
2635        }
2636    }
2637}
2638
2639/// Payload type with parameters
2640#[derive(Debug, Deserialize, Serialize, JsonSchema,  Clone, PartialEq, Hash, Eq)]
2641#[serde(untagged, rename_all = "snake_case")]
2642
2643pub enum PayloadSchemaParams {
2644    Keyword(KeywordIndexParams),
2645    Integer(IntegerIndexParams),
2646    Float(FloatIndexParams),
2647    Geo(GeoIndexParams),
2648    Text(TextIndexParams),
2649    Bool(BoolIndexParams),
2650    Datetime(DatetimeIndexParams),
2651    Uuid(UuidIndexParams),
2652}
2653
2654impl PayloadSchemaParams {
2655    /// Human-readable type name
2656    pub fn name(&self) -> &'static str {
2657        self.kind().name()
2658    }
2659
2660    pub fn kind(&self) -> PayloadSchemaType {
2661        match self {
2662            PayloadSchemaParams::Keyword(_) => PayloadSchemaType::Keyword,
2663            PayloadSchemaParams::Integer(_) => PayloadSchemaType::Integer,
2664            PayloadSchemaParams::Float(_) => PayloadSchemaType::Float,
2665            PayloadSchemaParams::Geo(_) => PayloadSchemaType::Geo,
2666            PayloadSchemaParams::Text(_) => PayloadSchemaType::Text,
2667            PayloadSchemaParams::Bool(_) => PayloadSchemaType::Bool,
2668            PayloadSchemaParams::Datetime(_) => PayloadSchemaType::Datetime,
2669            PayloadSchemaParams::Uuid(_) => PayloadSchemaType::Uuid,
2670        }
2671    }
2672
2673    pub fn tenant_optimization(&self) -> bool {
2674        match self {
2675            PayloadSchemaParams::Keyword(keyword) => keyword.is_tenant.unwrap_or_default(),
2676            PayloadSchemaParams::Integer(integer) => integer.is_principal.unwrap_or_default(),
2677            PayloadSchemaParams::Float(float) => float.is_principal.unwrap_or_default(),
2678            PayloadSchemaParams::Datetime(datetime) => datetime.is_principal.unwrap_or_default(),
2679            PayloadSchemaParams::Uuid(uuid) => uuid.is_tenant.unwrap_or_default(),
2680            PayloadSchemaParams::Geo(_)
2681            | PayloadSchemaParams::Text(_)
2682            | PayloadSchemaParams::Bool(_) => false,
2683        }
2684    }
2685
2686    pub fn is_on_disk(&self) -> bool {
2687        self.memory_placement().is_on_disk()
2688    }
2689
2690    /// Effective memory placement of the field index, resolving the new `memory` parameter
2691    /// against the deprecated `on_disk` flag.
2692    ///
2693    /// Defaults to [`Memory::Pinned`]: the legacy in-RAM field indexes are heap structures.
2694    pub fn memory_placement(&self) -> Memory {
2695        let (memory, on_disk) = match self {
2696            PayloadSchemaParams::Keyword(i) => (i.memory, i.on_disk),
2697            PayloadSchemaParams::Integer(i) => (i.memory, i.on_disk),
2698            PayloadSchemaParams::Float(i) => (i.memory, i.on_disk),
2699            PayloadSchemaParams::Datetime(i) => (i.memory, i.on_disk),
2700            PayloadSchemaParams::Uuid(i) => (i.memory, i.on_disk),
2701            PayloadSchemaParams::Text(i) => (i.memory, i.on_disk),
2702            PayloadSchemaParams::Geo(i) => (i.memory, i.on_disk),
2703            PayloadSchemaParams::Bool(i) => (i.memory, i.on_disk),
2704        };
2705        Memory::resolve(memory, on_disk.map(Memory::from_on_disk_heap)).unwrap_or(Memory::Pinned)
2706    }
2707
2708    pub fn enable_hnsw(&self) -> bool {
2709        match self {
2710            PayloadSchemaParams::Keyword(params) => params.enable_hnsw.unwrap_or(true),
2711            PayloadSchemaParams::Integer(params) => params.enable_hnsw.unwrap_or(true),
2712            PayloadSchemaParams::Float(params) => params.enable_hnsw.unwrap_or(true),
2713            PayloadSchemaParams::Datetime(params) => params.enable_hnsw.unwrap_or(true),
2714            PayloadSchemaParams::Uuid(params) => params.enable_hnsw.unwrap_or(true),
2715            PayloadSchemaParams::Text(params) => params.enable_hnsw.unwrap_or(true),
2716            PayloadSchemaParams::Geo(params) => params.enable_hnsw.unwrap_or(true),
2717            PayloadSchemaParams::Bool(params) => params.enable_hnsw.unwrap_or(true),
2718        }
2719    }
2720}
2721
2722impl Validate for PayloadSchemaParams {
2723    fn validate(&self) -> Result<(), ValidationErrors> {
2724        match self {
2725            PayloadSchemaParams::Keyword(_) => Ok(()),
2726            PayloadSchemaParams::Integer(integer_index_params) => integer_index_params.validate(),
2727            PayloadSchemaParams::Float(_) => Ok(()),
2728            PayloadSchemaParams::Geo(_) => Ok(()),
2729            PayloadSchemaParams::Text(_) => Ok(()),
2730            PayloadSchemaParams::Bool(_) => Ok(()),
2731            PayloadSchemaParams::Datetime(_) => Ok(()),
2732            PayloadSchemaParams::Uuid(_) => Ok(()),
2733        }
2734    }
2735}
2736
2737#[derive(Clone, Debug, Eq, Deserialize, Serialize, JsonSchema)]
2738#[serde(untagged, rename_all = "snake_case")]
2739pub enum PayloadFieldSchema {
2740    FieldType(PayloadSchemaType),
2741    FieldParams(PayloadSchemaParams),
2742}
2743
2744impl PartialEq for PayloadFieldSchema {
2745    fn eq(&self, other: &Self) -> bool {
2746        match (self, other) {
2747            (Self::FieldType(this), Self::FieldType(other)) => this == other,
2748            (Self::FieldParams(this), Self::FieldParams(other)) => this == other,
2749            (Self::FieldType(this), Self::FieldParams(other)) => &this.expand() == other,
2750            (Self::FieldParams(this), Self::FieldType(other)) => this == &other.expand(),
2751        }
2752    }
2753}
2754
2755impl hash::Hash for PayloadFieldSchema {
2756    fn hash<H: hash::Hasher>(&self, state: &mut H) {
2757        match self {
2758            PayloadFieldSchema::FieldType(default) => default.expand().hash(state),
2759            PayloadFieldSchema::FieldParams(params) => params.hash(state),
2760        }
2761    }
2762}
2763
2764impl Validate for PayloadFieldSchema {
2765    fn validate(&self) -> Result<(), ValidationErrors> {
2766        match self {
2767            PayloadFieldSchema::FieldType(_) => Ok(()), // nothing to validate
2768            PayloadFieldSchema::FieldParams(payload_schema_params) => {
2769                payload_schema_params.validate()
2770            }
2771        }
2772    }
2773}
2774
2775impl Display for PayloadFieldSchema {
2776    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2777        match self {
2778            PayloadFieldSchema::FieldType(t) => write!(f, "{}", t.name()),
2779            PayloadFieldSchema::FieldParams(params) => match params {
2780                PayloadSchemaParams::Float(_)
2781                | PayloadSchemaParams::Geo(_)
2782                | PayloadSchemaParams::Bool(_)
2783                | PayloadSchemaParams::Datetime(_)
2784                | PayloadSchemaParams::Uuid(_) => write!(f, "{}", params.name()),
2785                PayloadSchemaParams::Keyword(keyword_params) => {
2786                    if keyword_params.prefix.unwrap_or_default() {
2787                        write!(f, "keyword (with prefix: true)")
2788                    } else {
2789                        write!(f, "keyword")
2790                    }
2791                }
2792                PayloadSchemaParams::Integer(integer_params) => {
2793                    let range = integer_params.range.unwrap_or(true);
2794                    let lookup = integer_params.lookup.unwrap_or(true);
2795                    if range && lookup {
2796                        write!(f, "integer")
2797                    } else {
2798                        write!(f, "integer (with range: {range}, lookup: {lookup})")
2799                    }
2800                }
2801                PayloadSchemaParams::Text(text_params) => {
2802                    if text_params.phrase_matching.unwrap_or_default() {
2803                        write!(f, "text (with phrase_matching: true)")
2804                    } else {
2805                        write!(f, "text")
2806                    }
2807                }
2808            },
2809        }
2810    }
2811}
2812
2813impl TryFrom<&PayloadFieldSchema> for TextIndexParams {
2814    type Error = OperationError;
2815
2816    /// Extracts the full-text tokenizer params from a payload schema — used by
2817    /// the read-only full-text index `open`, the only index whose read
2818    /// behavior depends on its build-time config. Errors if the schema is not
2819    /// a text index.
2820    fn try_from(schema: &PayloadFieldSchema) -> Result<Self, Self::Error> {
2821        let expanded = schema.expand();
2822        let PayloadSchemaParams::Text(config) = expanded.as_ref() else {
2823            return Err(OperationError::service_error(
2824                "expected a text payload schema for a full-text index",
2825            ));
2826        };
2827        Ok(config.clone())
2828    }
2829}
2830
2831impl PayloadFieldSchema {
2832    pub fn expand(&self) -> Cow<'_, PayloadSchemaParams> {
2833        match self {
2834            PayloadFieldSchema::FieldType(t) => Cow::Owned(t.expand()),
2835            PayloadFieldSchema::FieldParams(p) => Cow::Borrowed(p),
2836        }
2837    }
2838
2839    /// Human-readable type name
2840    pub fn name(&self) -> &'static str {
2841        match self {
2842            PayloadFieldSchema::FieldType(field_type) => field_type.name(),
2843            PayloadFieldSchema::FieldParams(field_params) => field_params.name(),
2844        }
2845    }
2846
2847    pub fn is_tenant(&self) -> bool {
2848        match self {
2849            PayloadFieldSchema::FieldType(_) => false,
2850            PayloadFieldSchema::FieldParams(params) => params.tenant_optimization(),
2851        }
2852    }
2853
2854    pub fn is_on_disk(&self) -> bool {
2855        match self {
2856            PayloadFieldSchema::FieldType(_) => false,
2857            PayloadFieldSchema::FieldParams(params) => params.is_on_disk(),
2858        }
2859    }
2860
2861    /// Effective memory placement of the field index, resolving the new `memory` parameter
2862    /// against the deprecated `on_disk` flag. Defaults to [`Memory::Pinned`].
2863    pub fn memory_placement(&self) -> Memory {
2864        match self {
2865            PayloadFieldSchema::FieldType(_) => Memory::Pinned,
2866            PayloadFieldSchema::FieldParams(params) => params.memory_placement(),
2867        }
2868    }
2869
2870    pub fn kind(&self) -> PayloadSchemaType {
2871        match self {
2872            PayloadFieldSchema::FieldType(t) => *t,
2873            PayloadFieldSchema::FieldParams(p) => p.kind(),
2874        }
2875    }
2876
2877    /// Check if this type supports a `match` condition
2878    pub fn supports_match(&self) -> bool {
2879        match self {
2880            PayloadFieldSchema::FieldType(payload_schema_type) => match payload_schema_type {
2881                PayloadSchemaType::Keyword => true,
2882                PayloadSchemaType::Integer => true,
2883                PayloadSchemaType::Uuid => true,
2884                PayloadSchemaType::Bool => true,
2885                PayloadSchemaType::Float => false,
2886                PayloadSchemaType::Geo => false,
2887                PayloadSchemaType::Text => false,
2888                PayloadSchemaType::Datetime => false,
2889            },
2890            PayloadFieldSchema::FieldParams(payload_schema_params) => match payload_schema_params {
2891                PayloadSchemaParams::Keyword(_) => true,
2892                PayloadSchemaParams::Integer(integer_index_params) => {
2893                    integer_index_params.lookup == Some(true)
2894                }
2895                PayloadSchemaParams::Uuid(_) => true,
2896                PayloadSchemaParams::Bool(_) => true,
2897                PayloadSchemaParams::Float(_) => false,
2898                PayloadSchemaParams::Geo(_) => false,
2899                PayloadSchemaParams::Text(_) => false,
2900                PayloadSchemaParams::Datetime(_) => false,
2901            },
2902        }
2903    }
2904
2905    pub fn enable_hnsw(&self) -> bool {
2906        match self {
2907            PayloadFieldSchema::FieldType(_) => true,
2908            PayloadFieldSchema::FieldParams(p) => p.enable_hnsw(),
2909        }
2910    }
2911}
2912
2913impl From<PayloadSchemaType> for PayloadFieldSchema {
2914    fn from(payload_schema_type: PayloadSchemaType) -> Self {
2915        PayloadFieldSchema::FieldType(payload_schema_type)
2916    }
2917}
2918
2919impl TryFrom<PayloadIndexInfo> for PayloadFieldSchema {
2920    type Error = String;
2921
2922    fn try_from(index_info: PayloadIndexInfo) -> Result<Self, Self::Error> {
2923        let PayloadIndexInfo {
2924            data_type,
2925            params,
2926            points: _,
2927        } = index_info;
2928
2929        match params {
2930            None => Ok(PayloadFieldSchema::FieldType(data_type)),
2931
2932            Some(params) if data_type == params.kind() => {
2933                Ok(PayloadFieldSchema::FieldParams(params))
2934            }
2935
2936            Some(params) => Err(format!(
2937                "payload field with type {data_type:?} has parameters of type {:?}",
2938                params.kind(),
2939            )),
2940        }
2941    }
2942}
2943
2944#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
2945#[serde(untagged)]
2946pub enum ValueVariants {
2947    String(String),
2948    Integer(IntPayloadType),
2949    Bool(bool),
2950}
2951
2952impl ValueVariants {
2953    pub fn to_value(&self) -> Value {
2954        match self {
2955            ValueVariants::String(keyword) => Value::String(keyword.clone()),
2956            &ValueVariants::Integer(integer) => Value::Number(integer.into()),
2957            &ValueVariants::Bool(flag) => Value::Bool(flag),
2958        }
2959    }
2960}
2961
2962#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
2963#[serde(untagged)]
2964pub enum AnyVariants {
2965    Strings(IndexSet<String, FnvBuildHasher>),
2966    Integers(IndexSet<IntPayloadType, FnvBuildHasher>),
2967}
2968
2969impl Hash for AnyVariants {
2970    fn hash<H: Hasher>(&self, state: &mut H) {
2971        mem::discriminant(self).hash(state);
2972        match self {
2973            AnyVariants::Strings(index_set) => {
2974                for item in index_set {
2975                    item.hash(state);
2976                }
2977            }
2978            AnyVariants::Integers(index_set) => {
2979                for item in index_set {
2980                    item.hash(state);
2981                }
2982            }
2983        }
2984    }
2985}
2986
2987impl AnyVariants {
2988    pub fn len(&self) -> usize {
2989        match self {
2990            AnyVariants::Strings(index_set) => index_set.len(),
2991            AnyVariants::Integers(index_set) => index_set.len(),
2992        }
2993    }
2994
2995    pub fn is_empty(&self) -> bool {
2996        match self {
2997            AnyVariants::Strings(index_set) => index_set.is_empty(),
2998            AnyVariants::Integers(index_set) => index_set.is_empty(),
2999        }
3000    }
3001}
3002
3003/// Exact match of the given value
3004#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3005#[serde(rename_all = "snake_case")]
3006pub struct MatchValue {
3007    pub value: ValueVariants,
3008}
3009
3010/// Full-text match of the strings.
3011#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3012#[serde(rename_all = "snake_case")]
3013pub struct MatchText {
3014    pub text: String,
3015}
3016
3017/// Full-text match of at least one token of the string.
3018#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3019#[serde(rename_all = "snake_case")]
3020pub struct MatchTextAny {
3021    pub text_any: String,
3022}
3023
3024impl<S: Into<String>> From<S> for MatchText {
3025    fn from(text: S) -> Self {
3026        MatchText { text: text.into() }
3027    }
3028}
3029
3030/// Full-text phrase match of the string.
3031#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3032#[serde(rename_all = "snake_case")]
3033pub struct MatchPhrase {
3034    pub phrase: String,
3035}
3036
3037impl<S: Into<String>> From<S> for MatchPhrase {
3038    fn from(text: S) -> Self {
3039        MatchPhrase {
3040            phrase: text.into(),
3041        }
3042    }
3043}
3044
3045/// Match keyword values that start with the given string.
3046///
3047/// Byte-wise (hence, for valid UTF-8, character-wise) and case-sensitive,
3048/// consistent with exact keyword matching. Served efficiently by a keyword
3049/// index created with the `prefix` option.
3050#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3051#[serde(rename_all = "snake_case")]
3052pub struct MatchPrefix {
3053    pub prefix: String,
3054}
3055
3056impl<S: Into<String>> From<S> for MatchPrefix {
3057    fn from(prefix: S) -> Self {
3058        MatchPrefix {
3059            prefix: prefix.into(),
3060        }
3061    }
3062}
3063
3064/// Exact match on any of the given values
3065#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3066#[serde(rename_all = "snake_case")]
3067pub struct MatchAny {
3068    pub any: AnyVariants,
3069}
3070
3071/// Should have at least one value not matching the any given values
3072#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3073#[serde(rename_all = "snake_case")]
3074pub struct MatchExcept {
3075    pub except: AnyVariants,
3076}
3077
3078/// Match filter request
3079#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
3080#[serde(untagged, rename_all = "snake_case")]
3081pub enum MatchInterface {
3082    Value(MatchValue),
3083    Text(MatchText),
3084    TextAny(MatchTextAny),
3085    Phrase(MatchPhrase),
3086    Prefix(MatchPrefix),
3087    Any(MatchAny),
3088    Except(MatchExcept),
3089}
3090
3091/// Match filter request
3092#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3093#[serde(untagged, from = "MatchInterface")]
3094pub enum Match {
3095    Value(MatchValue),
3096    Text(MatchText),
3097    TextAny(MatchTextAny),
3098    Phrase(MatchPhrase),
3099    Prefix(MatchPrefix),
3100    Any(MatchAny),
3101    Except(MatchExcept),
3102}
3103
3104impl Match {
3105    pub fn new_value(value: ValueVariants) -> Self {
3106        Self::Value(MatchValue { value })
3107    }
3108
3109    pub fn new_text(text: &str) -> Self {
3110        Self::Text(MatchText { text: text.into() })
3111    }
3112
3113    pub fn new_prefix(prefix: &str) -> Self {
3114        Self::Prefix(MatchPrefix {
3115            prefix: prefix.into(),
3116        })
3117    }
3118
3119    pub fn new_any(any: AnyVariants) -> Self {
3120        Self::Any(MatchAny { any })
3121    }
3122
3123    pub fn new_except(except: AnyVariants) -> Self {
3124        Self::Except(MatchExcept { except })
3125    }
3126}
3127
3128impl From<AnyVariants> for Match {
3129    fn from(any: AnyVariants) -> Self {
3130        Self::Any(MatchAny { any })
3131    }
3132}
3133
3134impl From<MatchInterface> for Match {
3135    fn from(value: MatchInterface) -> Self {
3136        match value {
3137            MatchInterface::Value(value) => Self::Value(MatchValue { value: value.value }),
3138            MatchInterface::Text(text) => Self::Text(MatchText { text: text.text }),
3139            MatchInterface::TextAny(text_any) => Self::TextAny(MatchTextAny {
3140                text_any: text_any.text_any,
3141            }),
3142            MatchInterface::Any(any) => Self::Any(MatchAny { any: any.any }),
3143            MatchInterface::Except(except) => Self::Except(MatchExcept {
3144                except: except.except,
3145            }),
3146            MatchInterface::Phrase(MatchPhrase { phrase }) => Self::Phrase(MatchPhrase { phrase }),
3147            MatchInterface::Prefix(MatchPrefix { prefix }) => Self::Prefix(MatchPrefix { prefix }),
3148        }
3149    }
3150}
3151
3152impl From<bool> for Match {
3153    fn from(flag: bool) -> Self {
3154        Self::Value(MatchValue {
3155            value: ValueVariants::Bool(flag),
3156        })
3157    }
3158}
3159
3160impl From<String> for Match {
3161    fn from(keyword: String) -> Self {
3162        Self::Value(MatchValue {
3163            value: ValueVariants::String(keyword),
3164        })
3165    }
3166}
3167
3168impl From<EcoString> for Match {
3169    fn from(keyword: EcoString) -> Self {
3170        Self::Value(MatchValue {
3171            value: ValueVariants::String(keyword.into()),
3172        })
3173    }
3174}
3175
3176impl From<IntPayloadType> for Match {
3177    fn from(integer: IntPayloadType) -> Self {
3178        Self::Value(MatchValue {
3179            value: ValueVariants::Integer(integer),
3180        })
3181    }
3182}
3183
3184impl From<Vec<String>> for Match {
3185    fn from(keywords: Vec<String>) -> Self {
3186        let keywords: IndexSet<String, FnvBuildHasher> = keywords.into_iter().collect();
3187        Self::Any(MatchAny {
3188            any: AnyVariants::Strings(keywords),
3189        })
3190    }
3191}
3192
3193impl From<ValueVariants> for Match {
3194    fn from(value: ValueVariants) -> Self {
3195        Self::Value(MatchValue { value })
3196    }
3197}
3198
3199impl From<Vec<String>> for MatchExcept {
3200    fn from(keywords: Vec<String>) -> Self {
3201        let keywords: IndexSet<String, FnvBuildHasher> = keywords.into_iter().collect();
3202        MatchExcept {
3203            except: AnyVariants::Strings(keywords),
3204        }
3205    }
3206}
3207
3208impl From<Vec<IntPayloadType>> for Match {
3209    fn from(integers: Vec<IntPayloadType>) -> Self {
3210        let integers: IndexSet<_, FnvBuildHasher> = integers.into_iter().collect();
3211        Self::Any(MatchAny {
3212            any: AnyVariants::Integers(integers),
3213        })
3214    }
3215}
3216
3217impl From<Vec<IntPayloadType>> for MatchExcept {
3218    fn from(integers: Vec<IntPayloadType>) -> Self {
3219        let integers: IndexSet<_, FnvBuildHasher> = integers.into_iter().collect();
3220        MatchExcept {
3221            except: AnyVariants::Integers(integers),
3222        }
3223    }
3224}
3225
3226#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, JsonSchema)]
3227#[serde(untagged)]
3228pub enum RangeInterface {
3229    Float(Range<OrderedFloat<FloatPayloadType>>),
3230    DateTime(Range<DateTimePayloadType>),
3231}
3232
3233impl Hash for RangeInterface {
3234    fn hash<H: hash::Hasher>(&self, state: &mut H) {
3235        match self {
3236            RangeInterface::Float(range) => {
3237                let Range { lt, gt, gte, lte } = range;
3238                lt.hash(state);
3239                gt.hash(state);
3240                gte.hash(state);
3241                lte.hash(state);
3242            }
3243            RangeInterface::DateTime(range) => {
3244                let Range { lt, gt, gte, lte } = range;
3245                lt.hash(state);
3246                gt.hash(state);
3247                gte.hash(state);
3248                lte.hash(state);
3249            }
3250        }
3251    }
3252}
3253
3254#[derive(serde::Deserialize)]
3255#[serde(untagged)]
3256enum RangeInterfaceUntagged {
3257    Float(Range<OrderedFloatPayloadType>),
3258    DateTime(Range<DateTimePayloadType>),
3259}
3260
3261impl<'de> serde::Deserialize<'de> for RangeInterface {
3262    /// Parses range bounds, treating string bounds as RFC3339 datetimes for REST/JSON `datetime_range` filters.
3263    /// Preserves clear user-facing errors when datetime formats are invalid.
3264    /// Example accepted datetime bound: `2014-01-01T00:00:00Z`.
3265    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3266    where
3267        D: serde::Deserializer<'de>,
3268    {
3269        if !deserializer.is_human_readable() {
3270            return RangeInterfaceUntagged::deserialize(deserializer).map(|parsed| match parsed {
3271                RangeInterfaceUntagged::Float(r) => RangeInterface::Float(r),
3272                RangeInterfaceUntagged::DateTime(r) => RangeInterface::DateTime(r),
3273            });
3274        }
3275
3276        let value = serde_json::Value::deserialize(deserializer)?;
3277
3278        // If any range bound is a string -> treat as datetime range
3279        if let Some(obj) = value.as_object() {
3280            let keys = ["lt", "gt", "lte", "gte"];
3281            let has_string_bound = keys
3282                .iter()
3283                .any(|k| obj.get(*k).is_some_and(|v| v.is_string()));
3284
3285            if has_string_bound {
3286                return serde_json::from_value::<Range<DateTimePayloadType>>(value)
3287                    .map(RangeInterface::DateTime)
3288                    .map_err(serde::de::Error::custom);
3289            }
3290        }
3291
3292        // Fallback to existing untagged behavior
3293        let parsed = serde_json::from_value::<RangeInterfaceUntagged>(value)
3294            .map_err(serde::de::Error::custom)?;
3295
3296        Ok(match parsed {
3297            RangeInterfaceUntagged::Float(r) => RangeInterface::Float(r),
3298            RangeInterfaceUntagged::DateTime(r) => RangeInterface::DateTime(r),
3299        })
3300    }
3301}
3302
3303type OrderedFloatPayloadType = OrderedFloat<FloatPayloadType>;
3304
3305/// Range filter request
3306#[macro_rules_attribute::macro_rules_derive(crate::segment::common::macros::schemars_rename_generics)]
3307#[derive_args(< OrderedFloatPayloadType > => "Range", < DateTimePayloadType > => "DatetimeRange")]
3308#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
3309#[serde(rename_all = "snake_case")]
3310pub struct Range<T> {
3311    /// point.key < range.lt
3312    pub lt: Option<T>,
3313    /// point.key > range.gt
3314    pub gt: Option<T>,
3315    /// point.key >= range.gte
3316    pub gte: Option<T>,
3317    /// point.key <= range.lte
3318    pub lte: Option<T>,
3319}
3320
3321impl<T: Copy> Range<T> {
3322    /// Convert range to a range of another type
3323    pub fn map<U, F: Fn(T) -> U>(&self, f: F) -> Range<U> {
3324        let Self { lt, gt, gte, lte } = self;
3325        Range {
3326            lt: lt.map(&f),
3327            gt: gt.map(&f),
3328            gte: gte.map(&f),
3329            lte: lte.map(&f),
3330        }
3331    }
3332}
3333
3334impl<T: Copy + PartialOrd> Range<T> {
3335    pub fn check_range(&self, number: T) -> bool {
3336        let Self { lt, gt, gte, lte } = self;
3337        lt.is_none_or(|x| number < x)
3338            && gt.is_none_or(|x| number > x)
3339            && lte.is_none_or(|x| number <= x)
3340            && gte.is_none_or(|x| number >= x)
3341    }
3342}
3343
3344/// Values count filter request
3345#[derive(Debug, Deserialize, Serialize, JsonSchema, Copy, Clone, PartialEq, Eq, Hash)]
3346#[serde(rename_all = "snake_case")]
3347pub struct ValuesCount {
3348    /// point.key.length() < values_count.lt
3349    pub lt: Option<usize>,
3350    /// point.key.length() > values_count.gt
3351    pub gt: Option<usize>,
3352    /// point.key.length() >= values_count.gte
3353    pub gte: Option<usize>,
3354    /// point.key.length() <= values_count.lte
3355    pub lte: Option<usize>,
3356}
3357
3358impl ValuesCount {
3359    pub fn check_count(&self, count: usize) -> bool {
3360        let Self { lt, gt, gte, lte } = self;
3361        lt.is_none_or(|x| count < x)
3362            && gt.is_none_or(|x| count > x)
3363            && lte.is_none_or(|x| count <= x)
3364            && gte.is_none_or(|x| count >= x)
3365    }
3366
3367    pub fn check_count_from(&self, value: &Value) -> bool {
3368        let count = match value {
3369            Value::Null => 0,
3370            Value::Array(array) => array.len(),
3371            Value::Bool(_) | Value::Number(_) | Value::String(_) | Value::Object(_) => 1,
3372        };
3373
3374        self.check_count(count)
3375    }
3376}
3377
3378#[cfg(test)]
3379impl From<std::ops::Range<usize>> for ValuesCount {
3380    fn from(range: std::ops::Range<usize>) -> Self {
3381        Self {
3382            gte: Some(range.start),
3383            lt: Some(range.end),
3384            gt: None,
3385            lte: None,
3386        }
3387    }
3388}
3389
3390pub trait CheckGeoPoint {
3391    fn check_point(&self, point: &GeoPoint) -> bool;
3392}
3393
3394/// Geo filter request
3395///
3396/// Matches coordinates inside the rectangle, described by coordinates of lop-left and bottom-right edges
3397#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize, JsonSchema)]
3398#[serde(rename_all = "snake_case")]
3399pub struct GeoBoundingBox {
3400    /// Coordinates of the top left point of the area rectangle
3401    pub top_left: GeoPoint,
3402    /// Coordinates of the bottom right point of the area rectangle
3403    pub bottom_right: GeoPoint,
3404}
3405
3406impl CheckGeoPoint for GeoBoundingBox {
3407    fn check_point(&self, point: &GeoPoint) -> bool {
3408        let longitude_check = if self.top_left.lon > self.bottom_right.lon {
3409            // Handle antimeridian crossing
3410            point.lon > self.top_left.lon || point.lon < self.bottom_right.lon
3411        } else {
3412            self.top_left.lon < point.lon && point.lon < self.bottom_right.lon
3413        };
3414
3415        let latitude_check = self.bottom_right.lat < point.lat && point.lat < self.top_left.lat;
3416
3417        longitude_check && latitude_check
3418    }
3419}
3420
3421/// Geo filter request
3422///
3423/// Matches coordinates inside the circle of `radius` and center with coordinates `center`
3424#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize, JsonSchema)]
3425#[serde(rename_all = "snake_case")]
3426pub struct GeoRadius {
3427    /// Coordinates of the top left point of the area rectangle
3428    pub center: GeoPoint,
3429    /// Radius of the area in meters
3430    pub radius: OrderedFloat<f64>,
3431}
3432
3433impl Hash for GeoRadius {
3434    fn hash<H: hash::Hasher>(&self, state: &mut H) {
3435        let GeoRadius { center, radius } = self;
3436        center.hash(state);
3437        // Hash f64 by converting to bits
3438        OrderedFloat(*radius).hash(state);
3439    }
3440}
3441
3442impl CheckGeoPoint for GeoRadius {
3443    fn check_point(&self, point: &GeoPoint) -> bool {
3444        let query_center = Point::from(self.center);
3445        Haversine.distance(query_center, Point::from(*point)) < self.radius.0
3446    }
3447}
3448
3449#[derive(Deserialize)]
3450pub struct GeoPolygonShadow {
3451    pub exterior: GeoLineString,
3452    pub interiors: Option<Vec<GeoLineString>>,
3453}
3454
3455pub struct PolygonWrapper {
3456    pub polygon: Polygon,
3457}
3458
3459impl CheckGeoPoint for PolygonWrapper {
3460    fn check_point(&self, point: &GeoPoint) -> bool {
3461        let point_new = Point::new(point.lon.0, point.lat.0);
3462        self.polygon.contains(&point_new)
3463    }
3464}
3465
3466/// Geo filter request
3467///
3468/// Matches coordinates inside the polygon, defined by `exterior` and `interiors`
3469#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3470#[serde(try_from = "GeoPolygonShadow", rename_all = "snake_case")]
3471pub struct GeoPolygon {
3472    /// The exterior line bounds the surface
3473    /// must consist of a minimum of 4 points, and the first and last points
3474    /// must be the same.
3475    pub exterior: GeoLineString,
3476    /// Interior lines (if present) bound holes within the surface
3477    /// each GeoLineString must consist of a minimum of 4 points, and the first
3478    /// and last points must be the same.
3479    pub interiors: Option<Vec<GeoLineString>>,
3480}
3481
3482impl GeoPolygon {
3483    pub fn validate_line_string(line: &GeoLineString) -> OperationResult<()> {
3484        if line.points.len() <= 3 {
3485            return Err(OperationError::validation_error(format!(
3486                "polygon invalid, the size must be at least 4, got {}",
3487                line.points.len()
3488            )));
3489        }
3490
3491        if let (Some(first), Some(last)) = (line.points.first(), line.points.last())
3492            && ((first.lat - last.lat).abs() > f64::EPSILON
3493                || (first.lon - last.lon).abs() > f64::EPSILON)
3494        {
3495            return Err(OperationError::validation_error(
3496                "polygon invalid, the first and the last points should be the same to form a closed line",
3497            ));
3498        }
3499
3500        Ok(())
3501    }
3502
3503    // convert GeoPolygon to Geo crate Polygon class for checking point intersection
3504    pub fn convert(&self) -> PolygonWrapper {
3505        let exterior_line: LineString = LineString(
3506            self.exterior
3507                .points
3508                .iter()
3509                .map(|p| Coord {
3510                    x: p.lon.0,
3511                    y: p.lat.0,
3512                })
3513                .collect(),
3514        );
3515
3516        // Convert the interior points to coordinates (if any)
3517        let interior_lines: Vec<LineString> = match &self.interiors {
3518            None => vec![],
3519            Some(interiors) => interiors
3520                .iter()
3521                .map(|interior_points| {
3522                    interior_points
3523                        .points
3524                        .iter()
3525                        .map(|p| Coord {
3526                            x: p.lon.0,
3527                            y: p.lat.0,
3528                        })
3529                        .collect()
3530                })
3531                .map(LineString)
3532                .collect(),
3533        };
3534        PolygonWrapper {
3535            polygon: Polygon::new(exterior_line, interior_lines),
3536        }
3537    }
3538}
3539
3540impl TryFrom<GeoPolygonShadow> for GeoPolygon {
3541    type Error = OperationError;
3542
3543    fn try_from(value: GeoPolygonShadow) -> OperationResult<Self> {
3544        let GeoPolygonShadow {
3545            exterior,
3546            interiors,
3547        } = value;
3548        Self::validate_line_string(&exterior)?;
3549
3550        if let Some(interiors) = &interiors {
3551            for interior in interiors {
3552                Self::validate_line_string(interior)?;
3553            }
3554        }
3555
3556        Ok(GeoPolygon {
3557            exterior,
3558            interiors,
3559        })
3560    }
3561}
3562
3563/// All possible payload filtering conditions
3564#[derive(Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, PartialEq, Eq, Hash)]
3565#[validate(schema(function = "validate_field_condition"))]
3566#[serde(rename_all = "snake_case")]
3567pub struct FieldCondition {
3568    /// Payload key
3569    pub key: PayloadKeyType,
3570    /// Check if point has field with a given value
3571    #[serde(skip_serializing_if = "Option::is_none")]
3572    pub r#match: Option<Match>,
3573    /// Check if points value lies in a given range
3574    #[serde(skip_serializing_if = "Option::is_none")]
3575    pub range: Option<RangeInterface>,
3576    /// Check if points geolocation lies in a given area
3577    #[serde(skip_serializing_if = "Option::is_none")]
3578    pub geo_bounding_box: Option<GeoBoundingBox>,
3579    /// Check if geo point is within a given radius
3580    #[serde(skip_serializing_if = "Option::is_none")]
3581    pub geo_radius: Option<GeoRadius>,
3582    /// Check if geo point is within a given polygon
3583    #[serde(skip_serializing_if = "Option::is_none")]
3584    pub geo_polygon: Option<GeoPolygon>,
3585    /// Check number of values of the field
3586    #[serde(skip_serializing_if = "Option::is_none")]
3587    pub values_count: Option<ValuesCount>,
3588    /// Check that the field is empty, alternative syntax for `is_empty: "field_name"`
3589    #[serde(skip_serializing_if = "Option::is_none")]
3590    pub is_empty: Option<bool>,
3591    /// Check that the field is null, alternative syntax for `is_null: "field_name"`
3592    #[serde(skip_serializing_if = "Option::is_none")]
3593    pub is_null: Option<bool>,
3594}
3595
3596impl FieldCondition {
3597    pub fn new_match(key: PayloadKeyType, r#match: Match) -> Self {
3598        Self {
3599            key,
3600            r#match: Some(r#match),
3601            range: None,
3602            geo_bounding_box: None,
3603            geo_radius: None,
3604            geo_polygon: None,
3605            values_count: None,
3606            is_empty: None,
3607            is_null: None,
3608        }
3609    }
3610
3611    pub fn new_range(key: PayloadKeyType, range: Range<OrderedFloat<FloatPayloadType>>) -> Self {
3612        Self {
3613            key,
3614            r#match: None,
3615            range: Some(RangeInterface::Float(range)),
3616            geo_bounding_box: None,
3617            geo_radius: None,
3618            geo_polygon: None,
3619            values_count: None,
3620            is_empty: None,
3621            is_null: None,
3622        }
3623    }
3624
3625    pub fn new_datetime_range(
3626        key: PayloadKeyType,
3627        datetime_range: Range<DateTimePayloadType>,
3628    ) -> Self {
3629        Self {
3630            key,
3631            r#match: None,
3632            range: Some(RangeInterface::DateTime(datetime_range)),
3633            geo_bounding_box: None,
3634            geo_radius: None,
3635            geo_polygon: None,
3636            values_count: None,
3637            is_empty: None,
3638            is_null: None,
3639        }
3640    }
3641
3642    pub fn new_geo_bounding_box(key: PayloadKeyType, geo_bounding_box: GeoBoundingBox) -> Self {
3643        Self {
3644            key,
3645            r#match: None,
3646            range: None,
3647            geo_bounding_box: Some(geo_bounding_box),
3648            geo_radius: None,
3649            geo_polygon: None,
3650            values_count: None,
3651            is_empty: None,
3652            is_null: None,
3653        }
3654    }
3655
3656    pub fn new_geo_radius(key: PayloadKeyType, geo_radius: GeoRadius) -> Self {
3657        Self {
3658            key,
3659            r#match: None,
3660            range: None,
3661            geo_bounding_box: None,
3662            geo_radius: Some(geo_radius),
3663            geo_polygon: None,
3664            values_count: None,
3665            is_empty: None,
3666            is_null: None,
3667        }
3668    }
3669
3670    pub fn new_geo_polygon(key: PayloadKeyType, geo_polygon: GeoPolygon) -> Self {
3671        Self {
3672            key,
3673            r#match: None,
3674            range: None,
3675            geo_bounding_box: None,
3676            geo_radius: None,
3677            geo_polygon: Some(geo_polygon),
3678            values_count: None,
3679            is_empty: None,
3680            is_null: None,
3681        }
3682    }
3683
3684    pub fn new_values_count(key: PayloadKeyType, values_count: ValuesCount) -> Self {
3685        Self {
3686            key,
3687            r#match: None,
3688            range: None,
3689            geo_bounding_box: None,
3690            geo_radius: None,
3691            geo_polygon: None,
3692            values_count: Some(values_count),
3693            is_empty: None,
3694            is_null: None,
3695        }
3696    }
3697
3698    pub fn new_is_empty(key: PayloadKeyType, is_empty: bool) -> Self {
3699        Self {
3700            key,
3701            r#match: None,
3702            range: None,
3703            geo_bounding_box: None,
3704            geo_radius: None,
3705            geo_polygon: None,
3706            values_count: None,
3707            is_empty: Some(is_empty),
3708            is_null: None,
3709        }
3710    }
3711
3712    pub fn new_is_null(key: PayloadKeyType, is_null: bool) -> Self {
3713        Self {
3714            key,
3715            r#match: None,
3716            range: None,
3717            geo_bounding_box: None,
3718            geo_radius: None,
3719            geo_polygon: None,
3720            values_count: None,
3721            is_empty: None,
3722            is_null: Some(is_null),
3723        }
3724    }
3725
3726    pub fn all_fields_none(&self) -> bool {
3727        matches!(
3728            self,
3729            FieldCondition {
3730                r#match: None,
3731                range: None,
3732                geo_bounding_box: None,
3733                geo_radius: None,
3734                geo_polygon: None,
3735                values_count: None,
3736                key: _,
3737                is_empty: None,
3738                is_null: None,
3739            }
3740        )
3741    }
3742
3743    fn input_size(&self) -> usize {
3744        if self.r#match.is_none() {
3745            return 0;
3746        }
3747
3748        match self.r#match.as_ref().unwrap() {
3749            Match::Any(match_any) => match_any.any.len(),
3750            Match::Except(match_except) => match_except.except.len(),
3751            Match::Value(_) => 0,
3752            Match::Text(_) => 0,
3753            Match::Phrase(_) => 0,
3754            Match::TextAny(_) => 0,
3755            Match::Prefix(_) => 0,
3756        }
3757    }
3758}
3759
3760pub fn validate_field_condition(field_condition: &FieldCondition) -> Result<(), ValidationError> {
3761    if field_condition.all_fields_none() {
3762        Err(ValidationError::new(
3763            "At least one field condition must be specified",
3764        ))
3765    } else {
3766        Ok(())
3767    }
3768}
3769
3770/// Payload field
3771#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3772pub struct PayloadField {
3773    /// Payload field name
3774    pub key: PayloadKeyType,
3775}
3776
3777/// Select points with empty payload for a specified field
3778#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3779pub struct IsEmptyCondition {
3780    pub is_empty: PayloadField,
3781}
3782
3783/// Select points with null payload for a specified field
3784#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3785pub struct IsNullCondition {
3786    pub is_null: PayloadField,
3787}
3788
3789impl From<JsonPath> for IsNullCondition {
3790    fn from(key: PayloadKeyType) -> Self {
3791        IsNullCondition {
3792            is_null: PayloadField { key },
3793        }
3794    }
3795}
3796
3797impl From<JsonPath> for IsEmptyCondition {
3798    fn from(key: PayloadKeyType) -> Self {
3799        IsEmptyCondition {
3800            is_empty: PayloadField { key },
3801        }
3802    }
3803}
3804
3805/// ID-based filtering condition
3806#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq)]
3807pub struct HasIdCondition {
3808    #[schemars(schema_with = "HashSet::<PointIdType>::json_schema")]
3809    pub has_id: MaybeArc<AHashSet<PointIdType>>,
3810}
3811
3812impl Hash for HasIdCondition {
3813    fn hash<H: hash::Hasher>(&self, state: &mut H) {
3814        unordered_hash_unique(state, self.has_id.iter());
3815    }
3816}
3817
3818/// Filter points which have specific vector assigned
3819#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
3820pub struct HasVectorCondition {
3821    pub has_vector: VectorNameBuf,
3822}
3823
3824impl From<VectorNameBuf> for HasVectorCondition {
3825    fn from(vector: VectorNameBuf) -> Self {
3826        HasVectorCondition { has_vector: vector }
3827    }
3828}
3829
3830/// Threshold determining when to use an `Arc` in `HasIdCondition` if the condition includes many points.
3831/// Since we're cloning filters quite a lot, using an Arc for larger conditions reduces risk of memory leaks
3832/// and potentially improves performance in some places.
3833const HAS_ID_CONDITION_ARC_THRESHOLD: usize = 1_000;
3834
3835impl From<AHashSet<PointIdType>> for HasIdCondition {
3836    fn from(has_id: AHashSet<PointIdType>) -> Self {
3837        if has_id.len() > HAS_ID_CONDITION_ARC_THRESHOLD {
3838            HasIdCondition {
3839                has_id: MaybeArc::arc(has_id),
3840            }
3841        } else {
3842            HasIdCondition {
3843                has_id: MaybeArc::no_arc(has_id),
3844            }
3845        }
3846    }
3847}
3848
3849impl FromIterator<PointIdType> for HasIdCondition {
3850    fn from_iter<T: IntoIterator<Item = PointIdType>>(iter: T) -> Self {
3851        let items: AHashSet<_> = iter.into_iter().collect();
3852        // Arc-Threshold applies here, since we're reusing the From implementation from AHashSet.
3853        Self::from(items)
3854    }
3855}
3856
3857/// One of `total` disjoint deterministic slices of the id space.
3858///
3859/// A point belongs to the slice iff `hash(id) % total == index`, where `hash`
3860/// is SipHash-2-4 with a zero key over the canonical id bytes: 8 little-endian
3861/// bytes for numeric ids, the 16 RFC 4122 bytes for UUIDs. For a fixed
3862/// `total`, slices `0..total` are disjoint and together cover all points;
3863/// membership is uniform regardless of the id scheme and stable across
3864/// queries, segments, platforms and Qdrant versions.
3865///
3866/// Slices with different `total` values are correlated (same hash, no salt):
3867/// e.g. slice `0` of `total: 4` is a strict subset of slice `0` of `total: 2`.
3868/// This keeps a smaller sample contained in a larger one.
3869#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Copy, PartialEq, Eq, Hash)]
3870pub struct Slice {
3871    /// Total number of disjoint slices the id space is split into
3872    pub total: NonZeroU32,
3873    /// Which slice to select, must be in `0..total`
3874    pub index: u32,
3875}
3876
3877impl Slice {
3878    /// True iff `point_id` belongs to this slice.
3879    pub fn check(&self, point_id: PointIdType) -> bool {
3880        let Self { total, index } = self;
3881        slice_point_id_hash(point_id) % u64::from(total.get()) == u64::from(*index)
3882    }
3883}
3884
3885/// SipHash-2-4 with a zero key over the canonical byte encoding of a point id:
3886/// 8 little-endian bytes for [`ExtendedPointId::NumId`], the 16 RFC 4122 bytes
3887/// for [`ExtendedPointId::Uuid`].
3888///
3889/// This value is a public API contract of [`SliceCondition`]: clients may
3890/// reproduce it to predict slice membership locally, so it must never change.
3891/// It is deliberately independent from [`StableHash`], which is native-endian
3892/// and internal to resharding.
3893pub fn slice_point_id_hash(point_id: ExtendedPointId) -> u64 {
3894    let mut hasher = siphasher::sip::SipHasher24::new();
3895    match point_id {
3896        ExtendedPointId::NumId(num) => hasher.write(&num.to_le_bytes()),
3897        ExtendedPointId::Uuid(uuid) => hasher.write(uuid.as_bytes()),
3898    }
3899    hasher.finish()
3900}
3901
3902/// Select points that fall into one of `total` disjoint deterministic slices
3903/// of the id space, for parallel scans and reproducible sampling.
3904#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Copy, PartialEq, Eq, Hash, Validate)]
3905#[validate(schema(function = "validate_slice_condition"))]
3906pub struct SliceCondition {
3907    pub slice: Slice,
3908}
3909
3910pub fn validate_slice_condition(condition: &SliceCondition) -> Result<(), ValidationError> {
3911    let SliceCondition { slice } = condition;
3912    let Slice { total, index } = slice;
3913    if index < &total.get() {
3914        Ok(())
3915    } else {
3916        Err(ValidationError::new(
3917            "Slice index must be less than the total number of slices",
3918        ))
3919    }
3920}
3921
3922/// Select points with payload for a specified nested field
3923#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Validate, Hash)]
3924pub struct Nested {
3925    pub key: PayloadKeyType,
3926    #[validate(nested)]
3927    pub filter: Filter,
3928}
3929
3930#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Validate, Hash)]
3931pub struct NestedCondition {
3932    #[validate(nested)]
3933    pub nested: Nested,
3934}
3935
3936/// Container to work around the untagged enum limitation for condition
3937impl NestedCondition {
3938    pub fn new(nested: Nested) -> Self {
3939        Self { nested }
3940    }
3941
3942    /// Get the raw key without any modifications
3943    pub fn raw_key(&self) -> &PayloadKeyType {
3944        &self.nested.key
3945    }
3946
3947    /// Nested is made to be used with arrays, so we add `[]` to the key if it is not present for convenience
3948    pub fn array_key(&self) -> PayloadKeyType {
3949        self.raw_key().array_key()
3950    }
3951
3952    pub fn filter(&self) -> &Filter {
3953        &self.nested.filter
3954    }
3955}
3956
3957#[derive(Clone, Debug, Serialize, JsonSchema, PartialEq, Eq, Hash)]
3958#[serde(untagged)]
3959#[serde(
3960    expecting = "Expected some form of condition, which can be a field condition (like {\"key\": ..., \"match\": ... }), or some other mentioned in the documentation: https://qdrant.tech/documentation/concepts/filtering/#filtering-conditions"
3961)]
3962#[allow(clippy::large_enum_variant)]
3963pub enum Condition {
3964    /// Check if field satisfies provided condition
3965    Field(FieldCondition),
3966    /// Check if payload field is empty: equals to empty array, or does not exists
3967    IsEmpty(IsEmptyCondition),
3968    /// Check if payload field equals `NULL`
3969    IsNull(IsNullCondition),
3970    /// Check if points id is in a given set
3971    HasId(HasIdCondition),
3972    /// Check if point has vector assigned
3973    HasVector(HasVectorCondition),
3974    /// Check if point id falls into a deterministic slice of the id space
3975    Slice(SliceCondition),
3976    /// Nested filters
3977    Nested(NestedCondition),
3978    /// Nested filter
3979    Filter(Filter),
3980
3981    #[serde(skip)]
3982    CustomIdChecker(CustomIdChecker),
3983}
3984
3985#[derive(Deserialize)]
3986#[serde(untagged)]
3987#[serde(
3988    expecting = "Expected some form of condition, which can be a field condition (like {\"key\": ..., \"match\": ... }), or some other mentioned in the documentation: https://qdrant.tech/documentation/concepts/filtering/#filtering-conditions"
3989)]
3990#[expect(clippy::large_enum_variant, dead_code)]
3991enum ConditionUntagged {
3992    Field(FieldCondition),
3993    IsEmpty(IsEmptyCondition),
3994    IsNull(IsNullCondition),
3995    HasId(HasIdCondition),
3996    HasVector(HasVectorCondition),
3997    Slice(SliceCondition),
3998    Nested(NestedCondition),
3999    Filter(Filter),
4000
4001    #[serde(skip)]
4002    CustomIdChecker(CustomIdChecker),
4003}
4004
4005impl From<ConditionUntagged> for Condition {
4006    fn from(condition: ConditionUntagged) -> Self {
4007        match condition {
4008            ConditionUntagged::Field(condition) => Condition::Field(condition),
4009            ConditionUntagged::IsEmpty(condition) => Condition::IsEmpty(condition),
4010            ConditionUntagged::IsNull(condition) => Condition::IsNull(condition),
4011            ConditionUntagged::HasId(condition) => Condition::HasId(condition),
4012            ConditionUntagged::HasVector(condition) => Condition::HasVector(condition),
4013            ConditionUntagged::Slice(condition) => Condition::Slice(condition),
4014            ConditionUntagged::Nested(condition) => Condition::Nested(condition),
4015            ConditionUntagged::Filter(condition) => Condition::Filter(condition),
4016            ConditionUntagged::CustomIdChecker(condition) => Condition::CustomIdChecker(condition),
4017        }
4018    }
4019}
4020
4021impl<'de> serde::Deserialize<'de> for Condition {
4022    /// Deserializes Condition with special handling for FieldCondition to preserve
4023    /// readable RFC3339 datetime parse errors. Other variants use ConditionUntagged
4024    /// for compiler-level safety when new variants are added.
4025    /// Example accepted datetime value: `2014-01-01T00:00:00Z`.
4026    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
4027    where
4028        D: serde::Deserializer<'de>,
4029    {
4030        // Buffer into serde_value::Value which, unlike serde_json::Value,
4031        // can represent byte arrays from non-human-readable formats (e.g. CBOR).
4032        // Note: we cannot rely on `deserializer.is_human_readable()` here because
4033        // serde's internal ContentDeserializer (used by flatten + untagged) always
4034        // reports `true` regardless of the original format.
4035        let value = serde_value::Value::deserialize(deserializer)?;
4036
4037        // Special case: FieldCondition first to surface datetime parse errors.
4038        // Untagged enum would swallow these errors with generic message.
4039        if let serde_value::Value::Map(obj) = &value
4040            && let Some(key) = obj.get(&serde_value::Value::String("key".into()))
4041        {
4042            // Reject non-string `key` upfront, the deserialization error for it
4043            // would not mention which field is invalid.
4044            if !matches!(key, serde_value::Value::String(_)) {
4045                return Err(serde::de::Error::custom(
4046                    "Filter condition 'key' must be a string",
4047                ));
4048            }
4049            return value
4050                .deserialize_into()
4051                .map(Condition::Field)
4052                .map_err(serde::de::Error::custom);
4053        }
4054
4055        // All other variants handled by ConditionUntagged (compiler-safe)
4056        value
4057            .deserialize_into::<ConditionUntagged>()
4058            .map(Condition::from)
4059            .map_err(serde::de::Error::custom)
4060    }
4061}
4062
4063impl Condition {
4064    pub fn new_custom(checker: Arc<dyn CustomIdCheckerCondition + Send + Sync + 'static>) -> Self {
4065        Condition::CustomIdChecker(CustomIdChecker(checker))
4066    }
4067}
4068
4069#[derive(Debug, Clone)]
4070pub struct CustomIdChecker(pub Arc<dyn CustomIdCheckerCondition + Send + Sync + 'static>);
4071
4072impl Hash for CustomIdChecker {
4073    fn hash<H: hash::Hasher>(&self, state: &mut H) {
4074        // We cannot hash the inner function
4075        // This means that two different CustomIdChecker conditions will have the same hash,
4076        // but that's acceptable since we cannot do better, and only expected to be used
4077        // for logging and profiling purposes.
4078        std::ptr::hash(Arc::as_ptr(&self.0), state);
4079    }
4080}
4081
4082impl PartialEq for CustomIdChecker {
4083    fn eq(&self, other: &Self) -> bool {
4084        // We cannot compare the inner function
4085        // This means that two different CustomIdChecker conditions will never be equal,
4086        // but that's acceptable since we cannot do better, and only expected to be used
4087        // for logging and profiling purposes.
4088        Arc::ptr_eq(&self.0, &other.0)
4089    }
4090}
4091
4092impl Eq for CustomIdChecker {}
4093
4094impl Condition {
4095    pub fn new_nested(key: JsonPath, filter: Filter) -> Self {
4096        Self::Nested(NestedCondition {
4097            nested: Nested { key, filter },
4098        })
4099    }
4100
4101    pub fn size_estimation(&self) -> usize {
4102        match self {
4103            Condition::Field(field_condition) => field_condition.input_size(),
4104            Condition::HasId(has_id_condition) => has_id_condition.has_id.len(),
4105            Condition::Filter(filter) => filter.max_condition_input_size(),
4106            Condition::Nested(nested) => nested.filter().max_condition_input_size(),
4107            Condition::IsEmpty(_)
4108            | Condition::IsNull(_)
4109            | Condition::HasVector(_)
4110            | Condition::Slice(_)
4111            | Condition::CustomIdChecker(_) => 0,
4112        }
4113    }
4114
4115    pub fn sub_conditions_count(&self) -> usize {
4116        match self {
4117            Condition::Nested(nested_condition) => {
4118                nested_condition.filter().total_conditions_count()
4119            }
4120            Condition::Filter(filter) => filter.total_conditions_count(),
4121            Condition::Field(_)
4122            | Condition::IsEmpty(_)
4123            | Condition::IsNull(_)
4124            | Condition::CustomIdChecker(_)
4125            | Condition::HasId(_)
4126            | Condition::HasVector(_)
4127            | Condition::Slice(_) => 1,
4128        }
4129    }
4130
4131    pub fn targeted_key(&self) -> Option<PayloadKeyType> {
4132        match self {
4133            Condition::Field(field_condition) => Some(field_condition.key.clone()),
4134            Condition::IsEmpty(is_empty_condition) => Some(is_empty_condition.is_empty.key.clone()),
4135            Condition::IsNull(is_null_condition) => Some(is_null_condition.is_null.key.clone()),
4136            Condition::Nested(nested_condition) => Some(nested_condition.array_key()),
4137            Condition::Filter(filter) => filter.iter_conditions().find_map(|c| c.targeted_key()),
4138            Condition::HasId(_)
4139            | Condition::HasVector(_)
4140            | Condition::Slice(_)
4141            | Condition::CustomIdChecker(_) => None,
4142        }
4143    }
4144}
4145
4146// The validator crate does not support deriving for enums.
4147impl Validate for Condition {
4148    fn validate(&self) -> Result<(), ValidationErrors> {
4149        match self {
4150            Condition::HasId(_)
4151            | Condition::IsEmpty(_)
4152            | Condition::IsNull(_)
4153            | Condition::HasVector(_) => Ok(()),
4154            Condition::Field(field_condition) => field_condition.validate(),
4155            Condition::Slice(slice_condition) => slice_condition.validate(),
4156            Condition::Nested(nested_condition) => nested_condition.validate(),
4157            Condition::Filter(filter) => filter.validate(),
4158            Condition::CustomIdChecker(_) => Ok(()),
4159        }
4160    }
4161}
4162
4163pub trait CustomIdCheckerCondition: fmt::Debug {
4164    fn estimate_cardinality(&self, points: usize) -> CardinalityEstimation;
4165    fn check(&self, point_id: ExtendedPointId) -> bool;
4166}
4167
4168/// Options for specifying which payload to include or not
4169#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Hash)]
4170#[serde(untagged, rename_all = "snake_case")]
4171#[serde(
4172    expecting = "Expected a boolean, an array of strings, or an object with an include/exclude field"
4173)]
4174pub enum WithPayloadInterface {
4175    /// If `true` - return all payload,
4176    /// If `false` - do not return payload
4177    Bool(bool),
4178    /// Specify which fields to return
4179    Fields(Vec<JsonPath>),
4180    /// Specify included or excluded fields
4181    Selector(PayloadSelector),
4182}
4183
4184impl From<bool> for WithPayloadInterface {
4185    fn from(b: bool) -> Self {
4186        WithPayloadInterface::Bool(b)
4187    }
4188}
4189
4190impl Default for WithPayloadInterface {
4191    fn default() -> Self {
4192        WithPayloadInterface::Bool(false)
4193    }
4194}
4195
4196/// Options for specifying which vector to include
4197#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
4198#[serde(untagged, rename_all = "snake_case")]
4199#[serde(expecting = "Expected a boolean, or an array of strings")]
4200pub enum WithVector {
4201    /// If `true` - return all vector,
4202    /// If `false` - do not return vector
4203    Bool(bool),
4204    /// Specify which vector to return
4205    Selector(Vec<VectorNameBuf>),
4206}
4207
4208impl WithVector {
4209    pub fn is_enabled(&self) -> bool {
4210        match self {
4211            WithVector::Bool(b) => *b,
4212            WithVector::Selector(_) => true,
4213        }
4214    }
4215
4216    /// Merges two `WithVector` options, additively.
4217    pub fn merge(&self, other: &WithVector) -> WithVector {
4218        match (self, other) {
4219            // if any is true, then true
4220            (WithVector::Bool(true), _) => WithVector::Bool(true),
4221            (_, WithVector::Bool(true)) => WithVector::Bool(true),
4222
4223            // if both are false, then false
4224            (WithVector::Bool(false), WithVector::Bool(false)) => WithVector::Bool(false),
4225
4226            // merge selectors
4227            (WithVector::Selector(s1), WithVector::Selector(s2)) => {
4228                WithVector::Selector(s1.iter().chain(s2).unique().cloned().collect())
4229            }
4230
4231            // use selector from the other option
4232            (WithVector::Bool(false), WithVector::Selector(s)) => WithVector::Selector(s.clone()),
4233            (WithVector::Selector(s), WithVector::Bool(false)) => WithVector::Selector(s.clone()),
4234        }
4235    }
4236}
4237
4238impl From<bool> for WithVector {
4239    fn from(b: bool) -> Self {
4240        WithVector::Bool(b)
4241    }
4242}
4243
4244impl From<VectorNameBuf> for WithVector {
4245    fn from(name: VectorNameBuf) -> Self {
4246        WithVector::Selector(vec![name])
4247    }
4248}
4249
4250impl Default for WithVector {
4251    fn default() -> Self {
4252        WithVector::Bool(false)
4253    }
4254}
4255
4256impl WithPayloadInterface {
4257    pub fn is_required(&self) -> bool {
4258        match self {
4259            WithPayloadInterface::Bool(b) => *b,
4260            WithPayloadInterface::Fields(_) | WithPayloadInterface::Selector(_) => true,
4261        }
4262    }
4263}
4264
4265impl From<bool> for WithPayload {
4266    fn from(x: bool) -> Self {
4267        WithPayload {
4268            enable: x,
4269            payload_selector: None,
4270        }
4271    }
4272}
4273
4274impl From<WithPayloadInterface> for WithPayload {
4275    fn from(interface: WithPayloadInterface) -> Self {
4276        match interface {
4277            WithPayloadInterface::Bool(enable) => WithPayload {
4278                enable,
4279                payload_selector: None,
4280            },
4281            WithPayloadInterface::Fields(fields) => WithPayload {
4282                enable: true,
4283                payload_selector: Some(PayloadSelector::new_include(fields)),
4284            },
4285            WithPayloadInterface::Selector(selector) => WithPayload {
4286                enable: true,
4287                payload_selector: Some(selector),
4288            },
4289        }
4290    }
4291}
4292
4293impl From<&WithPayloadInterface> for WithPayload {
4294    fn from(interface: &WithPayloadInterface) -> Self {
4295        WithPayload::from(interface.clone())
4296    }
4297}
4298
4299#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
4300#[serde(deny_unknown_fields, rename_all = "snake_case")]
4301pub struct PayloadSelectorInclude {
4302    /// Only include this payload keys
4303    pub include: Vec<PayloadKeyType>,
4304}
4305
4306impl PayloadSelectorInclude {
4307    pub fn new(include: Vec<PayloadKeyType>) -> Self {
4308        Self { include }
4309    }
4310}
4311
4312#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
4313#[serde(deny_unknown_fields, rename_all = "snake_case")]
4314pub struct PayloadSelectorExclude {
4315    /// Exclude this fields from returning payload
4316    pub exclude: Vec<PayloadKeyType>,
4317}
4318
4319impl PayloadSelectorExclude {
4320    pub fn new(exclude: Vec<PayloadKeyType>) -> Self {
4321        Self { exclude }
4322    }
4323}
4324
4325/// Specifies how to treat payload selector
4326#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq, Eq, Hash)]
4327#[serde(untagged, rename_all = "snake_case")]
4328pub enum PayloadSelector {
4329    /// Include only this fields into response payload
4330    Include(PayloadSelectorInclude),
4331    /// Exclude this fields from result payload. Keep all other fields.
4332    Exclude(PayloadSelectorExclude),
4333}
4334
4335impl From<PayloadSelectorExclude> for WithPayloadInterface {
4336    fn from(selector: PayloadSelectorExclude) -> Self {
4337        WithPayloadInterface::Selector(PayloadSelector::Exclude(selector))
4338    }
4339}
4340
4341impl From<PayloadSelectorInclude> for WithPayloadInterface {
4342    fn from(selector: PayloadSelectorInclude) -> Self {
4343        WithPayloadInterface::Selector(PayloadSelector::Include(selector))
4344    }
4345}
4346
4347impl PayloadSelector {
4348    pub fn new_include(vecs_payload_key_type: Vec<PayloadKeyType>) -> Self {
4349        PayloadSelector::Include(PayloadSelectorInclude {
4350            include: vecs_payload_key_type,
4351        })
4352    }
4353
4354    pub fn new_exclude(vecs_payload_key_type: Vec<PayloadKeyType>) -> Self {
4355        PayloadSelector::Exclude(PayloadSelectorExclude {
4356            exclude: vecs_payload_key_type,
4357        })
4358    }
4359
4360    /// Process payload selector
4361    pub fn process(&self, x: Payload) -> Payload {
4362        match self {
4363            PayloadSelector::Include(selector) => JsonPath::value_filter(&x.0, |key, _| {
4364                selector
4365                    .include
4366                    .iter()
4367                    .any(|pattern| pattern.check_include_pattern(key))
4368            })
4369            .into(),
4370            PayloadSelector::Exclude(selector) => JsonPath::value_filter(&x.0, |key, _| {
4371                selector
4372                    .exclude
4373                    .iter()
4374                    .all(|pattern| !pattern.check_exclude_pattern(key))
4375            })
4376            .into(),
4377        }
4378    }
4379}
4380
4381#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Default, PartialEq, Eq)]
4382#[serde(deny_unknown_fields, rename_all = "snake_case")]
4383pub struct WithPayload {
4384    /// Enable return payloads or not
4385    pub enable: bool,
4386    /// Filter include and exclude payloads
4387    pub payload_selector: Option<PayloadSelector>,
4388}
4389
4390#[derive(
4391    Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, PartialEq, Eq, Default, Hash,
4392)]
4393#[serde(rename_all = "snake_case")]
4394pub struct MinShould {
4395    #[validate(nested)]
4396    pub conditions: Vec<Condition>,
4397    // Require `min_count > 0`. An empty condition list with `min_count == 0`
4398    // matches every point, which is a footgun; reject it at the API boundary.
4399    // See <https://github.com/qdrant/qdrant/pull/9401>.
4400    #[validate(range(min = 1, message = "min_count must be greater than 0"))]
4401    pub min_count: usize,
4402}
4403
4404#[derive(
4405    Debug, Deserialize, Serialize, JsonSchema, Validate, Clone, PartialEq, Eq, Default, Hash,
4406)]
4407#[serde(deny_unknown_fields, rename_all = "snake_case")]
4408pub struct Filter {
4409    /// At least one of those conditions should match
4410    #[validate(nested)]
4411    #[serde(
4412        default,
4413        with = "MaybeOneOrMany",
4414        skip_serializing_if = "Option::is_none"
4415    )]
4416    #[schemars(with = "MaybeOneOrMany<Condition>")]
4417    pub should: Option<Vec<Condition>>,
4418    /// At least minimum amount of given conditions should match
4419    #[validate(nested)]
4420    #[serde(skip_serializing_if = "Option::is_none")]
4421    pub min_should: Option<MinShould>,
4422    /// All conditions must match
4423    #[validate(nested)]
4424    #[serde(
4425        default,
4426        with = "MaybeOneOrMany",
4427        skip_serializing_if = "Option::is_none"
4428    )]
4429    #[schemars(with = "MaybeOneOrMany<Condition>")]
4430    pub must: Option<Vec<Condition>>,
4431    /// All conditions must NOT match
4432    #[validate(nested)]
4433    #[serde(
4434        default,
4435        with = "MaybeOneOrMany",
4436        skip_serializing_if = "Option::is_none"
4437    )]
4438    #[schemars(with = "MaybeOneOrMany<Condition>")]
4439    pub must_not: Option<Vec<Condition>>,
4440}
4441
4442impl Filter {
4443    pub fn new() -> Self {
4444        Filter {
4445            should: None,
4446            min_should: None,
4447            must: None,
4448            must_not: None,
4449        }
4450    }
4451
4452    pub fn new_should(condition: Condition) -> Self {
4453        Filter {
4454            should: Some(vec![condition]),
4455            min_should: None,
4456            must: None,
4457            must_not: None,
4458        }
4459    }
4460
4461    pub fn new_min_should(min_should: MinShould) -> Self {
4462        Filter {
4463            should: None,
4464            min_should: Some(min_should),
4465            must: None,
4466            must_not: None,
4467        }
4468    }
4469
4470    pub fn new_must(condition: Condition) -> Self {
4471        Filter {
4472            should: None,
4473            min_should: None,
4474            must: Some(vec![condition]),
4475            must_not: None,
4476        }
4477    }
4478
4479    pub fn new_must_not(condition: Condition) -> Self {
4480        Filter {
4481            should: None,
4482            min_should: None,
4483            must: None,
4484            must_not: Some(vec![condition]),
4485        }
4486    }
4487
4488    /// Create an extended filtering condition, which would also include filter by given list of IDs.
4489    pub fn with_point_ids(self, ids: impl IntoIterator<Item = PointIdType>) -> Filter {
4490        let has_id_condition: HasIdCondition = ids.into_iter().collect();
4491
4492        let Filter {
4493            should,
4494            min_should,
4495            must,
4496            must_not,
4497        } = self;
4498
4499        let new_must = match must {
4500            Some(mut must) => {
4501                must.push(Condition::HasId(has_id_condition));
4502                Some(must)
4503            }
4504            None => Some(vec![Condition::HasId(has_id_condition)]),
4505        };
4506
4507        Filter {
4508            should,
4509            min_should,
4510            must: new_must,
4511            must_not,
4512        }
4513    }
4514
4515    pub fn merge(&self, other: &Filter) -> Filter {
4516        self.clone().merge_owned(other.clone())
4517    }
4518
4519    pub fn merge_owned(self, other: Filter) -> Filter {
4520        let merge_component = |this, other| -> Option<Vec<Condition>> {
4521            match (this, other) {
4522                (None, None) => None,
4523                (Some(this), None) => Some(this),
4524                (None, Some(other)) => Some(other),
4525                (Some(mut this), Some(mut other)) => {
4526                    this.append(&mut other);
4527                    Some(this)
4528                }
4529            }
4530        };
4531        Filter {
4532            should: merge_component(self.should, other.should),
4533            min_should: {
4534                match (self.min_should, other.min_should) {
4535                    (None, None) => None,
4536                    (Some(this), None) => Some(this),
4537                    (None, Some(other)) => Some(other),
4538                    (Some(mut this), Some(mut other)) => {
4539                        this.conditions.append(&mut other.conditions);
4540
4541                        // The union of conditions should be able to have at least the bigger of the two min_counts
4542                        this.min_count = this.min_count.max(other.min_count);
4543
4544                        Some(this)
4545                    }
4546                }
4547            },
4548            must: merge_component(self.must, other.must),
4549            must_not: merge_component(self.must_not, other.must_not),
4550        }
4551    }
4552
4553    pub fn merge_opts(this: Option<Self>, other: Option<Self>) -> Option<Self> {
4554        match (this, other) {
4555            (None, None) => None,
4556            (Some(this), None) => Some(this),
4557            (None, Some(other)) => Some(other),
4558            (Some(this), Some(other)) => Some(this.merge_owned(other)),
4559        }
4560    }
4561
4562    pub fn iter_conditions(&self) -> impl Iterator<Item = &Condition> {
4563        self.must
4564            .iter()
4565            .flatten()
4566            .chain(self.must_not.iter().flatten())
4567            .chain(self.should.iter().flatten())
4568            .chain(self.min_should.iter().flat_map(|i| &i.conditions))
4569    }
4570
4571    /// Returns the total amount of conditions of the filter, including all nested filter.
4572    pub fn total_conditions_count(&self) -> usize {
4573        fn count_all_conditions(field: Option<&Vec<Condition>>) -> usize {
4574            field
4575                .map(|i| i.iter().map(|j| j.sub_conditions_count()).sum::<usize>())
4576                .unwrap_or(0)
4577        }
4578
4579        count_all_conditions(self.should.as_ref())
4580            + count_all_conditions(self.min_should.as_ref().map(|i| &i.conditions))
4581            + count_all_conditions(self.must.as_ref())
4582            + count_all_conditions(self.must_not.as_ref())
4583    }
4584
4585    /// Returns the size of the largest condition.
4586    pub fn max_condition_input_size(&self) -> usize {
4587        self.iter_conditions()
4588            .map(|i| i.size_estimation())
4589            .max()
4590            .unwrap_or(0)
4591    }
4592}
4593
4594#[derive(Debug, Clone, Copy, Eq, PartialEq)]
4595pub enum SnapshotFormat {
4596    /// Created by Qdrant `<0.11.0`.
4597    ///
4598    /// The collection snapshot contains nested tar archives for segments.
4599    /// Segment tar archives contain a plain copy of the segment directory.
4600    ///
4601    /// ```plaintext
4602    /// ./0/segments/
4603    /// ├── 0b31e274-dc65-40e4-8493-67ebed4bcf10.tar
4604    /// │   ├── segment.json
4605    /// │   ├── CURRENT
4606    /// │   ├── 000009.sst
4607    /// │   ├── 000010.sst
4608    /// │   └── …
4609    /// ├── 1d6c96ec-7965-491a-9c45-362d55361e9b.tar
4610    /// └── …
4611    /// ```
4612    Ancient,
4613    /// Qdrant `>=0.11.0` `<=1.13` (and maybe even later).
4614    ///
4615    /// The collection snapshot contains nested tar archives for segments.
4616    /// Distinguished by a single top-level directory `snapshot` in each segment
4617    /// tar archive. RocksDB data stored as backups and requires unpacking
4618    /// procedure.
4619    ///
4620    /// ```plaintext
4621    /// ./0/segments/
4622    /// ├── 0b31e274-dc65-40e4-8493-67ebed4bcf10.tar
4623    /// │   └── snapshot/                               # single top-level dir
4624    /// │       ├── db_backup/                          # rockdb backup
4625    /// │       │   ├── meta/
4626    /// │       │   ├── private/
4627    /// │       │   └── shared_checksum/
4628    /// │       ├── payload_index_db_backup             # rocksdb backup
4629    /// │       │   ├── meta/
4630    /// │       │   ├── private/
4631    /// │       │   └── shared_checksum/
4632    /// │       └── files/                              # regular files
4633    /// │           ├── segment.json
4634    /// │           └── …
4635    /// ├── 1d6c96ec-7965-491a-9c45-362d55361e9b.tar
4636    /// └── …
4637    /// ```
4638    Regular,
4639    /// New experimental format.
4640    ///
4641    /// ```plaintext
4642    /// ./0/segments/
4643    /// ├── 0b31e274-dc65-40e4-8493-67ebed4bcf10/
4644    /// │   ├── db_backup/                              # rockdb backup
4645    /// │   │   ├── meta/
4646    /// │   │   ├── private/
4647    /// │   │   └── shared_checksum/
4648    /// │   ├── payload_index_db_backup                 # rocksdb backup
4649    /// │   │   ├── meta/
4650    /// │   │   ├── private/
4651    /// │   │   └── shared_checksum/
4652    /// │   └── files/                                  # regular files
4653    /// │       ├── segment.json
4654    /// │       └── …
4655    /// ├── 1d6c96ec-7965-491a-9c45-362d55361e9b/
4656    /// └── …
4657    /// ```
4658    Streamable,
4659}
4660
4661#[cfg(test)]
4662pub(crate) mod test_utils {
4663    use super::{GeoLineString, GeoPoint, GeoPolygon};
4664
4665    pub fn build_polygon(exterior_points: Vec<(f64, f64)>) -> GeoPolygon {
4666        let exterior_line = GeoLineString {
4667            points: exterior_points
4668                .into_iter()
4669                .map(|(lon, lat)| GeoPoint::new_unchecked(lon, lat))
4670                .collect(),
4671        };
4672
4673        GeoPolygon {
4674            exterior: exterior_line,
4675            interiors: None,
4676        }
4677    }
4678
4679    pub fn build_polygon_with_interiors(
4680        exterior_points: Vec<(f64, f64)>,
4681        interiors_points: Vec<Vec<(f64, f64)>>,
4682    ) -> GeoPolygon {
4683        let exterior_line = GeoLineString {
4684            points: exterior_points
4685                .into_iter()
4686                .map(|(lon, lat)| GeoPoint::new_unchecked(lon, lat))
4687                .collect(),
4688        };
4689
4690        let interior_lines = Some(
4691            interiors_points
4692                .into_iter()
4693                .map(|points| GeoLineString {
4694                    points: points
4695                        .into_iter()
4696                        .map(|(lon, lat)| GeoPoint::new_unchecked(lon, lat))
4697                        .collect(),
4698                })
4699                .collect(),
4700        );
4701
4702        GeoPolygon {
4703            exterior: exterior_line,
4704            interiors: interior_lines,
4705        }
4706    }
4707}
4708
4709#[cfg(test)]
4710mod tests {
4711    #![expect(clippy::wildcard_enum_match_arm, reason = "test code")]
4712
4713    use std::assert_matches;
4714
4715    use itertools::Itertools;
4716    use rstest::rstest;
4717    use serde_json;
4718    use validator::Validate;
4719
4720    use super::test_utils::build_polygon_with_interiors;
4721    use super::*;
4722
4723    #[test]
4724    fn test_memory_legacy_mapping() {
4725        // Mmap-backed components: on-disk data loads lazily, in-RAM data is populated mmap
4726        assert_eq!(Memory::from_on_disk(true), Memory::Cold);
4727        assert_eq!(Memory::from_on_disk(false), Memory::Cached);
4728        // Heap-backed components: in-RAM data is a heap structure, never evicted
4729        assert_eq!(Memory::from_on_disk_heap(true), Memory::Cold);
4730        assert_eq!(Memory::from_on_disk_heap(false), Memory::Pinned);
4731
4732        assert!(Memory::Cold.is_on_disk());
4733        assert!(!Memory::Cached.is_on_disk());
4734        assert!(!Memory::Pinned.is_on_disk());
4735
4736        assert!(!Memory::Cold.is_heap());
4737        assert!(!Memory::Cached.is_heap());
4738        assert!(Memory::Pinned.is_heap());
4739
4740        assert!(!Memory::Cold.populate_on_open());
4741        assert!(Memory::Cached.populate_on_open());
4742        assert!(Memory::Pinned.populate_on_open());
4743    }
4744
4745    #[test]
4746    fn test_memory_resolve_precedence() {
4747        // Explicit `memory` always wins over the legacy parameter
4748        assert_eq!(
4749            Memory::resolve(Some(Memory::Cached), Some(Memory::Cold)),
4750            Some(Memory::Cached),
4751        );
4752        // Legacy parameter is used when `memory` is not set
4753        assert_eq!(
4754            Memory::resolve(None, Some(Memory::Cold)),
4755            Some(Memory::Cold),
4756        );
4757        assert_eq!(
4758            Memory::resolve(Some(Memory::Pinned), None),
4759            Some(Memory::Pinned)
4760        );
4761        assert_eq!(Memory::resolve(None, None), None);
4762    }
4763
4764    #[test]
4765    fn test_hnsw_memory_placement() {
4766        let mut config = HnswConfig::default();
4767        assert_eq!(config.memory_placement(), Memory::Cached);
4768
4769        config.on_disk = Some(true);
4770        assert_eq!(config.memory_placement(), Memory::Cold);
4771
4772        // Explicit `memory` overrides the deprecated `on_disk` flag
4773        config.memory = Some(Memory::Pinned);
4774        assert_eq!(config.memory_placement(), Memory::Pinned);
4775    }
4776
4777    #[test]
4778    fn test_hnsw_memory_mismatch_no_rebuild_for_same_placement() {
4779        // Expressing the same effective placement through `memory` instead of the deprecated
4780        // `on_disk` flag must not trigger a rebuild
4781        let legacy = HnswConfig {
4782            on_disk: Some(true),
4783            ..HnswConfig::default()
4784        };
4785        let explicit = HnswConfig {
4786            on_disk: Some(true),
4787            memory: Some(Memory::Cold),
4788            ..HnswConfig::default()
4789        };
4790        assert!(!legacy.mismatch_requires_rebuild(&explicit));
4791
4792        // Changing the effective placement does require a rebuild
4793        let cached = HnswConfig {
4794            memory: Some(Memory::Cached),
4795            ..legacy
4796        };
4797        assert!(legacy.mismatch_requires_rebuild(&cached));
4798    }
4799
4800    #[test]
4801    fn test_quantization_memory_placement() {
4802        let mut scalar = ScalarQuantizationConfig {
4803            r#type: ScalarType::Int8,
4804            quantile: None,
4805            always_ram: None,
4806            memory: None,
4807        };
4808        // Unset: follow the original vector storage placement
4809        assert_eq!(scalar.memory_placement(), None);
4810        // Legacy `always_ram=false` also means "follow storage"
4811        scalar.always_ram = Some(false);
4812        assert_eq!(scalar.memory_placement(), None);
4813        // Legacy `always_ram=true` means pinned in RAM
4814        scalar.always_ram = Some(true);
4815        assert_eq!(scalar.memory_placement(), Some(Memory::Pinned));
4816        // Explicit `memory` overrides the deprecated flag
4817        scalar.memory = Some(Memory::Cached);
4818        assert_eq!(scalar.memory_placement(), Some(Memory::Cached));
4819    }
4820
4821    #[test]
4822    fn test_storage_type_memory_mapping() {
4823        assert_eq!(
4824            VectorStorageType::appendable_from_memory(Memory::Cold),
4825            VectorStorageType::ChunkedMmap,
4826        );
4827        assert_eq!(
4828            VectorStorageType::appendable_from_memory(Memory::Cached),
4829            VectorStorageType::InRamChunkedMmap,
4830        );
4831        assert_eq!(
4832            VectorStorageType::immutable_from_memory(Memory::Cold),
4833            VectorStorageType::Mmap,
4834        );
4835        assert_eq!(
4836            VectorStorageType::immutable_from_memory(Memory::Cached),
4837            VectorStorageType::InRamMmap,
4838        );
4839
4840        assert_eq!(
4841            PayloadStorageType::from_memory(Memory::Cold),
4842            PayloadStorageType::Mmap
4843        );
4844        assert_eq!(
4845            PayloadStorageType::from_memory(Memory::Cached),
4846            PayloadStorageType::InRamMmap,
4847        );
4848        assert_eq!(PayloadStorageType::Mmap.memory(), Memory::Cold);
4849        assert_eq!(PayloadStorageType::InRamMmap.memory(), Memory::Cached);
4850    }
4851
4852    #[test]
4853    fn test_payload_field_index_memory_placement() {
4854        let mut params = KeywordIndexParams::default();
4855        // Legacy in-RAM field indexes are heap structures: pinned by default
4856        assert_eq!(
4857            PayloadSchemaParams::Keyword(params.clone()).memory_placement(),
4858            Memory::Pinned,
4859        );
4860        params.on_disk = Some(true);
4861        assert_eq!(
4862            PayloadSchemaParams::Keyword(params.clone()).memory_placement(),
4863            Memory::Cold,
4864        );
4865        // Explicit `memory` overrides the deprecated `on_disk` flag
4866        params.memory = Some(Memory::Cached);
4867        assert_eq!(
4868            PayloadSchemaParams::Keyword(params).memory_placement(),
4869            Memory::Cached,
4870        );
4871    }
4872
4873    #[test]
4874    fn test_search_params_rejects_zero_hnsw_ef() {
4875        let params = SearchParams {
4876            hnsw_ef: Some(0),
4877            ..Default::default()
4878        };
4879
4880        let err = params.validate().unwrap_err().to_string();
4881        assert!(err.contains("hnsw_ef"), "error was: {err}");
4882
4883        let params = SearchParams {
4884            hnsw_ef: Some(1),
4885            ..Default::default()
4886        };
4887        params.validate().unwrap();
4888    }
4889
4890    fn match_condition(key: &str, value: &str) -> Condition {
4891        Condition::Field(FieldCondition::new_match(
4892            JsonPath::new(key),
4893            value.to_string().into(),
4894        ))
4895    }
4896
4897    #[test]
4898    fn test_idf_params_json_shapes() {
4899        // Explicit global shorthand.
4900        let params: IdfParams = serde_json::from_str(r#""global""#).unwrap();
4901        assert_eq!(params, IdfParams::Scope(IdfScope::Global));
4902        assert_eq!(params.corpus(), None);
4903        params.validate().unwrap();
4904
4905        // Corpus object form.
4906        let params: IdfParams = serde_json::from_str(
4907            r#"{"corpus": {"must": [{"key": "tenant", "match": {"value": "acme"}}]}}"#,
4908        )
4909        .unwrap();
4910        let expected_corpus = Filter::new_must(match_condition("tenant", "acme"));
4911        assert_eq!(params.corpus(), Some(&expected_corpus));
4912        params.validate().unwrap();
4913
4914        // Round-trip both shapes.
4915        for params in [
4916            IdfParams::Scope(IdfScope::Global),
4917            IdfParams::Corpus(IdfCorpusParams {
4918                corpus: expected_corpus,
4919            }),
4920        ] {
4921            let json = serde_json::to_string(&params).unwrap();
4922            let restored: IdfParams = serde_json::from_str(&json).unwrap();
4923            assert_eq!(params, restored);
4924        }
4925    }
4926
4927    #[test]
4928    fn test_idf_corpus_accepts_any_filter() {
4929        let corpus_params = |corpus: Filter| IdfParams::Corpus(IdfCorpusParams { corpus });
4930
4931        // The corpus is a regular filter — any valid filter shape is accepted.
4932        corpus_params(Filter::new_must(match_condition("tenant", "acme")))
4933            .validate()
4934            .unwrap();
4935        corpus_params(Filter::new_should(match_condition("tenant", "acme")))
4936            .validate()
4937            .unwrap();
4938        corpus_params(Filter::new_must_not(match_condition("tenant", "acme")))
4939            .validate()
4940            .unwrap();
4941        corpus_params(Filter::new_must(Condition::Field(
4942            FieldCondition::new_range(
4943                JsonPath::new("year"),
4944                Range {
4945                    lt: None,
4946                    gt: None,
4947                    gte: Some(OrderedFloat(2024.0)),
4948                    lte: None,
4949                },
4950            ),
4951        )))
4952        .validate()
4953        .unwrap();
4954    }
4955
4956    #[test]
4957    #[ignore]
4958    fn test_rmp_vs_cbor_deserialize() {
4959        let payload = payload_json! {"payload_key": "payload_value"};
4960        let raw = rmp_serde::to_vec(&payload).unwrap();
4961        let de_record: Payload = serde_cbor::from_slice(&raw).unwrap();
4962        eprintln!("payload = {payload:#?}");
4963        eprintln!("de_record = {de_record:#?}");
4964    }
4965
4966    #[rstest]
4967    #[case::rfc_3339("2020-03-01T00:00:00Z")]
4968    #[case::rfc_3339_custom_tz("2020-03-01T00:00:00-09:00")]
4969    #[case::rfc_3339_custom_tz_no_colon("2020-03-01 00:00:00-0900")]
4970    #[case::rfc_3339_custom_tz_no_colon_and_t("2020-03-01T00:00:00-0900")]
4971    #[case::rfc_3339_custom_tz_no_minutes("2020-03-01 00:00:00-09")]
4972    #[case::rfc_3339_and_decimals("2020-03-01T00:00:00.123456Z")]
4973    #[case::without_z("2020-03-01T00:00:00")]
4974    #[case::without_z_and_decimals("2020-03-01T00:00:00.12")]
4975    #[case::space_sep_without_z("2020-03-01 00:00:00")]
4976    #[case::space_sep_without_z_and_decimals("2020-03-01 00:00:00.123456")]
4977    #[case::t_sep_without_seconds("2020-03-01T00:00")]
4978    #[case::space_sep_without_seconds("2020-03-01 00:00")]
4979    #[case::date_only("2020-03-01")]
4980    fn test_datetime_deserialization(#[case] datetime: &str) {
4981        let datetime = DateTimePayloadType::from_str(datetime).unwrap();
4982        let serialized = serde_json::to_string(&datetime).unwrap();
4983        let deserialized: DateTimePayloadType = serde_json::from_str(&serialized).unwrap();
4984        assert_eq!(datetime, deserialized);
4985    }
4986
4987    #[test]
4988    fn test_datetime_deserialization_equivalency() {
4989        let datetime_str = "2020-03-01T01:02:03.123456Z";
4990        let datetime_str_no_z = "2020-03-01T01:02:03.123456";
4991        let datetime = DateTimePayloadType::from_str(datetime_str).unwrap();
4992        let datetime_no_z = DateTimePayloadType::from_str(datetime_str_no_z).unwrap();
4993
4994        // Having or not the Z at the end of the string both mean UTC time
4995        assert_eq!(datetime.timestamp(), datetime_no_z.timestamp());
4996    }
4997
4998    #[test]
4999    fn test_invalid_datetime_range_returns_clear_rfc3339_error() {
5000        let json = r#"{
5001            "key": "created_at",
5002            "range": {
5003                "gte": "2014-01-01T00:00:00BAD"
5004            }
5005        }"#;
5006
5007        let err = serde_json::from_str::<Condition>(json)
5008            .unwrap_err()
5009            .to_string();
5010
5011        assert!(err.contains("RFC3339"), "err was: {err}");
5012        assert!(err.contains("2014-01-01T00:00:00BAD"), "err was: {err}");
5013        assert!(err.contains("Example"), "err was: {err}");
5014    }
5015
5016    #[test]
5017    fn test_condition_non_string_key_returns_clear_error() {
5018        for json in [
5019            r#"{"key": null, "match": {"value": "A"}}"#,
5020            r#"{"key": 42, "match": {"value": "A"}}"#,
5021            r#"{"key": ["a"], "match": {"value": "A"}}"#,
5022        ] {
5023            let err = serde_json::from_str::<Condition>(json)
5024                .unwrap_err()
5025                .to_string();
5026
5027            assert!(
5028                err.contains("Filter condition 'key' must be a string"),
5029                "err was: {err}"
5030            );
5031        }
5032    }
5033
5034    /// Regression test: DateTimePayloadType binary serialization roundtrip.
5035    /// Ensures DateTimePayloadType parses binary-encoded RFC3339 strings.
5036    #[test]
5037    fn test_datetime_payload_type_binary_roundtrip() {
5038        let original = DateTimePayloadType::from_str("2024-06-15T12:30:45Z").unwrap();
5039
5040        // rmp-serde uses non-human-readable format
5041        let binary = rmp_serde::to_vec(&original).expect("serialize");
5042        let restored: DateTimePayloadType = rmp_serde::from_slice(&binary).expect("deserialize");
5043
5044        assert_eq!(original, restored);
5045    }
5046
5047    /// Regression test: RangeInterface with datetime binary roundtrip.
5048    /// Ensures the RangeInterface datetime deserialization works in binary.
5049    #[test]
5050    fn test_range_interface_datetime_binary_roundtrip() {
5051        let dt_gte = DateTimePayloadType::from_str("2024-01-01T00:00:00Z").unwrap();
5052        let dt_lte = DateTimePayloadType::from_str("2024-12-31T23:59:59Z").unwrap();
5053
5054        let range = RangeInterface::DateTime(Range {
5055            lt: None,
5056            gt: None,
5057            gte: Some(dt_gte),
5058            lte: Some(dt_lte),
5059        });
5060
5061        // rmp-serde uses non-human-readable format
5062        let binary = rmp_serde::to_vec(&range).expect("serialize");
5063        let restored: RangeInterface = rmp_serde::from_slice(&binary).expect("deserialize");
5064
5065        assert_eq!(range, restored);
5066    }
5067
5068    /// Regression test: Non-FieldCondition JSON deserialization uses ConditionUntagged fallback.
5069    /// Ensures compiler-safe handling of other Condition variants.
5070    #[test]
5071    fn test_condition_json_fallback_to_untagged() {
5072        // IsEmptyCondition (no "key" field at top level, uses "is_empty" instead)
5073        let is_empty_json = r#"{"is_empty": {"key": "optional_field"}}"#;
5074        let condition: Condition = serde_json::from_str(is_empty_json).unwrap();
5075        assert_matches!(condition, Condition::IsEmpty(_));
5076
5077        // HasIdCondition
5078        let has_id_json = r#"{"has_id": [1, 2, 3]}"#;
5079        let condition: Condition = serde_json::from_str(has_id_json).unwrap();
5080        assert_matches!(condition, Condition::HasId(_));
5081
5082        // Nested Filter
5083        let nested_json = r#"{"nested": {"key": "items", "filter": {"must": []}}}"#;
5084        let condition: Condition = serde_json::from_str(nested_json).unwrap();
5085        assert_matches!(condition, Condition::Nested(_));
5086
5087        // SliceCondition
5088        let slice_json = r#"{"slice": {"total": 8, "index": 3}}"#;
5089        let condition: Condition = serde_json::from_str(slice_json).unwrap();
5090        assert_matches!(condition, Condition::Slice(_));
5091    }
5092
5093    #[test]
5094    fn test_slice_condition_serde_and_validation() {
5095        let condition: Condition =
5096            serde_json::from_str(r#"{"slice": {"total": 8, "index": 3}}"#).unwrap();
5097        let Condition::Slice(slice_condition) = &condition else {
5098            panic!("expected slice condition, got {condition:?}");
5099        };
5100        assert_eq!(slice_condition.slice.total.get(), 8);
5101        assert_eq!(slice_condition.slice.index, 3);
5102        condition.validate().unwrap();
5103
5104        let serialized = serde_json::to_value(&condition).unwrap();
5105        assert_eq!(
5106            serialized,
5107            serde_json::json!({"slice": {"total": 8, "index": 3}}),
5108        );
5109
5110        // Zero total is rejected at deserialization
5111        serde_json::from_str::<SliceCondition>(r#"{"slice": {"total": 0, "index": 0}}"#)
5112            .unwrap_err();
5113
5114        // Out-of-range index is rejected by validation
5115        let condition: Condition =
5116            serde_json::from_str(r#"{"slice": {"total": 4, "index": 4}}"#).unwrap();
5117        condition.validate().unwrap_err();
5118    }
5119
5120    /// Frozen public contract: SipHash-2-4 with a zero key over canonical id
5121    /// bytes (8 LE bytes for numeric ids, 16 RFC 4122 bytes for UUIDs).
5122    /// Clients reproduce this hash to predict slice membership locally — if
5123    /// this test fails, slice membership visibly changed for every existing
5124    /// query and the change must be reverted.
5125    #[test]
5126    fn test_slice_point_id_hash_contract() {
5127        // Vectors independently reproduced with a reference SipHash-2-4
5128        // implementation outside this codebase.
5129        let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
5130        assert_eq!(
5131            slice_point_id_hash(ExtendedPointId::NumId(0)),
5132            0xe849_e8bb_6ffe_2567,
5133        );
5134        assert_eq!(
5135            slice_point_id_hash(ExtendedPointId::NumId(42)),
5136            0x0fc2_553f_0761_9dd3,
5137        );
5138        assert_eq!(
5139            slice_point_id_hash(ExtendedPointId::Uuid(uuid)),
5140            0xf5d0_ca21_ba34_d504,
5141        );
5142    }
5143
5144    #[test]
5145    fn test_slice_disjoint_and_exhaustive() {
5146        let numeric_ids = (0..1000_u64).map(ExtendedPointId::NumId);
5147        let uuid_ids = (0..1000_u128).map(|seed| {
5148            ExtendedPointId::Uuid(Uuid::from_u128(
5149                seed.wrapping_mul(0x0123_4567_89ab_cdef_fedc_ba98_7654_3210),
5150            ))
5151        });
5152
5153        for point_id in numeric_ids.chain(uuid_ids) {
5154            // Exactly one slice of each total matches
5155            for total in [1, 2, 5, 10] {
5156                let total = NonZeroU32::new(total).unwrap();
5157                (0..total.get())
5158                    .filter(|&index| Slice { total, index }.check(point_id))
5159                    .exactly_one()
5160                    .unwrap();
5161            }
5162
5163            // A finer slice is a subset of the coarser slice it maps onto:
5164            // hash % 10 == index implies hash % 5 == index % 5
5165            let fine_total = NonZeroU32::new(10).unwrap();
5166            let fine_index = (0..fine_total.get())
5167                .filter(|&index| {
5168                    Slice {
5169                        total: fine_total,
5170                        index,
5171                    }
5172                    .check(point_id)
5173                })
5174                .exactly_one()
5175                .unwrap();
5176            assert!(
5177                Slice {
5178                    total: NonZeroU32::new(5).unwrap(),
5179                    index: fine_index % 5,
5180                }
5181                .check(point_id)
5182            );
5183        }
5184    }
5185
5186    #[test]
5187    fn test_datetime_wrapper_transcoding() {
5188        let expected = DateTimeWrapper(chrono::Utc::now());
5189        let transcoded = DateTimeWrapper::from_str(&expected.to_string()).unwrap();
5190        assert_eq!(expected, transcoded);
5191    }
5192
5193    #[test]
5194    fn test_timezone_ordering() {
5195        let datetimes = [
5196            "2000-06-08 00:18:53+0900",
5197            "2000-06-07 07:25:34-1100",
5198            "2000-07-10T00:18:53+0100",
5199            "2000-07-11 00:25:34-01:00",
5200            "2000-07-11 00:25:35-01",
5201        ];
5202
5203        let sorted_datetimes: Vec<_> = datetimes
5204            .iter()
5205            .enumerate()
5206            .map(|(i, s)| (i, DateTimePayloadType::from_str(s).unwrap()))
5207            .sorted_by_key(|(_, dt)| dt.timestamp())
5208            .collect();
5209
5210        sorted_datetimes
5211            .array_windows()
5212            .for_each(|[(i1, dt1), (i2, dt2)]| {
5213                assert!(
5214                    i1 < i2,
5215                    "i1: {}, dt1: {}, ts1: {}\ni2: {}, dt2: {}, ts2: {}",
5216                    i1,
5217                    dt1.0,
5218                    dt1.timestamp(),
5219                    i2,
5220                    dt2.0,
5221                    dt2.timestamp()
5222                );
5223            });
5224    }
5225
5226    #[test]
5227    fn test_geo_radius_check_point() {
5228        let radius = GeoRadius {
5229            center: GeoPoint::new_unchecked(0.0, 0.0),
5230            radius: OrderedFloat(80000.0),
5231        };
5232
5233        let inside_result = radius.check_point(&GeoPoint::new_unchecked(0.5, 0.5));
5234        assert!(inside_result);
5235
5236        let outside_result = radius.check_point(&GeoPoint::new_unchecked(1.5, 1.5));
5237        assert!(!outside_result);
5238    }
5239
5240    #[test]
5241    fn test_geo_boundingbox_check_point() {
5242        let bounding_box = GeoBoundingBox {
5243            top_left: GeoPoint::new_unchecked(-1.0, 1.0),
5244            bottom_right: GeoPoint::new_unchecked(1.0, -1.0),
5245        };
5246
5247        // haversine distance between (0, 0) and (0.5, 0.5) is 78626.29627999048
5248        let inside_result = bounding_box.check_point(&GeoPoint::new_unchecked(-0.5, 0.5));
5249        assert!(inside_result);
5250
5251        // haversine distance between (0, 0) and (0.5, 0.5) is 235866.91169814655
5252        let outside_result = bounding_box.check_point(&GeoPoint::new_unchecked(1.5, 1.5));
5253        assert!(!outside_result);
5254    }
5255
5256    #[test]
5257    fn test_geo_boundingbox_antimeridian_check_point() {
5258        // Use the bounding box for USA: (74.071028, 167), (18.7763, -66.885417)
5259        let bounding_box = GeoBoundingBox {
5260            top_left: GeoPoint::new_unchecked(167.0, 74.071028),
5261            bottom_right: GeoPoint::new_unchecked(-66.885417, 18.7763),
5262        };
5263
5264        // Test NYC, which is inside the bounding box
5265        let inside_result =
5266            bounding_box.check_point(&GeoPoint::new_unchecked(-73.991516, 40.75798));
5267        assert!(inside_result);
5268
5269        // Test Berlin, which is outside the bounding box
5270        let outside_result = bounding_box.check_point(&GeoPoint::new_unchecked(13.41053, 52.52437));
5271        assert!(!outside_result);
5272    }
5273
5274    #[test]
5275    fn test_geo_polygon_check_point() {
5276        let test_cases = [
5277            // Create a GeoPolygon with a square shape
5278            (
5279                // Exterior
5280                vec![
5281                    (-1.0, -1.0),
5282                    (1.0, -1.0),
5283                    (1.0, 1.0),
5284                    (-1.0, 1.0),
5285                    (-1.0, -1.0),
5286                ],
5287                // Interiors
5288                vec![vec![]],
5289                // Expected results
5290                vec![((0.5, 0.5), true), ((1.5, 1.5), false), ((1.0, 0.0), false)],
5291            ),
5292            // Create a GeoPolygon as a `twisted square`
5293            (
5294                // Exterior
5295                vec![
5296                    (-1.0, -1.0),
5297                    (1.0, 1.0),
5298                    (1.0, -1.0),
5299                    (-1.0, 1.0),
5300                    (-1.0, -1.0),
5301                ],
5302                // Interiors
5303                vec![vec![]],
5304                // Expected results
5305                vec![((0.5, 0.0), true), ((0.0, 0.5), false), ((0.0, 0.0), false)],
5306            ),
5307            // Create a GeoPolygon with an interior (a 'hole' inside the polygon)
5308            (
5309                // Exterior
5310                vec![
5311                    (-1.0, -1.0),
5312                    (1.5, -1.0),
5313                    (1.5, 1.5),
5314                    (-1.0, 1.5),
5315                    (-1.0, -1.0),
5316                ],
5317                // Interiors
5318                vec![vec![
5319                    (-0.5, -0.5),
5320                    (-0.5, 0.5),
5321                    (0.5, 0.5),
5322                    (0.5, -0.5),
5323                    (-0.5, -0.5),
5324                ]],
5325                // Expected results
5326                vec![((0.6, 0.6), true), ((0.0, 0.0), false), ((0.5, 0.5), false)],
5327            ),
5328        ];
5329
5330        for (exterior, interiors, points) in test_cases {
5331            let polygon = build_polygon_with_interiors(exterior, interiors);
5332
5333            for ((lon, lat), expected_result) in points {
5334                let inside_result = polygon
5335                    .convert()
5336                    .check_point(&GeoPoint::new_unchecked(lon, lat));
5337                assert_eq!(inside_result, expected_result);
5338            }
5339        }
5340    }
5341
5342    #[test]
5343    fn test_serialize_query() {
5344        let filter = Filter {
5345            must: Some(vec![Condition::Field(FieldCondition::new_match(
5346                JsonPath::new("hello"),
5347                "world".to_owned().into(),
5348            ))]),
5349            must_not: None,
5350            should: None,
5351            min_should: None,
5352        };
5353        let json = serde_json::to_string_pretty(&filter).unwrap();
5354        eprintln!("{json}")
5355    }
5356
5357    #[test]
5358    fn test_deny_unknown_fields() {
5359        let query1 = r#"
5360         {
5361            "wrong": "query"
5362         }
5363         "#;
5364        let filter: Result<Filter, _> = serde_json::from_str(query1);
5365
5366        assert!(filter.is_err())
5367    }
5368
5369    #[test]
5370    fn test_parse_match_query() {
5371        let query = r#"
5372        {
5373            "key": "hello",
5374            "match": { "value": 42 }
5375        }
5376        "#;
5377        let condition: FieldCondition = serde_json::from_str(query).unwrap();
5378        assert_eq!(
5379            condition.r#match.unwrap(),
5380            Match::Value(MatchValue {
5381                value: ValueVariants::Integer(42)
5382            })
5383        );
5384
5385        let query = r#"
5386        {
5387            "key": "hello",
5388            "match": { "value": true }
5389        }
5390        "#;
5391        let condition: FieldCondition = serde_json::from_str(query).unwrap();
5392        assert_eq!(
5393            condition.r#match.unwrap(),
5394            Match::Value(MatchValue {
5395                value: ValueVariants::Bool(true)
5396            })
5397        );
5398
5399        let query = r#"
5400        {
5401            "key": "hello",
5402            "match": { "value": "world" }
5403        }
5404        "#;
5405
5406        let condition: FieldCondition = serde_json::from_str(query).unwrap();
5407        assert_eq!(
5408            condition.r#match.unwrap(),
5409            Match::Value(MatchValue {
5410                value: ValueVariants::String("world".to_owned())
5411            })
5412        );
5413    }
5414
5415    #[test]
5416    fn test_parse_match_any() {
5417        let query = r#"
5418        {
5419            "should": [
5420                {
5421                    "key": "Jason",
5422                    "match": {
5423                        "any": [
5424                            "Bourne",
5425                            "Momoa",
5426                            "Statham"
5427                        ]
5428                    }
5429                }
5430            ]
5431        }
5432        "#;
5433
5434        let filter: Filter = serde_json::from_str(query).unwrap();
5435        let should = filter.should.unwrap();
5436
5437        assert_eq!(should.len(), 1);
5438        let Some(Condition::Field(c)) = should.first() else {
5439            panic!("Condition::Field expected")
5440        };
5441
5442        assert_eq!(c.key.to_string(), "Jason");
5443
5444        let Match::Any(m) = c.r#match.as_ref().unwrap() else {
5445            panic!("Match::Any expected")
5446        };
5447        if let AnyVariants::Strings(kws) = &m.any {
5448            assert_eq!(kws.len(), 3);
5449            let expect: IndexSet<_, FnvBuildHasher> = ["Bourne", "Momoa", "Statham"]
5450                .into_iter()
5451                .map(|i| i.to_string())
5452                .collect();
5453            assert_eq!(kws, &expect);
5454        } else {
5455            panic!("AnyVariants::Keywords expected");
5456        }
5457    }
5458
5459    #[test]
5460    fn test_parse_match_any_mixed_types() {
5461        let query = r#"
5462        {
5463            "should": [
5464                {
5465                    "key": "Jason",
5466                    "match": {
5467                        "any": [
5468                            "Bourne",
5469                            42
5470                        ]
5471                    }
5472                }
5473            ]
5474        }
5475        "#;
5476
5477        let result: Result<Filter, _> = serde_json::from_str(query);
5478        assert!(result.is_err());
5479    }
5480
5481    #[test]
5482    fn test_parse_nested_match_query() {
5483        let query = r#"
5484        {
5485            "key": "hello.nested",
5486            "match": { "value": 42 }
5487        }
5488        "#;
5489        let condition: FieldCondition = serde_json::from_str(query).unwrap();
5490        assert_eq!(
5491            condition.r#match.unwrap(),
5492            Match::Value(MatchValue {
5493                value: ValueVariants::Integer(42)
5494            })
5495        );
5496
5497        let query = r#"
5498        {
5499            "key": "hello.nested",
5500            "match": { "value": true }
5501        }
5502        "#;
5503        let condition: FieldCondition = serde_json::from_str(query).unwrap();
5504        assert_eq!(
5505            condition.r#match.unwrap(),
5506            Match::Value(MatchValue {
5507                value: ValueVariants::Bool(true)
5508            })
5509        );
5510
5511        let query = r#"
5512        {
5513            "key": "hello.nested",
5514            "match": { "value": "world" }
5515        }
5516        "#;
5517
5518        let condition: FieldCondition = serde_json::from_str(query).unwrap();
5519        assert_eq!(
5520            condition.r#match.unwrap(),
5521            Match::Value(MatchValue {
5522                value: ValueVariants::String("world".to_owned())
5523            })
5524        );
5525    }
5526
5527    #[test]
5528    fn test_parse_empty_query() {
5529        let query = r#"
5530        {
5531            "should": [
5532                {
5533                    "is_empty" : {
5534                        "key" : "Jason"
5535                    }
5536                }
5537            ]
5538        }
5539        "#;
5540
5541        let filter: Filter = serde_json::from_str(query).unwrap();
5542        let should = filter.should.unwrap();
5543
5544        assert_eq!(should.len(), 1);
5545        let Some(Condition::IsEmpty(c)) = should.first() else {
5546            panic!("Condition::IsEmpty expected")
5547        };
5548
5549        assert_eq!(c.is_empty.key.to_string(), "Jason");
5550    }
5551
5552    #[test]
5553    fn test_parse_null_query() {
5554        let query = r#"
5555        {
5556            "should": [
5557                {
5558                    "is_null" : {
5559                        "key" : "Jason"
5560                    }
5561                }
5562            ]
5563        }
5564        "#;
5565
5566        let filter: Filter = serde_json::from_str(query).unwrap();
5567        let should = filter.should.unwrap();
5568
5569        assert_eq!(should.len(), 1);
5570        let Some(Condition::IsNull(c)) = should.first() else {
5571            panic!("Condition::IsNull expected")
5572        };
5573
5574        assert_eq!(c.is_null.key.to_string(), "Jason");
5575    }
5576
5577    #[test]
5578    fn test_parse_nested_filter_query() {
5579        let query = r#"
5580        {
5581          "must": [
5582            {
5583              "nested": {
5584                "key": "country.cities",
5585                "filter": {
5586                  "must": [
5587                    {
5588                      "key": "population",
5589                      "range": {
5590                        "gte": 8
5591                      }
5592                    },
5593                    {
5594                      "key": "sightseeing",
5595                      "values_count": {
5596                        "lt": 3
5597                      }
5598                    }
5599                  ]
5600                }
5601              }
5602            }
5603          ]
5604        }
5605        "#;
5606        let filter: Filter = serde_json::from_str(query).unwrap();
5607        let musts = filter.must.unwrap();
5608        assert_eq!(musts.len(), 1);
5609        match musts.first() {
5610            Some(Condition::Nested(nested_condition)) => {
5611                assert_eq!(nested_condition.raw_key().to_string(), "country.cities");
5612                assert_eq!(nested_condition.array_key().to_string(), "country.cities[]");
5613                let nested_musts = nested_condition.filter().must.as_ref().unwrap();
5614                assert_eq!(nested_musts.len(), 2);
5615                let first_must = nested_musts.first().unwrap();
5616                match first_must {
5617                    Condition::Field(c) => {
5618                        assert_eq!(c.key.to_string(), "population");
5619                        assert!(c.range.is_some());
5620                    }
5621                    _ => panic!("Condition::Field expected"),
5622                }
5623
5624                let second_must = nested_musts.get(1).unwrap();
5625                match second_must {
5626                    Condition::Field(c) => {
5627                        assert_eq!(c.key.to_string(), "sightseeing");
5628                        assert!(c.values_count.is_some());
5629                    }
5630                    _ => panic!("Condition::Field expected"),
5631                }
5632            }
5633            o => panic!("Condition::Nested expected but got {o:?}"),
5634        };
5635    }
5636
5637    #[test]
5638    fn test_parse_single_nested_filter_query() {
5639        let query = r#"
5640        {
5641          "must": {
5642              "nested": {
5643                "key": "country.cities",
5644                "filter": {
5645                  "must": {
5646                      "key": "population",
5647                      "range": {
5648                        "gte": 8
5649                      }
5650                    }
5651                }
5652              }
5653            }
5654        }
5655        "#;
5656        let filter: Filter = serde_json::from_str(query).unwrap();
5657        let musts = filter.must.unwrap();
5658        assert_eq!(musts.len(), 1);
5659
5660        let first_must = musts.first().unwrap();
5661        let Condition::Nested(nested_condition) = first_must else {
5662            panic!("Condition::Nested expected but got {first_must:?}")
5663        };
5664
5665        assert_eq!(nested_condition.raw_key().to_string(), "country.cities");
5666        assert_eq!(nested_condition.array_key().to_string(), "country.cities[]");
5667
5668        let nested_must = nested_condition.filter().must.as_ref().unwrap();
5669        assert_eq!(nested_must.len(), 1);
5670
5671        let must = nested_must.first().unwrap();
5672        let Condition::Field(c) = must else {
5673            panic!("Condition::Field expected, got {must:?}")
5674        };
5675
5676        assert_eq!(c.key.to_string(), "population");
5677        assert!(c.range.is_some());
5678    }
5679
5680    #[test]
5681    fn test_payload_query_parse() {
5682        let query1 = r#"
5683        {
5684            "must": [
5685                {
5686                    "key": "hello",
5687                    "match": {
5688                        "value": 42
5689                    }
5690                },
5691                {
5692                    "must_not": [
5693                        {
5694                            "has_id": [1, 2, 3, 4]
5695                        },
5696                        {
5697                            "key": "geo_field",
5698                            "geo_bounding_box": {
5699                                "top_left": {
5700                                    "lon": 13.410146,
5701                                    "lat": 52.519289
5702                                },
5703                                "bottom_right": {
5704                                    "lon": 13.432683,
5705                                    "lat": 52.505582
5706                                }
5707                            }
5708                        }
5709                    ]
5710                }
5711            ]
5712        }
5713        "#;
5714
5715        let filter: Filter = serde_json::from_str(query1).unwrap();
5716        eprintln!("{filter:?}");
5717        let must = filter.must.unwrap();
5718        let _must_not = filter.must_not;
5719        assert_eq!(must.len(), 2);
5720        match must.get(1) {
5721            Some(Condition::Filter(f)) => {
5722                let must_not = &f.must_not;
5723                match must_not {
5724                    Some(v) => assert_eq!(v.len(), 2),
5725                    None => panic!("Filter expected"),
5726                }
5727            }
5728            _ => panic!("Condition expected"),
5729        }
5730    }
5731
5732    #[test]
5733    fn test_nested_payload_query_parse() {
5734        let query1 = r#"
5735        {
5736            "must": [
5737                {
5738                    "key": "hello.nested.world",
5739                    "match": {
5740                        "value": 42
5741                    }
5742                },
5743                {
5744                    "key": "foo.nested.bar",
5745                    "match": {
5746                        "value": 1
5747                    }
5748                }
5749            ]
5750        }
5751        "#;
5752
5753        let filter: Filter = serde_json::from_str(query1).unwrap();
5754        let must = filter.must.unwrap();
5755        assert_eq!(must.len(), 2);
5756    }
5757
5758    #[test]
5759    fn test_min_should_query_parse() {
5760        let query1 = r#"
5761        {
5762            "min_should": {
5763                "conditions": [
5764                    {
5765                        "key": "hello.nested.world",
5766                        "match": {
5767                            "value": 42
5768                        }
5769                    },
5770                    {
5771                        "key": "foo.nested.bar",
5772                        "match": {
5773                            "value": 1
5774                        }
5775                    }
5776                ],
5777                "min_count": 2
5778            }
5779        }
5780        "#;
5781
5782        let filter: Filter = serde_json::from_str(query1).unwrap();
5783        let min_should = filter.min_should.unwrap();
5784        assert_eq!(min_should.conditions.len(), 2);
5785    }
5786
5787    #[test]
5788    fn test_min_should_nested_parse() {
5789        let query1 = r#"
5790        {
5791            "must": [
5792                {
5793                    "min_should": {
5794                        "conditions": [
5795                            {
5796                                "key": "hello.nested.world",
5797                                "match": {
5798                                    "value": 42
5799                                }
5800                            },
5801                            {
5802                                "key": "foo.nested.bar",
5803                                "match": {
5804                                    "value": 1
5805                                }
5806                            }
5807                        ],
5808                        "min_count": 2
5809                    }
5810                }
5811            ]
5812        }
5813        "#;
5814
5815        let filter: Filter = serde_json::from_str(query1).unwrap();
5816        let must = filter.must.unwrap();
5817        assert_eq!(must.len(), 1);
5818
5819        match must.first() {
5820            Some(Condition::Filter(f)) => {
5821                let min_should = &f.min_should;
5822                match min_should {
5823                    Some(v) => assert_eq!(v.conditions.len(), 2),
5824                    None => panic!("Filter expected"),
5825                }
5826            }
5827            _ => panic!("Condition expected"),
5828        }
5829    }
5830
5831    #[test]
5832    fn test_min_should_min_count_validation() {
5833        // `min_count == 0` is rejected: it is meaningless and a footgun, as an
5834        // empty condition list with `min_count == 0` matches every point.
5835        // See <https://github.com/qdrant/qdrant/pull/9401>.
5836        let invalid = Filter::new_min_should(MinShould {
5837            conditions: vec![],
5838            min_count: 0,
5839        });
5840        assert!(invalid.validate().is_err());
5841
5842        // `min_count >= 1` is accepted.
5843        let valid = Filter::new_min_should(MinShould {
5844            conditions: vec![],
5845            min_count: 1,
5846        });
5847        assert!(valid.validate().is_ok());
5848    }
5849
5850    #[test]
5851    fn test_geo_validation() {
5852        let query1 = r#"
5853        {
5854            "must": [
5855                {
5856                    "key": "geo_field",
5857                    "geo_bounding_box": {
5858                        "top_left": {
5859                            "lon": 1113.410146,
5860                            "lat": 52.519289
5861                        },
5862                        "bottom_right": {
5863                            "lon": 13.432683,
5864                            "lat": 52.505582
5865                        }
5866                    }
5867                }
5868            ]
5869        }
5870        "#;
5871        let filter: Result<Filter, _> = serde_json::from_str(query1);
5872        assert!(filter.is_err());
5873
5874        let query2 = r#"
5875        {
5876            "must": [
5877                {
5878                    "key": "geo_field",
5879                    "geo_polygon": {
5880                        "exterior": {},
5881                        "interiors": []
5882                    }
5883                }
5884            ]
5885        }
5886        "#;
5887        let filter: Result<Filter, _> = serde_json::from_str(query2);
5888        assert!(filter.is_err());
5889
5890        let query3 = r#"
5891        {
5892            "must": [
5893                {
5894                    "key": "geo_field",
5895                    "geo_polygon": {
5896                        "exterior":{
5897                            "points": [
5898                                {"lon": -12.0, "lat": -34.0},
5899                                {"lon": 11.0, "lat": -22.0},
5900                                {"lon": -32.0, "lat": -14.0}
5901                            ]
5902                        },
5903                        "interiors": []
5904                    }
5905                }
5906            ]
5907        }
5908        "#;
5909        let filter: Result<Filter, _> = serde_json::from_str(query3);
5910        assert!(filter.is_err());
5911
5912        let query4 = r#"
5913        {
5914            "must": [
5915                {
5916                    "key": "geo_field",
5917                    "geo_polygon": {
5918                        "exterior": {
5919                            "points": [
5920                                {"lon": -12.0, "lat": -34.0},
5921                                {"lon": 11.0, "lat": -22.0},
5922                                {"lon": -32.0, "lat": -14.0},
5923                                {"lon": -12.0, "lat": -34.0}
5924                            ]
5925                        },
5926                        "interiors": []
5927                    }
5928                }
5929            ]
5930        }
5931        "#;
5932        let filter: Result<Filter, _> = serde_json::from_str(query4);
5933        assert!(filter.is_ok());
5934
5935        let query5 = r#"
5936            {
5937                "must": [
5938                    {
5939                        "key": "geo_field",
5940                        "geo_polygon": {
5941                            "exterior": {
5942                                    "points": [
5943                                        {"lon": -12.0, "lat": -34.0},
5944                                        {"lon": 11.0, "lat": -22.0},
5945                                        {"lon": -32.0, "lat": -14.0},
5946                                        {"lon": -12.0, "lat": -34.0}
5947                                    ]
5948                                },
5949                            "interiors": [
5950                                {
5951                                    "points": [
5952                                        {"lon": -12.0, "lat": -34.0},
5953                                        {"lon": 11.0, "lat": -22.0},
5954                                        {"lon": -32.0, "lat": -14.0}
5955                                    ]
5956                                }
5957                            ]
5958                        }
5959                    }
5960                ]
5961            }
5962            "#;
5963        let filter: Result<Filter, _> = serde_json::from_str(query5);
5964        assert!(filter.is_err());
5965
5966        let query6 = r#"
5967            {
5968                "must": [
5969                    {
5970                        "key": "geo_field",
5971                        "geo_polygon": {
5972                            "exterior": {
5973                                    "points": [
5974                                        {"lon": -12.0, "lat": -34.0},
5975                                        {"lon": 11.0, "lat": -22.0},
5976                                        {"lon": -32.0, "lat": -14.0},
5977                                        {"lon": -12.0, "lat": -34.0}
5978                                    ]
5979                                },
5980                            "interiors": [
5981                                {
5982                                    "points": [
5983                                        {"lon": -12.0, "lat": -34.0},
5984                                        {"lon": 11.0, "lat": -22.0},
5985                                        {"lon": -32.0, "lat": -14.0},
5986                                        {"lon": -12.0, "lat": -34.0}
5987                                    ]
5988                                }
5989                            ]
5990                        }
5991                    }
5992                ]
5993            }
5994            "#;
5995        let filter: Result<Filter, _> = serde_json::from_str(query6);
5996        assert!(filter.is_ok());
5997    }
5998
5999    #[test]
6000    fn test_payload_parsing() {
6001        let ft = PayloadFieldSchema::FieldType(PayloadSchemaType::Keyword);
6002        let ft_json = serde_json::to_string(&ft).unwrap();
6003        eprintln!("ft_json = {ft_json:?}");
6004
6005        let ft = PayloadFieldSchema::FieldParams(PayloadSchemaParams::Text(Default::default()));
6006        let ft_json = serde_json::to_string(&ft).unwrap();
6007        eprintln!("ft_json = {ft_json:?}");
6008
6009        let query = r#""keyword""#;
6010        let field_type: PayloadSchemaType = serde_json::from_str(query).unwrap();
6011        eprintln!("field_type = {field_type:?}");
6012    }
6013
6014    #[test]
6015    fn merge_filters() {
6016        let condition1 = Condition::Field(FieldCondition::new_match(
6017            JsonPath::new("summary"),
6018            Match::new_text("Berlin"),
6019        ));
6020        let mut this = Filter::new_must(condition1.clone());
6021        this.should = Some(vec![condition1.clone()]);
6022
6023        let condition2 = Condition::Field(FieldCondition::new_match(
6024            JsonPath::new("city"),
6025            Match::new_value(ValueVariants::String("Osaka".into())),
6026        ));
6027        let other = Filter::new_must(condition2.clone());
6028
6029        let merged = this.merge(&other);
6030
6031        assert!(merged.must.is_some());
6032        assert_eq!(merged.must.as_ref().unwrap().len(), 2);
6033        assert!(merged.must_not.is_none());
6034        assert!(merged.should.is_some());
6035        assert_eq!(merged.should.as_ref().unwrap().len(), 1);
6036
6037        assert!(merged.must.as_ref().unwrap().contains(&condition1));
6038        assert!(merged.must.as_ref().unwrap().contains(&condition2));
6039        assert!(merged.should.as_ref().unwrap().contains(&condition1));
6040    }
6041
6042    #[test]
6043    fn test_payload_selector_include() {
6044        let payload = payload_json! {
6045            "a": 1,
6046            "b": {
6047                "c": 123,
6048                "e": {
6049                    "f": [1,2,3],
6050                    "g": 7,
6051                    "h": "text",
6052                    "i": [
6053                        {
6054                            "j": 1,
6055                            "k": 2
6056
6057                        },
6058                        {
6059                            "j": 3,
6060                            "k": 4
6061                        }
6062                    ]
6063                }
6064            }
6065        };
6066
6067        // include root & nested
6068        let selector =
6069            PayloadSelector::new_include(vec![JsonPath::new("a"), JsonPath::new("b.e.f")]);
6070        let payload = selector.process(payload);
6071
6072        let expected = payload_json! {
6073            "a": 1,
6074            "b": {
6075                "e": {
6076                    "f": [1,2,3],
6077                }
6078            }
6079        };
6080        assert_eq!(payload, expected);
6081    }
6082
6083    #[test]
6084    fn test_payload_selector_array_include() {
6085        let payload = payload_json! {
6086            "a": 1,
6087            "b": {
6088                "c": 123,
6089                "f": [1,2,3,4,5],
6090            }
6091        };
6092
6093        // handles duplicates
6094        let selector = PayloadSelector::new_include(vec![JsonPath::new("a"), JsonPath::new("a")]);
6095        let payload = selector.process(payload);
6096
6097        let expected = payload_json! {
6098            "a": 1
6099        };
6100        assert_eq!(payload, expected);
6101
6102        // ignore path that points to array
6103        let selector = PayloadSelector::new_include(vec![JsonPath::new("b.f[0]")]);
6104        let payload = selector.process(payload);
6105
6106        // nothing included
6107        let expected = payload_json! {};
6108        assert_eq!(payload, expected);
6109    }
6110
6111    #[test]
6112    fn test_payload_selector_no_implicit_array_include() {
6113        let payload = payload_json! {
6114            "a": 1,
6115            "b": {
6116                "c": [
6117                    {
6118                        "d": 1,
6119                        "e": 2
6120                    },
6121                    {
6122                        "d": 3,
6123                        "e": 4
6124                    }
6125                ],
6126            }
6127        };
6128
6129        let selector = PayloadSelector::new_include(vec![JsonPath::new("b.c")]);
6130        let selected_payload = selector.process(payload.clone());
6131
6132        let expected = payload_json! {
6133            "b": {
6134                "c": [
6135                    {
6136                        "d": 1,
6137                        "e": 2
6138                    },
6139                    {
6140                        "d": 3,
6141                        "e": 4
6142                    }
6143                ]
6144            }
6145        };
6146        assert_eq!(selected_payload, expected);
6147
6148        // with explicit array traversal ([] notation)
6149        let selector = PayloadSelector::new_include(vec![JsonPath::new("b.c[].d")]);
6150        let selected_payload = selector.process(payload.clone());
6151
6152        let expected = payload_json! {
6153            "b": {
6154                "c": [
6155                    {"d": 1},
6156                    {"d": 3}
6157                ]
6158            }
6159        };
6160        assert_eq!(selected_payload, expected);
6161
6162        // shortcuts implicit array traversal
6163        let selector = PayloadSelector::new_include(vec![JsonPath::new("b.c.d")]);
6164        let selected_payload = selector.process(payload);
6165
6166        let expected = payload_json! {
6167            "b": {
6168                "c": []
6169            }
6170        };
6171        assert_eq!(selected_payload, expected);
6172    }
6173
6174    #[test]
6175    fn test_payload_selector_exclude() {
6176        let payload = payload_json! {
6177            "a": 1,
6178            "b": {
6179                "c": 123,
6180                "e": {
6181                    "f": [1,2,3],
6182                    "g": 7,
6183                    "h": "text",
6184                    "i": [
6185                        {
6186                            "j": 1,
6187                            "k": 2
6188
6189                        },
6190                        {
6191                            "j": 3,
6192                            "k": 4
6193                        }
6194                    ]
6195                }
6196            }
6197        };
6198
6199        // exclude
6200        let selector =
6201            PayloadSelector::new_exclude(vec![JsonPath::new("a"), JsonPath::new("b.e.f")]);
6202        let payload = selector.process(payload);
6203
6204        // root removal & nested removal
6205        let expected = payload_json! {
6206            "b": {
6207                "c": 123,
6208                "e": {
6209                    "g": 7,
6210                    "h": "text",
6211                    "i": [
6212                        {
6213                            "j": 1,
6214                            "k": 2
6215
6216                        },
6217                        {
6218                            "j": 3,
6219                            "k": 4
6220                        }
6221                    ]
6222                }
6223            }
6224        };
6225        assert_eq!(payload, expected);
6226    }
6227
6228    #[test]
6229    fn test_payload_selector_array_exclude() {
6230        let payload = payload_json! {
6231            "a": 1,
6232            "b": {
6233                "c": 123,
6234                "f": [1,2,3,4,5],
6235            }
6236        };
6237
6238        // handles duplicates
6239        let selector = PayloadSelector::new_exclude(vec![JsonPath::new("a"), JsonPath::new("a")]);
6240        let payload = selector.process(payload);
6241
6242        // single removal
6243        let expected = payload_json! {
6244            "b": {
6245                "c": 123,
6246                "f": [1,2,3,4,5],
6247            }
6248        };
6249        assert_eq!(payload, expected);
6250
6251        // ignore path that points to array
6252        let selector = PayloadSelector::new_exclude(vec![JsonPath::new("b.f[0]")]);
6253
6254        let payload = selector.process(payload);
6255
6256        // no removal
6257        let expected = payload_json! {
6258            "b": {
6259                "c": 123,
6260                "f": [1,2,3,4,5],
6261            }
6262        };
6263        assert_eq!(payload, expected);
6264    }
6265
6266    #[test]
6267    fn test_extended_point_id_cbor_roundtrip() {
6268        let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
6269
6270        for point_id in [ExtendedPointId::Uuid(uuid), ExtendedPointId::NumId(42)] {
6271            let cbor_bytes = serde_cbor::to_vec(&point_id).unwrap();
6272            let deserialized: ExtendedPointId = serde_cbor::from_slice(&cbor_bytes).unwrap();
6273            assert_eq!(point_id, deserialized);
6274        }
6275    }
6276
6277    #[test]
6278    fn test_filter_with_match_and_has_id_uuid_cbor_roundtrip() {
6279        let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
6280        let filter = Filter {
6281            should: None,
6282            min_should: None,
6283            must: Some(vec![Condition::Field(FieldCondition::new_match(
6284                crate::segment::json_path::JsonPath::new("org_id"),
6285                Match::new_value(ValueVariants::String("test_org".to_string())),
6286            ))]),
6287            must_not: Some(vec![Condition::HasId(HasIdCondition {
6288                has_id: [ExtendedPointId::Uuid(uuid)].into_iter().collect(),
6289            })]),
6290        };
6291
6292        let cbor_bytes = serde_cbor::to_vec(&filter).unwrap();
6293        let deserialized: Filter = serde_cbor::from_slice(&cbor_bytes).unwrap();
6294        assert_eq!(filter, deserialized);
6295    }
6296}
6297
6298fn shard_key_string_example() -> String {
6299    "region_1".to_string()
6300}
6301
6302fn shard_key_number_example() -> u64 {
6303    12
6304}
6305
6306#[derive(Deserialize, Serialize, JsonSchema,  Debug, Clone, PartialEq, Eq, Hash)]
6307#[serde(untagged)]
6308pub enum ShardKey {
6309    #[schemars(
6310        schema_with = "String::json_schema",
6311        example = "shard_key_string_example"
6312    )]
6313    Keyword(EcoString),
6314    #[schemars(example = "shard_key_number_example")]
6315    
6316    Number(u64),
6317}
6318
6319impl From<String> for ShardKey {
6320    fn from(s: String) -> Self {
6321        ShardKey::Keyword(EcoString::from(s))
6322    }
6323}
6324
6325impl From<EcoString> for ShardKey {
6326    fn from(s: EcoString) -> Self {
6327        ShardKey::Keyword(s)
6328    }
6329}
6330
6331impl From<&str> for ShardKey {
6332    fn from(s: &str) -> Self {
6333        ShardKey::Keyword(EcoString::from(s))
6334    }
6335}
6336
6337impl From<u64> for ShardKey {
6338    fn from(n: u64) -> Self {
6339        ShardKey::Number(n)
6340    }
6341}
6342
6343impl Display for ShardKey {
6344    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6345        match self {
6346            ShardKey::Keyword(keyword) => write!(f, "\"{keyword}\""),
6347            ShardKey::Number(number) => write!(f, "{number}"),
6348        }
6349    }
6350}