Skip to main content

zvec_rust/
schema.rs

1use std::ffi::CStr;
2
3use crate::error::{check_error, to_cstring, Error, ErrorCode, Result};
4use crate::types::{DataType, IndexType, MetricType, QuantizeType};
5
6/// Parameters for configuring an index on a field.
7pub struct IndexParams {
8    pub(crate) handle: *mut zvec_rust_sys::zvec_index_params_t,
9    owned: bool,
10}
11
12impl IndexParams {
13    /// Returns the raw FFI handle.
14    ///
15    /// # Safety
16    /// The caller must not use the handle after the `IndexParams` is dropped.
17    pub unsafe fn as_raw(&self) -> *mut zvec_rust_sys::zvec_index_params_t {
18        self.handle
19    }
20
21    /// Creates HNSW index parameters.
22    pub fn hnsw(metric: MetricType, m: i32, ef_construction: i32) -> Result<Self> {
23        unsafe {
24            let handle = zvec_rust_sys::zvec_index_params_create(IndexType::Hnsw as u32);
25            if handle.is_null() {
26                return Err(Error {
27                    code: ErrorCode::InternalError,
28                    message: "failed to create HNSW index params".into(),
29                });
30            }
31            check_error(zvec_rust_sys::zvec_index_params_set_metric_type(
32                handle,
33                metric as u32,
34            ))?;
35            check_error(zvec_rust_sys::zvec_index_params_set_hnsw_params(
36                handle,
37                m,
38                ef_construction,
39            ))?;
40            Ok(IndexParams {
41                handle,
42                owned: true,
43            })
44        }
45    }
46
47    /// Creates HNSW index parameters with quantization.
48    pub fn hnsw_with_quantize(
49        metric: MetricType,
50        m: i32,
51        ef_construction: i32,
52        quantize: QuantizeType,
53    ) -> Result<Self> {
54        unsafe {
55            let handle = zvec_rust_sys::zvec_index_params_create(IndexType::Hnsw as u32);
56            if handle.is_null() {
57                return Err(Error {
58                    code: ErrorCode::InternalError,
59                    message: "failed to create HNSW index params".into(),
60                });
61            }
62            check_error(zvec_rust_sys::zvec_index_params_set_metric_type(
63                handle,
64                metric as u32,
65            ))?;
66            check_error(zvec_rust_sys::zvec_index_params_set_hnsw_params(
67                handle,
68                m,
69                ef_construction,
70            ))?;
71            check_error(zvec_rust_sys::zvec_index_params_set_quantize_type(
72                handle,
73                quantize as u32,
74            ))?;
75            Ok(IndexParams {
76                handle,
77                owned: true,
78            })
79        }
80    }
81
82    /// Creates IVF index parameters.
83    pub fn ivf(metric: MetricType, n_list: i32, n_iters: i32, use_soar: bool) -> Result<Self> {
84        unsafe {
85            let handle = zvec_rust_sys::zvec_index_params_create(IndexType::Ivf as u32);
86            if handle.is_null() {
87                return Err(Error {
88                    code: ErrorCode::InternalError,
89                    message: "failed to create IVF index params".into(),
90                });
91            }
92            check_error(zvec_rust_sys::zvec_index_params_set_metric_type(
93                handle,
94                metric as u32,
95            ))?;
96            check_error(zvec_rust_sys::zvec_index_params_set_ivf_params(
97                handle, n_list, n_iters, use_soar,
98            ))?;
99            Ok(IndexParams {
100                handle,
101                owned: true,
102            })
103        }
104    }
105
106    /// Creates Flat index parameters.
107    pub fn flat(metric: MetricType) -> Result<Self> {
108        unsafe {
109            let handle = zvec_rust_sys::zvec_index_params_create(IndexType::Flat as u32);
110            if handle.is_null() {
111                return Err(Error {
112                    code: ErrorCode::InternalError,
113                    message: "failed to create Flat index params".into(),
114                });
115            }
116            check_error(zvec_rust_sys::zvec_index_params_set_metric_type(
117                handle,
118                metric as u32,
119            ))?;
120            Ok(IndexParams {
121                handle,
122                owned: true,
123            })
124        }
125    }
126
127    /// Creates DiskANN index parameters.
128    ///
129    /// DiskANN is a disk-based graph index that keeps the bulk of the index on
130    /// disk, drastically reducing memory usage for large datasets.
131    ///
132    /// - `max_degree`: graph connectivity, i.e. max out-degree of the graph (typical: 64)
133    /// - `list_size`: build-time candidate list size during construction (typical: 100)
134    /// - `pq_chunk_num`: PQ chunk count; `0` disables product quantization
135    pub fn diskann(
136        metric: MetricType,
137        max_degree: i32,
138        list_size: i32,
139        pq_chunk_num: i32,
140    ) -> Result<Self> {
141        unsafe {
142            let handle = zvec_rust_sys::zvec_index_params_create(IndexType::Diskann as u32);
143            if handle.is_null() {
144                return Err(Error {
145                    code: ErrorCode::InternalError,
146                    message: "failed to create DiskANN index params".into(),
147                });
148            }
149            check_error(zvec_rust_sys::zvec_index_params_set_metric_type(
150                handle,
151                metric as u32,
152            ))?;
153            check_error(zvec_rust_sys::zvec_index_params_set_diskann_params(
154                handle,
155                max_degree,
156                list_size,
157                pq_chunk_num,
158            ))?;
159            Ok(IndexParams {
160                handle,
161                owned: true,
162            })
163        }
164    }
165
166    /// Creates inverted index parameters for scalar fields.
167    pub fn invert(enable_range_opt: bool, enable_wildcard: bool) -> Result<Self> {
168        unsafe {
169            let handle = zvec_rust_sys::zvec_index_params_create(IndexType::Invert as u32);
170            if handle.is_null() {
171                return Err(Error {
172                    code: ErrorCode::InternalError,
173                    message: "failed to create Invert index params".into(),
174                });
175            }
176            check_error(zvec_rust_sys::zvec_index_params_set_invert_params(
177                handle,
178                enable_range_opt,
179                enable_wildcard,
180            ))?;
181            Ok(IndexParams {
182                handle,
183                owned: true,
184            })
185        }
186    }
187
188    /// Creates FTS (Full-Text Search) index parameters.
189    ///
190    /// All parameters are optional — passing `None` keeps the library default.
191    pub fn fts(
192        tokenizer_name: Option<&str>,
193        filters: Option<&[&str]>,
194        extra_params: Option<&str>,
195    ) -> Result<Self> {
196        unsafe {
197            let handle = zvec_rust_sys::zvec_index_params_create(IndexType::Fts as u32);
198            if handle.is_null() {
199                return Err(Error {
200                    code: ErrorCode::InternalError,
201                    message: "failed to create FTS index params".into(),
202                });
203            }
204            let c_tokenizer = tokenizer_name.map(to_cstring).transpose()?;
205            let c_extra = extra_params.map(to_cstring).transpose()?;
206
207            let filter_array = if let Some(f) = filters {
208                let arr = zvec_rust_sys::zvec_string_array_create(f.len());
209                for (i, s) in f.iter().enumerate() {
210                    let cs = to_cstring(s)?;
211                    zvec_rust_sys::zvec_string_array_add(arr, i, cs.as_ptr());
212                }
213                arr
214            } else {
215                std::ptr::null_mut()
216            };
217
218            let result = check_error(zvec_rust_sys::zvec_index_params_set_fts_params(
219                handle,
220                c_tokenizer
221                    .as_ref()
222                    .map_or(std::ptr::null(), |c| c.as_ptr()),
223                filter_array as *const _,
224                c_extra.as_ref().map_or(std::ptr::null(), |c| c.as_ptr()),
225            ));
226
227            if !filter_array.is_null() {
228                zvec_rust_sys::zvec_string_array_destroy(filter_array);
229            }
230            result?;
231            Ok(IndexParams {
232                handle,
233                owned: true,
234            })
235        }
236    }
237
238    /// Returns the index type.
239    pub fn index_type(&self) -> IndexType {
240        IndexType::from(unsafe { zvec_rust_sys::zvec_index_params_get_type(self.handle) })
241    }
242
243    /// Returns the metric type.
244    pub fn metric_type(&self) -> MetricType {
245        MetricType::from(unsafe { zvec_rust_sys::zvec_index_params_get_metric_type(self.handle) })
246    }
247
248    /// Returns the quantize type.
249    pub fn quantize_type(&self) -> QuantizeType {
250        QuantizeType::from(unsafe {
251            zvec_rust_sys::zvec_index_params_get_quantize_type(self.handle)
252        })
253    }
254
255    /// Sets the metric type.
256    pub fn set_metric_type(&mut self, metric: MetricType) -> Result<()> {
257        check_error(unsafe {
258            zvec_rust_sys::zvec_index_params_set_metric_type(self.handle, metric as u32)
259        })
260    }
261
262    /// Sets the quantize type.
263    pub fn set_quantize_type(&mut self, quantize: QuantizeType) -> Result<()> {
264        check_error(unsafe {
265            zvec_rust_sys::zvec_index_params_set_quantize_type(self.handle, quantize as u32)
266        })
267    }
268}
269
270impl Drop for IndexParams {
271    fn drop(&mut self) {
272        if self.owned && !self.handle.is_null() {
273            unsafe { zvec_rust_sys::zvec_index_params_destroy(self.handle) };
274        }
275    }
276}
277
278/// Schema definition for a single field in a collection.
279pub struct FieldSchema {
280    pub(crate) handle: *mut zvec_rust_sys::zvec_field_schema_t,
281    owned: bool,
282}
283
284impl FieldSchema {
285    /// Creates a new field schema.
286    ///
287    /// - `name`: Field name
288    /// - `data_type`: Data type of the field
289    /// - `nullable`: Whether the field can be null
290    /// - `dimension`: Vector dimension (0 for non-vector fields)
291    pub fn new(name: &str, data_type: DataType, nullable: bool, dimension: u32) -> Result<Self> {
292        let c_name = to_cstring(name)?;
293        let handle = unsafe {
294            zvec_rust_sys::zvec_field_schema_create(
295                c_name.as_ptr(),
296                data_type as u32,
297                nullable,
298                dimension,
299            )
300        };
301        if handle.is_null() {
302            return Err(Error {
303                code: ErrorCode::InternalError,
304                message: "failed to create field schema".into(),
305            });
306        }
307        Ok(FieldSchema {
308            handle,
309            owned: true,
310        })
311    }
312
313    /// Creates a non-owning wrapper around an existing handle.
314    #[allow(dead_code)]
315    pub(crate) fn from_borrowed(handle: *mut zvec_rust_sys::zvec_field_schema_t) -> Self {
316        FieldSchema {
317            handle,
318            owned: false,
319        }
320    }
321
322    /// Sets the index parameters for this field.
323    pub fn set_index_params(&mut self, params: &IndexParams) -> Result<()> {
324        check_error(unsafe {
325            zvec_rust_sys::zvec_field_schema_set_index_params(self.handle, params.handle)
326        })
327    }
328
329    /// Returns the field name.
330    pub fn name(&self) -> &str {
331        unsafe {
332            let ptr = zvec_rust_sys::zvec_field_schema_get_name(self.handle);
333            if ptr.is_null() {
334                return "";
335            }
336            CStr::from_ptr(ptr).to_str().unwrap_or("")
337        }
338    }
339
340    /// Returns the data type.
341    pub fn data_type(&self) -> DataType {
342        DataType::from(unsafe { zvec_rust_sys::zvec_field_schema_get_data_type(self.handle) })
343    }
344
345    /// Returns the dimension (for vector fields).
346    pub fn dimension(&self) -> u32 {
347        unsafe { zvec_rust_sys::zvec_field_schema_get_dimension(self.handle) }
348    }
349
350    /// Returns whether the field is nullable.
351    pub fn is_nullable(&self) -> bool {
352        unsafe { zvec_rust_sys::zvec_field_schema_is_nullable(self.handle) }
353    }
354
355    /// Returns whether this is a vector field.
356    pub fn is_vector_field(&self) -> bool {
357        unsafe { zvec_rust_sys::zvec_field_schema_is_vector_field(self.handle) }
358    }
359
360    /// Returns whether this is a dense vector field.
361    pub fn is_dense_vector(&self) -> bool {
362        unsafe { zvec_rust_sys::zvec_field_schema_is_dense_vector(self.handle) }
363    }
364
365    /// Returns whether this is a sparse vector field.
366    pub fn is_sparse_vector(&self) -> bool {
367        unsafe { zvec_rust_sys::zvec_field_schema_is_sparse_vector(self.handle) }
368    }
369
370    /// Returns whether this field has an index.
371    pub fn has_index(&self) -> bool {
372        unsafe { zvec_rust_sys::zvec_field_schema_has_index(self.handle) }
373    }
374
375    /// Returns the index type.
376    pub fn index_type(&self) -> IndexType {
377        IndexType::from(unsafe { zvec_rust_sys::zvec_field_schema_get_index_type(self.handle) })
378    }
379
380    /// Returns whether this is an array type.
381    pub fn is_array_type(&self) -> bool {
382        unsafe { zvec_rust_sys::zvec_field_schema_is_array_type(self.handle) }
383    }
384}
385
386impl Drop for FieldSchema {
387    fn drop(&mut self) {
388        if self.owned && !self.handle.is_null() {
389            unsafe { zvec_rust_sys::zvec_field_schema_destroy(self.handle) };
390        }
391    }
392}
393
394/// Schema definition for a collection, containing field definitions.
395pub struct CollectionSchema {
396    pub(crate) handle: *mut zvec_rust_sys::zvec_collection_schema_t,
397    owned: bool,
398}
399
400impl CollectionSchema {
401    /// Creates a new collection schema with the given name.
402    pub fn new(name: &str) -> Result<Self> {
403        let c_name = to_cstring(name)?;
404        let handle = unsafe { zvec_rust_sys::zvec_collection_schema_create(c_name.as_ptr()) };
405        if handle.is_null() {
406            return Err(Error {
407                code: ErrorCode::InternalError,
408                message: "failed to create collection schema".into(),
409            });
410        }
411        Ok(CollectionSchema {
412            handle,
413            owned: true,
414        })
415    }
416
417    /// Returns a builder for constructing a collection schema.
418    pub fn builder(name: &str) -> CollectionSchemaBuilder {
419        CollectionSchemaBuilder::new(name)
420    }
421
422    /// Creates a non-owning wrapper around an existing handle.
423    pub(crate) fn from_owned(handle: *mut zvec_rust_sys::zvec_collection_schema_t) -> Self {
424        CollectionSchema {
425            handle,
426            owned: true,
427        }
428    }
429
430    /// Adds a field to the schema.
431    pub fn add_field(&mut self, field: &FieldSchema) -> Result<()> {
432        check_error(unsafe {
433            zvec_rust_sys::zvec_collection_schema_add_field(self.handle, field.handle)
434        })
435    }
436
437    /// Returns the collection name.
438    pub fn name(&self) -> &str {
439        unsafe {
440            let ptr = zvec_rust_sys::zvec_collection_schema_get_name(self.handle);
441            if ptr.is_null() {
442                return "";
443            }
444            CStr::from_ptr(ptr).to_str().unwrap_or("")
445        }
446    }
447
448    /// Checks if a field exists in the schema.
449    pub fn has_field(&self, name: &str) -> bool {
450        let c_name = match to_cstring(name) {
451            Ok(s) => s,
452            Err(_) => return false,
453        };
454        unsafe { zvec_rust_sys::zvec_collection_schema_has_field(self.handle, c_name.as_ptr()) }
455    }
456
457    /// Checks if a field has an index.
458    pub fn has_index(&self, field_name: &str) -> bool {
459        let c_name = match to_cstring(field_name) {
460            Ok(s) => s,
461            Err(_) => return false,
462        };
463        unsafe { zvec_rust_sys::zvec_collection_schema_has_index(self.handle, c_name.as_ptr()) }
464    }
465
466    /// Drops a field from the schema.
467    pub fn drop_field(&mut self, name: &str) -> Result<()> {
468        let c_name = to_cstring(name)?;
469        check_error(unsafe {
470            zvec_rust_sys::zvec_collection_schema_drop_field(self.handle, c_name.as_ptr())
471        })
472    }
473
474    /// Adds an index to a field.
475    pub fn add_index(&mut self, field_name: &str, params: &IndexParams) -> Result<()> {
476        let c_name = to_cstring(field_name)?;
477        check_error(unsafe {
478            zvec_rust_sys::zvec_collection_schema_add_index(
479                self.handle,
480                c_name.as_ptr(),
481                params.handle,
482            )
483        })
484    }
485
486    /// Drops an index from a field.
487    pub fn drop_index(&mut self, field_name: &str) -> Result<()> {
488        let c_name = to_cstring(field_name)?;
489        check_error(unsafe {
490            zvec_rust_sys::zvec_collection_schema_drop_index(self.handle, c_name.as_ptr())
491        })
492    }
493
494    /// Sets the maximum document count per segment.
495    pub fn set_max_doc_count_per_segment(&mut self, count: u64) -> Result<()> {
496        check_error(unsafe {
497            zvec_rust_sys::zvec_collection_schema_set_max_doc_count_per_segment(self.handle, count)
498        })
499    }
500
501    /// Returns the maximum document count per segment.
502    pub fn max_doc_count_per_segment(&self) -> u64 {
503        unsafe { zvec_rust_sys::zvec_collection_schema_get_max_doc_count_per_segment(self.handle) }
504    }
505}
506
507impl Drop for CollectionSchema {
508    fn drop(&mut self) {
509        if self.owned && !self.handle.is_null() {
510            unsafe { zvec_rust_sys::zvec_collection_schema_destroy(self.handle) };
511        }
512    }
513}
514
515/// Builder for constructing a [`CollectionSchema`] with a fluent API.
516pub struct CollectionSchemaBuilder {
517    name: String,
518    fields: Vec<(FieldSchema, Option<IndexParams>)>,
519    max_doc_count_per_segment: Option<u64>,
520    deferred_error: Option<Error>,
521}
522
523impl CollectionSchemaBuilder {
524    /// Creates a new builder with the given collection name.
525    pub fn new(name: &str) -> Self {
526        CollectionSchemaBuilder {
527            name: name.to_string(),
528            fields: Vec::new(),
529            max_doc_count_per_segment: None,
530            deferred_error: None,
531        }
532    }
533
534    /// Adds a field to the schema.
535    pub fn add_field(mut self, field: FieldSchema) -> Self {
536        self.fields.push((field, None));
537        self
538    }
539
540    /// Adds a vector field with index parameters.
541    pub fn add_vector_field(
542        self,
543        name: &str,
544        data_type: DataType,
545        dimension: u32,
546        index_params: IndexParams,
547    ) -> Self {
548        match FieldSchema::new(name, data_type, false, dimension) {
549            Ok(field) => {
550                let mut s = self;
551                s.fields.push((field, Some(index_params)));
552                s
553            }
554            Err(e) => {
555                let mut s = self;
556                s.deferred_error = Some(e);
557                s
558            }
559        }
560    }
561
562    /// Adds a scalar field with an inverted index.
563    pub fn add_indexed_field(
564        self,
565        name: &str,
566        data_type: DataType,
567        index_params: IndexParams,
568    ) -> Self {
569        match FieldSchema::new(name, data_type, false, 0) {
570            Ok(field) => {
571                let mut s = self;
572                s.fields.push((field, Some(index_params)));
573                s
574            }
575            Err(e) => {
576                let mut s = self;
577                s.deferred_error = Some(e);
578                s
579            }
580        }
581    }
582
583    /// Sets the maximum document count per segment.
584    pub fn max_doc_count_per_segment(mut self, count: u64) -> Self {
585        self.max_doc_count_per_segment = Some(count);
586        self
587    }
588
589    /// Builds the collection schema.
590    pub fn build(self) -> Result<CollectionSchema> {
591        if let Some(e) = self.deferred_error {
592            return Err(e);
593        }
594        let mut schema = CollectionSchema::new(&self.name)?;
595
596        for (mut field, index_params) in self.fields {
597            if let Some(params) = &index_params {
598                field.set_index_params(params)?;
599            }
600            schema.add_field(&field)?;
601        }
602
603        if let Some(count) = self.max_doc_count_per_segment {
604            schema.set_max_doc_count_per_segment(count)?;
605        }
606
607        Ok(schema)
608    }
609}
610
611#[cfg(test)]
612mod tests {
613    use super::*;
614    use crate::types::MetricType;
615
616    #[test]
617    fn test_collection_schema_builder_default_values() {
618        let builder = CollectionSchemaBuilder::new("test_collection");
619        assert_eq!(builder.name, "test_collection");
620        assert!(builder.fields.is_empty());
621        assert!(builder.max_doc_count_per_segment.is_none());
622    }
623
624    #[test]
625    fn test_collection_schema_builder_add_field() {
626        let field = FieldSchema::new("test_field", DataType::Int32, false, 0).unwrap();
627        let builder = CollectionSchemaBuilder::new("test_collection").add_field(field);
628
629        assert_eq!(builder.fields.len(), 1);
630        assert_eq!(builder.fields[0].0.name(), "test_field");
631        assert!(builder.fields[0].1.is_none());
632    }
633
634    #[test]
635    fn test_collection_schema_builder_add_vector_field() {
636        let index_params = IndexParams::hnsw(MetricType::L2, 16, 200).unwrap();
637        let builder = CollectionSchemaBuilder::new("test_collection").add_vector_field(
638            "vector",
639            DataType::VectorFp32,
640            128,
641            index_params,
642        );
643
644        assert_eq!(builder.fields.len(), 1);
645        assert_eq!(builder.fields[0].0.name(), "vector");
646        assert!(builder.fields[0].1.is_some());
647    }
648
649    #[test]
650    fn test_collection_schema_builder_add_indexed_field() {
651        let index_params = IndexParams::invert(true, false).unwrap();
652        let builder = CollectionSchemaBuilder::new("test_collection").add_indexed_field(
653            "age",
654            DataType::Int32,
655            index_params,
656        );
657
658        assert_eq!(builder.fields.len(), 1);
659        assert_eq!(builder.fields[0].0.name(), "age");
660        assert!(builder.fields[0].1.is_some());
661    }
662
663    #[test]
664    fn test_collection_schema_builder_max_doc_count_per_segment() {
665        let builder =
666            CollectionSchemaBuilder::new("test_collection").max_doc_count_per_segment(1000);
667
668        assert_eq!(builder.max_doc_count_per_segment, Some(1000));
669    }
670
671    #[test]
672    fn test_collection_schema_builder_multiple_fields() {
673        let field1 = FieldSchema::new("id", DataType::Int64, false, 0).unwrap();
674        let field2 = FieldSchema::new("name", DataType::String, false, 0).unwrap();
675        let index_params = IndexParams::hnsw(MetricType::L2, 16, 200).unwrap();
676
677        let builder = CollectionSchemaBuilder::new("test_collection")
678            .add_field(field1)
679            .add_field(field2)
680            .add_vector_field("vector", DataType::VectorFp32, 128, index_params);
681
682        assert_eq!(builder.fields.len(), 3);
683        assert_eq!(builder.fields[0].0.name(), "id");
684        assert_eq!(builder.fields[1].0.name(), "name");
685        assert_eq!(builder.fields[2].0.name(), "vector");
686    }
687
688    #[test]
689    fn test_index_params_hnsw() {
690        let params = IndexParams::hnsw(MetricType::L2, 16, 200).unwrap();
691        assert_eq!(params.index_type(), IndexType::Hnsw);
692        assert_eq!(params.metric_type(), MetricType::L2);
693    }
694
695    #[test]
696    fn test_index_params_hnsw_with_quantize() {
697        let params =
698            IndexParams::hnsw_with_quantize(MetricType::L2, 16, 200, QuantizeType::Int8).unwrap();
699        assert_eq!(params.index_type(), IndexType::Hnsw);
700        assert_eq!(params.metric_type(), MetricType::L2);
701        assert_eq!(params.quantize_type(), QuantizeType::Int8);
702    }
703
704    #[test]
705    fn test_index_params_ivf() {
706        let params = IndexParams::ivf(MetricType::Ip, 100, 10, true).unwrap();
707        assert_eq!(params.index_type(), IndexType::Ivf);
708        assert_eq!(params.metric_type(), MetricType::Ip);
709    }
710
711    #[test]
712    fn test_index_params_flat() {
713        let params = IndexParams::flat(MetricType::Cosine).unwrap();
714        assert_eq!(params.index_type(), IndexType::Flat);
715        assert_eq!(params.metric_type(), MetricType::Cosine);
716    }
717
718    #[test]
719    fn test_index_params_diskann() {
720        let params = IndexParams::diskann(MetricType::L2, 32, 100, 0).unwrap();
721        assert_eq!(params.index_type(), IndexType::Diskann);
722        assert_eq!(params.metric_type(), MetricType::L2);
723    }
724
725    #[test]
726    fn test_index_params_invert() {
727        let params = IndexParams::invert(true, true).unwrap();
728        assert_eq!(params.index_type(), IndexType::Invert);
729    }
730
731    #[test]
732    fn test_field_schema_properties() {
733        let field = FieldSchema::new("test_field", DataType::VectorFp32, true, 128).unwrap();
734        assert_eq!(field.name(), "test_field");
735        assert_eq!(field.data_type(), DataType::VectorFp32);
736        assert_eq!(field.dimension(), 128);
737        assert!(field.is_nullable());
738    }
739
740    #[test]
741    fn test_collection_schema_builder_method() {
742        let builder = CollectionSchema::builder("test_collection");
743        assert_eq!(builder.name, "test_collection");
744        assert!(builder.fields.is_empty());
745    }
746
747    #[test]
748    fn test_field_schema_non_nullable() {
749        let field = FieldSchema::new("test_field", DataType::Int32, false, 0).unwrap();
750        assert!(!field.is_nullable());
751    }
752
753    #[test]
754    fn test_field_schema_scalar_dimension_zero() {
755        let field = FieldSchema::new("scalar_field", DataType::String, false, 0).unwrap();
756        assert_eq!(field.dimension(), 0);
757        assert!(!field.is_vector_field());
758    }
759
760    #[test]
761    fn test_field_schema_new_with_null_byte() {
762        let result = FieldSchema::new("invalid\0field", DataType::String, false, 0);
763        assert!(result.is_err());
764    }
765
766    #[test]
767    fn test_field_schema_new_success() {
768        let result = FieldSchema::new("valid_field", DataType::Int64, true, 0);
769        assert!(result.is_ok());
770        let field = result.unwrap();
771        assert_eq!(field.name(), "valid_field");
772        assert_eq!(field.data_type(), DataType::Int64);
773    }
774
775    #[test]
776    fn test_index_params_set_metric_type() {
777        let mut params = IndexParams::hnsw(MetricType::L2, 16, 200).unwrap();
778        params.set_metric_type(MetricType::Cosine).unwrap();
779        assert_eq!(params.metric_type(), MetricType::Cosine);
780    }
781
782    #[test]
783    fn test_index_params_set_quantize_type() {
784        let mut params =
785            IndexParams::hnsw_with_quantize(MetricType::L2, 16, 200, QuantizeType::Undefined)
786                .unwrap();
787        params.set_quantize_type(QuantizeType::Int8).unwrap();
788        assert_eq!(params.quantize_type(), QuantizeType::Int8);
789    }
790
791    #[test]
792    fn test_index_params_ivf_cosine() {
793        let params = IndexParams::ivf(MetricType::Cosine, 100, 10, false).unwrap();
794        assert_eq!(params.index_type(), IndexType::Ivf);
795        assert_eq!(params.metric_type(), MetricType::Cosine);
796    }
797
798    #[test]
799    fn test_index_params_flat_ip() {
800        let params = IndexParams::flat(MetricType::Ip).unwrap();
801        assert_eq!(params.index_type(), IndexType::Flat);
802        assert_eq!(params.metric_type(), MetricType::Ip);
803    }
804
805    #[test]
806    fn test_collection_schema_builder_chaining() {
807        let vector_index = IndexParams::hnsw(MetricType::L2, 16, 200).unwrap();
808        let scalar_index = IndexParams::invert(true, false).unwrap();
809
810        let builder = CollectionSchemaBuilder::new("chained_collection")
811            .add_field(FieldSchema::new("id", DataType::Int64, false, 0).unwrap())
812            .add_vector_field("embedding", DataType::VectorFp32, 128, vector_index)
813            .add_indexed_field("category", DataType::String, scalar_index)
814            .max_doc_count_per_segment(5000);
815
816        assert_eq!(builder.fields.len(), 3);
817        assert_eq!(builder.max_doc_count_per_segment, Some(5000));
818        assert_eq!(builder.fields[0].0.name(), "id");
819        assert_eq!(builder.fields[1].0.name(), "embedding");
820        assert_eq!(builder.fields[2].0.name(), "category");
821    }
822}