Skip to main content

qdrant_edge/segment/
types.rs

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