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