Skip to main content

zvec_rust/
query.rs

1use std::os::raw::c_void;
2
3use crate::error::{check_error, to_cstring, Error, ErrorCode, Result};
4
5/// HNSW-specific query parameters.
6pub struct HnswQueryParams {
7    pub(crate) handle: *mut zvec_rust_sys::zvec_hnsw_query_params_t,
8}
9
10impl HnswQueryParams {
11    /// Creates new HNSW query parameters.
12    pub fn new(ef: i32, radius: f32, is_linear: bool, is_using_refiner: bool) -> Self {
13        let handle = unsafe {
14            zvec_rust_sys::zvec_query_params_hnsw_create(ef, radius, is_linear, is_using_refiner)
15        };
16        HnswQueryParams { handle }
17    }
18
19    /// Sets the exploration factor.
20    pub fn set_ef(&mut self, ef: i32) -> Result<()> {
21        check_error(unsafe { zvec_rust_sys::zvec_query_params_hnsw_set_ef(self.handle, ef) })
22    }
23
24    /// Returns the exploration factor.
25    pub fn ef(&self) -> i32 {
26        unsafe { zvec_rust_sys::zvec_query_params_hnsw_get_ef(self.handle) }
27    }
28}
29
30impl Drop for HnswQueryParams {
31    fn drop(&mut self) {
32        if !self.handle.is_null() {
33            unsafe { zvec_rust_sys::zvec_query_params_hnsw_destroy(self.handle) };
34        }
35    }
36}
37
38/// IVF-specific query parameters.
39pub struct IvfQueryParams {
40    pub(crate) handle: *mut zvec_rust_sys::zvec_ivf_query_params_t,
41}
42
43impl IvfQueryParams {
44    /// Creates new IVF query parameters.
45    pub fn new(nprobe: i32, is_using_refiner: bool, scale_factor: f32) -> Self {
46        let handle = unsafe {
47            zvec_rust_sys::zvec_query_params_ivf_create(nprobe, is_using_refiner, scale_factor)
48        };
49        IvfQueryParams { handle }
50    }
51
52    /// Sets the number of probe clusters.
53    pub fn set_nprobe(&mut self, nprobe: i32) -> Result<()> {
54        check_error(unsafe { zvec_rust_sys::zvec_query_params_ivf_set_nprobe(self.handle, nprobe) })
55    }
56
57    /// Returns the number of probe clusters.
58    pub fn nprobe(&self) -> i32 {
59        unsafe { zvec_rust_sys::zvec_query_params_ivf_get_nprobe(self.handle) }
60    }
61}
62
63impl Drop for IvfQueryParams {
64    fn drop(&mut self) {
65        if !self.handle.is_null() {
66            unsafe { zvec_rust_sys::zvec_query_params_ivf_destroy(self.handle) };
67        }
68    }
69}
70
71/// Flat-specific query parameters.
72pub struct FlatQueryParams {
73    pub(crate) handle: *mut zvec_rust_sys::zvec_flat_query_params_t,
74}
75
76impl FlatQueryParams {
77    /// Creates new Flat query parameters.
78    pub fn new(is_using_refiner: bool, scale_factor: f32) -> Self {
79        let handle =
80            unsafe { zvec_rust_sys::zvec_query_params_flat_create(is_using_refiner, scale_factor) };
81        FlatQueryParams { handle }
82    }
83}
84
85impl Drop for FlatQueryParams {
86    fn drop(&mut self) {
87        if !self.handle.is_null() {
88            unsafe { zvec_rust_sys::zvec_query_params_flat_destroy(self.handle) };
89        }
90    }
91}
92
93/// DiskANN-specific query parameters.
94pub struct DiskannQueryParams {
95    pub(crate) handle: *mut zvec_rust_sys::zvec_diskann_query_params_t,
96}
97
98impl DiskannQueryParams {
99    /// Creates new DiskANN query parameters.
100    ///
101    /// - `list_size`: search frontier size (default in the C library: 300)
102    pub fn new(list_size: i32) -> Self {
103        let handle = unsafe { zvec_rust_sys::zvec_query_params_diskann_create(list_size) };
104        DiskannQueryParams { handle }
105    }
106
107    /// Sets the search-time frontier size.
108    pub fn set_list_size(&mut self, list_size: i32) -> Result<()> {
109        check_error(unsafe {
110            zvec_rust_sys::zvec_query_params_diskann_set_list_size(self.handle, list_size)
111        })
112    }
113
114    /// Returns the search-time frontier size.
115    pub fn list_size(&self) -> i32 {
116        unsafe { zvec_rust_sys::zvec_query_params_diskann_get_list_size(self.handle) }
117    }
118}
119
120impl Drop for DiskannQueryParams {
121    fn drop(&mut self) {
122        if !self.handle.is_null() {
123            unsafe { zvec_rust_sys::zvec_query_params_diskann_destroy(self.handle) };
124        }
125    }
126}
127
128/// FTS-specific query parameters controlling the default boolean operator.
129pub struct FtsQueryParams {
130    pub(crate) handle: *mut zvec_rust_sys::zvec_fts_query_params_t,
131}
132
133impl FtsQueryParams {
134    /// Creates new FTS query parameters.
135    ///
136    /// `default_operator` sets the boolean operator for adjacent bare terms
137    /// ("OR" or "AND", case-insensitive). Pass `None` to use the library default.
138    pub fn new(default_operator: Option<&str>) -> Result<Self> {
139        let c_op = default_operator.map(to_cstring).transpose()?;
140        let handle = unsafe {
141            zvec_rust_sys::zvec_query_params_fts_create(
142                c_op.as_ref().map_or(std::ptr::null(), |c| c.as_ptr()),
143            )
144        };
145        if handle.is_null() {
146            return Err(Error {
147                code: ErrorCode::InternalError,
148                message: "failed to create FTS query params".into(),
149            });
150        }
151        Ok(FtsQueryParams { handle })
152    }
153
154    /// Sets the default boolean operator.
155    pub fn set_default_operator(&mut self, op: &str) -> Result<()> {
156        let c_op = to_cstring(op)?;
157        check_error(unsafe {
158            zvec_rust_sys::zvec_query_params_fts_set_default_operator(self.handle, c_op.as_ptr())
159        })
160    }
161
162    /// Returns the default boolean operator.
163    pub fn default_operator(&self) -> Option<String> {
164        unsafe {
165            let ptr = zvec_rust_sys::zvec_query_params_fts_get_default_operator(self.handle);
166            if ptr.is_null() {
167                return None;
168            }
169            Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
170        }
171    }
172}
173
174impl Drop for FtsQueryParams {
175    fn drop(&mut self) {
176        if !self.handle.is_null() {
177            unsafe { zvec_rust_sys::zvec_query_params_fts_destroy(self.handle) };
178        }
179    }
180}
181
182/// FTS query payload holding the query expression and match string.
183///
184/// - `query_string`: a boolean / advanced query expression
185/// - `match_string`: a natural-language match string
186pub struct Fts {
187    pub(crate) handle: *mut zvec_rust_sys::zvec_fts_t,
188}
189
190impl Fts {
191    /// Creates a new FTS query payload.
192    pub fn new() -> Result<Self> {
193        let handle = unsafe { zvec_rust_sys::zvec_fts_create() };
194        if handle.is_null() {
195            return Err(Error {
196                code: ErrorCode::InternalError,
197                message: "failed to create FTS payload".into(),
198            });
199        }
200        Ok(Fts { handle })
201    }
202
203    /// Sets the boolean / advanced query expression.
204    pub fn set_query_string(&mut self, query: &str) -> Result<()> {
205        let c = to_cstring(query)?;
206        check_error(unsafe { zvec_rust_sys::zvec_fts_set_query_string(self.handle, c.as_ptr()) })
207    }
208
209    /// Sets the natural-language match string.
210    pub fn set_match_string(&mut self, match_str: &str) -> Result<()> {
211        let c = to_cstring(match_str)?;
212        check_error(unsafe { zvec_rust_sys::zvec_fts_set_match_string(self.handle, c.as_ptr()) })
213    }
214
215    /// Returns the query expression, or `None` if not set.
216    pub fn query_string(&self) -> Option<String> {
217        unsafe {
218            let ptr = zvec_rust_sys::zvec_fts_get_query_string(self.handle);
219            if ptr.is_null() {
220                return None;
221            }
222            Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
223        }
224    }
225
226    /// Returns the match string, or `None` if not set.
227    pub fn match_string(&self) -> Option<String> {
228        unsafe {
229            let ptr = zvec_rust_sys::zvec_fts_get_match_string(self.handle);
230            if ptr.is_null() {
231                return None;
232            }
233            Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
234        }
235    }
236}
237
238impl Drop for Fts {
239    fn drop(&mut self) {
240        if !self.handle.is_null() {
241            unsafe { zvec_rust_sys::zvec_fts_destroy(self.handle) };
242        }
243    }
244}
245
246/// A vector similarity search query.
247pub struct SearchQuery {
248    pub(crate) handle: *mut zvec_rust_sys::zvec_vector_query_t,
249}
250
251impl SearchQuery {
252    /// Returns the raw FFI handle.
253    ///
254    /// # Safety
255    /// The caller must not use the handle after the `SearchQuery` is dropped.
256    pub unsafe fn as_raw(&self) -> *mut zvec_rust_sys::zvec_vector_query_t {
257        self.handle
258    }
259
260    /// Creates a `SearchQuery` from a raw FFI handle.
261    ///
262    /// # Safety
263    /// The caller must ensure the handle is valid and was created by the zvec C API.
264    /// The `SearchQuery` takes ownership and will call `zvec_vector_query_destroy` on drop.
265    pub unsafe fn from_raw(handle: *mut zvec_rust_sys::zvec_vector_query_t) -> Self {
266        SearchQuery { handle }
267    }
268
269    /// Creates a new vector query with the given field name, query vector, and topk.
270    pub fn new(field_name: &str, vector: &[f32], topk: i32) -> Result<Self> {
271        let handle = unsafe { zvec_rust_sys::zvec_vector_query_create() };
272        if handle.is_null() {
273            return Err(Error {
274                code: ErrorCode::InternalError,
275                message: "failed to create vector query".into(),
276            });
277        }
278
279        let c_field = to_cstring(field_name)?;
280        let query = SearchQuery { handle };
281
282        check_error(unsafe {
283            zvec_rust_sys::zvec_vector_query_set_field_name(query.handle, c_field.as_ptr())
284        })?;
285        check_error(unsafe {
286            zvec_rust_sys::zvec_vector_query_set_query_vector(
287                query.handle,
288                vector.as_ptr() as *const c_void,
289                std::mem::size_of_val(vector),
290            )
291        })?;
292        check_error(unsafe { zvec_rust_sys::zvec_vector_query_set_topk(query.handle, topk) })?;
293
294        Ok(query)
295    }
296
297    /// Creates a pure full-text (keyword-only) search query with no dense vector.
298    ///
299    /// [`SearchQuery::new`] and [`SearchQueryBuilder::build`] both require a query
300    /// vector, so an FTS clause can only ever be layered on top of a dense vector
301    /// (hybrid search). This constructor reaches the C API's vector-less FTS path:
302    /// once an FTS payload is attached, the underlying `zvec_vector_query` does not
303    /// require a query vector, enabling keyword-only retrieval.
304    ///
305    /// `field_name` must be the FTS-indexed field.
306    pub fn fts(field_name: &str, fts: &Fts, topk: i32) -> Result<Self> {
307        let handle = unsafe { zvec_rust_sys::zvec_vector_query_create() };
308        if handle.is_null() {
309            return Err(Error {
310                code: ErrorCode::InternalError,
311                message: "failed to create vector query".into(),
312            });
313        }
314
315        // Wrap the handle before any fallible call so `Drop` frees it on early return.
316        let mut query = SearchQuery { handle };
317        let c_field = to_cstring(field_name)?;
318
319        check_error(unsafe {
320            zvec_rust_sys::zvec_vector_query_set_field_name(query.handle, c_field.as_ptr())
321        })?;
322        check_error(unsafe { zvec_rust_sys::zvec_vector_query_set_topk(query.handle, topk) })?;
323        query.set_fts(fts)?;
324
325        Ok(query)
326    }
327
328    /// Returns a builder for constructing a search query.
329    pub fn builder() -> SearchQueryBuilder {
330        SearchQueryBuilder::new()
331    }
332
333    /// Sets the filter expression.
334    pub fn set_filter(&mut self, filter: &str) -> Result<()> {
335        let c_filter = to_cstring(filter)?;
336        check_error(unsafe {
337            zvec_rust_sys::zvec_vector_query_set_filter(self.handle, c_filter.as_ptr())
338        })
339    }
340
341    /// Sets whether to include vector data in results.
342    pub fn set_include_vector(&mut self, include: bool) -> Result<()> {
343        check_error(unsafe {
344            zvec_rust_sys::zvec_vector_query_set_include_vector(self.handle, include)
345        })
346    }
347
348    /// Sets whether to include doc ID in results.
349    pub fn set_include_doc_id(&mut self, include: bool) -> Result<()> {
350        check_error(unsafe {
351            zvec_rust_sys::zvec_vector_query_set_include_doc_id(self.handle, include)
352        })
353    }
354
355    /// Sets the output fields to include in results.
356    pub fn set_output_fields(&mut self, fields: &[&str]) -> Result<()> {
357        let c_fields: Vec<_> = fields
358            .iter()
359            .map(|f| to_cstring(f))
360            .collect::<Result<Vec<_>>>()?;
361        let c_ptrs: Vec<_> = c_fields.iter().map(|f| f.as_ptr()).collect();
362        check_error(unsafe {
363            zvec_rust_sys::zvec_vector_query_set_output_fields(
364                self.handle,
365                c_ptrs.as_ptr(),
366                c_ptrs.len(),
367            )
368        })
369    }
370
371    /// Sets HNSW query parameters (takes ownership on success).
372    pub fn set_hnsw_params(&mut self, mut params: HnswQueryParams) -> Result<()> {
373        check_error(unsafe {
374            zvec_rust_sys::zvec_vector_query_set_hnsw_params(self.handle, params.handle)
375        })?;
376        // Ownership transferred to query only on success; prevent double-free
377        params.handle = std::ptr::null_mut();
378        Ok(())
379    }
380
381    /// Sets IVF query parameters (takes ownership on success).
382    pub fn set_ivf_params(&mut self, mut params: IvfQueryParams) -> Result<()> {
383        check_error(unsafe {
384            zvec_rust_sys::zvec_vector_query_set_ivf_params(self.handle, params.handle)
385        })?;
386        params.handle = std::ptr::null_mut();
387        Ok(())
388    }
389
390    /// Sets Flat query parameters (takes ownership on success).
391    pub fn set_flat_params(&mut self, mut params: FlatQueryParams) -> Result<()> {
392        check_error(unsafe {
393            zvec_rust_sys::zvec_vector_query_set_flat_params(self.handle, params.handle)
394        })?;
395        params.handle = std::ptr::null_mut();
396        Ok(())
397    }
398
399    /// Sets DiskANN query parameters (takes ownership on success).
400    pub fn set_diskann_params(&mut self, mut params: DiskannQueryParams) -> Result<()> {
401        check_error(unsafe {
402            zvec_rust_sys::zvec_vector_query_set_diskann_params(self.handle, params.handle)
403        })?;
404        params.handle = std::ptr::null_mut();
405        Ok(())
406    }
407
408    /// Sets FTS query parameters (takes ownership on success).
409    pub fn set_fts_params(&mut self, mut params: FtsQueryParams) -> Result<()> {
410        check_error(unsafe {
411            zvec_rust_sys::zvec_vector_query_set_fts_params(self.handle, params.handle)
412        })?;
413        params.handle = std::ptr::null_mut();
414        Ok(())
415    }
416
417    /// Sets FTS payload (payload is copied, caller retains ownership).
418    pub fn set_fts(&mut self, fts: &Fts) -> Result<()> {
419        check_error(unsafe { zvec_rust_sys::zvec_vector_query_set_fts(self.handle, fts.handle) })
420    }
421}
422
423impl Drop for SearchQuery {
424    fn drop(&mut self) {
425        if !self.handle.is_null() {
426            unsafe { zvec_rust_sys::zvec_vector_query_destroy(self.handle) };
427        }
428    }
429}
430
431/// Builder for constructing a [`SearchQuery`].
432pub struct SearchQueryBuilder {
433    field_name: Option<String>,
434    vector: Option<Vec<f32>>,
435    topk: i32,
436    filter: Option<String>,
437    include_vector: Option<bool>,
438    include_doc_id: Option<bool>,
439    output_fields: Option<Vec<String>>,
440    fts_query_string: Option<String>,
441    fts_match_string: Option<String>,
442}
443
444impl SearchQueryBuilder {
445    fn new() -> Self {
446        SearchQueryBuilder {
447            field_name: None,
448            vector: None,
449            topk: 10,
450            filter: None,
451            include_vector: None,
452            include_doc_id: None,
453            output_fields: None,
454            fts_query_string: None,
455            fts_match_string: None,
456        }
457    }
458
459    /// Sets the field name to query.
460    pub fn field_name(mut self, name: &str) -> Self {
461        self.field_name = Some(name.to_string());
462        self
463    }
464
465    /// Sets the query vector.
466    pub fn vector(mut self, vector: &[f32]) -> Self {
467        self.vector = Some(vector.to_vec());
468        self
469    }
470
471    /// Sets the number of results to return.
472    pub fn topk(mut self, topk: i32) -> Self {
473        self.topk = topk;
474        self
475    }
476
477    /// Sets the filter expression.
478    pub fn filter(mut self, filter: &str) -> Self {
479        self.filter = Some(filter.to_string());
480        self
481    }
482
483    /// Sets whether to include vector data in results.
484    pub fn include_vector(mut self, include: bool) -> Self {
485        self.include_vector = Some(include);
486        self
487    }
488
489    /// Sets whether to include doc ID in results.
490    pub fn include_doc_id(mut self, include: bool) -> Self {
491        self.include_doc_id = Some(include);
492        self
493    }
494
495    /// Sets the output fields.
496    pub fn output_fields(mut self, fields: &[&str]) -> Self {
497        self.output_fields = Some(fields.iter().map(|s| s.to_string()).collect());
498        self
499    }
500
501    /// Sets the FTS boolean / advanced query expression.
502    pub fn fts_query_string(mut self, query: &str) -> Self {
503        self.fts_query_string = Some(query.to_string());
504        self
505    }
506
507    /// Sets the FTS natural-language match string.
508    pub fn fts_match_string(mut self, match_str: &str) -> Self {
509        self.fts_match_string = Some(match_str.to_string());
510        self
511    }
512
513    /// Builds the search query.
514    pub fn build(self) -> Result<SearchQuery> {
515        let field_name = self.field_name.ok_or_else(|| Error {
516            code: ErrorCode::InvalidArgument,
517            message: "field_name is required".into(),
518        })?;
519        let vector = self.vector.ok_or_else(|| Error {
520            code: ErrorCode::InvalidArgument,
521            message: "vector is required".into(),
522        })?;
523
524        let mut query = SearchQuery::new(&field_name, &vector, self.topk)?;
525
526        if let Some(filter) = &self.filter {
527            query.set_filter(filter)?;
528        }
529        if let Some(include) = self.include_vector {
530            query.set_include_vector(include)?;
531        }
532        if let Some(include) = self.include_doc_id {
533            query.set_include_doc_id(include)?;
534        }
535        if let Some(fields) = &self.output_fields {
536            let field_refs: Vec<&str> = fields.iter().map(|s| s.as_str()).collect();
537            query.set_output_fields(&field_refs)?;
538        }
539        if self.fts_query_string.is_some() || self.fts_match_string.is_some() {
540            let mut fts = Fts::new()?;
541            if let Some(qs) = &self.fts_query_string {
542                fts.set_query_string(qs)?;
543            }
544            if let Some(ms) = &self.fts_match_string {
545                fts.set_match_string(ms)?;
546            }
547            query.set_fts(&fts)?;
548        }
549
550        Ok(query)
551    }
552}
553
554/// A grouped vector similarity search query.
555pub struct GroupBySearchQuery {
556    pub(crate) handle: *mut zvec_rust_sys::zvec_group_by_vector_query_t,
557}
558
559impl GroupBySearchQuery {
560    /// Creates a new group-by search query.
561    pub fn new(
562        field_name: &str,
563        group_by_field: &str,
564        vector: &[f32],
565        group_count: u32,
566        group_topk: u32,
567    ) -> Result<Self> {
568        let handle = unsafe { zvec_rust_sys::zvec_group_by_vector_query_create() };
569        if handle.is_null() {
570            return Err(Error {
571                code: ErrorCode::InternalError,
572                message: "failed to create group by vector query".into(),
573            });
574        }
575
576        let c_field = to_cstring(field_name)?;
577        let c_group_field = to_cstring(group_by_field)?;
578
579        check_error(unsafe {
580            zvec_rust_sys::zvec_group_by_vector_query_set_field_name(handle, c_field.as_ptr())
581        })?;
582        check_error(unsafe {
583            zvec_rust_sys::zvec_group_by_vector_query_set_group_by_field_name(
584                handle,
585                c_group_field.as_ptr(),
586            )
587        })?;
588        check_error(unsafe {
589            zvec_rust_sys::zvec_group_by_vector_query_set_query_vector(
590                handle,
591                vector.as_ptr() as *const c_void,
592                std::mem::size_of_val(vector),
593            )
594        })?;
595        check_error(unsafe {
596            zvec_rust_sys::zvec_group_by_vector_query_set_group_count(handle, group_count)
597        })?;
598        check_error(unsafe {
599            zvec_rust_sys::zvec_group_by_vector_query_set_topk_per_group(handle, group_topk)
600        })?;
601
602        Ok(GroupBySearchQuery { handle })
603    }
604
605    /// Sets the filter expression.
606    pub fn set_filter(&mut self, filter: &str) -> Result<()> {
607        let c_filter = to_cstring(filter)?;
608        check_error(unsafe {
609            zvec_rust_sys::zvec_group_by_vector_query_set_filter(self.handle, c_filter.as_ptr())
610        })
611    }
612
613    /// Sets whether to include vector data in results.
614    pub fn set_include_vector(&mut self, include: bool) -> Result<()> {
615        check_error(unsafe {
616            zvec_rust_sys::zvec_group_by_vector_query_set_include_vector(self.handle, include)
617        })
618    }
619
620    /// Sets the output fields.
621    pub fn set_output_fields(&mut self, fields: &[&str]) -> Result<()> {
622        let c_fields: Vec<_> = fields
623            .iter()
624            .map(|f| to_cstring(f))
625            .collect::<Result<Vec<_>>>()?;
626        let c_ptrs: Vec<_> = c_fields.iter().map(|f| f.as_ptr()).collect();
627        check_error(unsafe {
628            zvec_rust_sys::zvec_group_by_vector_query_set_output_fields(
629                self.handle,
630                c_ptrs.as_ptr(),
631                c_ptrs.len(),
632            )
633        })
634    }
635
636    /// Sets HNSW query parameters (takes ownership on success).
637    pub fn set_hnsw_params(&mut self, mut params: HnswQueryParams) -> Result<()> {
638        check_error(unsafe {
639            zvec_rust_sys::zvec_group_by_vector_query_set_hnsw_params(self.handle, params.handle)
640        })?;
641        // Ownership transferred to query only on success; prevent double-free
642        params.handle = std::ptr::null_mut();
643        Ok(())
644    }
645
646    /// Sets IVF query parameters (takes ownership on success).
647    pub fn set_ivf_params(&mut self, mut params: IvfQueryParams) -> Result<()> {
648        check_error(unsafe {
649            zvec_rust_sys::zvec_group_by_vector_query_set_ivf_params(self.handle, params.handle)
650        })?;
651        // Ownership transferred to query only on success; prevent double-free
652        params.handle = std::ptr::null_mut();
653        Ok(())
654    }
655
656    /// Sets Flat query parameters (takes ownership on success).
657    pub fn set_flat_params(&mut self, mut params: FlatQueryParams) -> Result<()> {
658        check_error(unsafe {
659            zvec_rust_sys::zvec_group_by_vector_query_set_flat_params(self.handle, params.handle)
660        })?;
661        // Ownership transferred to query only on success; prevent double-free
662        params.handle = std::ptr::null_mut();
663        Ok(())
664    }
665
666    /// Sets DiskANN query parameters (takes ownership on success).
667    pub fn set_diskann_params(&mut self, mut params: DiskannQueryParams) -> Result<()> {
668        check_error(unsafe {
669            zvec_rust_sys::zvec_group_by_vector_query_set_diskann_params(self.handle, params.handle)
670        })?;
671        // Ownership transferred to query only on success; prevent double-free
672        params.handle = std::ptr::null_mut();
673        Ok(())
674    }
675}
676
677impl Drop for GroupBySearchQuery {
678    fn drop(&mut self) {
679        if !self.handle.is_null() {
680            unsafe { zvec_rust_sys::zvec_group_by_vector_query_destroy(self.handle) };
681        }
682    }
683}
684
685#[cfg(test)]
686mod tests {
687    use super::*;
688
689    #[test]
690    fn test_vector_query_builder_default_values() {
691        let builder = SearchQueryBuilder::new();
692        assert!(builder.field_name.is_none());
693        assert!(builder.vector.is_none());
694        assert_eq!(builder.topk, 10);
695        assert!(builder.filter.is_none());
696        assert!(builder.include_vector.is_none());
697        assert!(builder.include_doc_id.is_none());
698        assert!(builder.output_fields.is_none());
699    }
700
701    #[test]
702    fn test_vector_query_builder_setters() {
703        let builder = SearchQueryBuilder::new()
704            .field_name("test_field")
705            .vector(&[1.0, 2.0, 3.0])
706            .topk(5)
707            .filter("age > 18")
708            .include_vector(true)
709            .include_doc_id(false)
710            .output_fields(&["name", "age"]);
711
712        assert_eq!(builder.field_name, Some("test_field".to_string()));
713        assert_eq!(builder.vector, Some(vec![1.0, 2.0, 3.0]));
714        assert_eq!(builder.topk, 5);
715        assert_eq!(builder.filter, Some("age > 18".to_string()));
716        assert_eq!(builder.include_vector, Some(true));
717        assert_eq!(builder.include_doc_id, Some(false));
718        assert_eq!(
719            builder.output_fields,
720            Some(vec!["name".to_string(), "age".to_string()])
721        );
722    }
723
724    #[test]
725    fn test_vector_query_builder_build_missing_field_name() {
726        let builder = SearchQueryBuilder::new().vector(&[1.0, 2.0, 3.0]);
727
728        let result = builder.build();
729        assert!(result.is_err());
730        if let Err(e) = result {
731            assert_eq!(e.code, ErrorCode::InvalidArgument);
732            assert!(e.message.contains("field_name is required"));
733        }
734    }
735
736    #[test]
737    fn test_vector_query_builder_build_missing_vector() {
738        let builder = SearchQueryBuilder::new().field_name("test_field");
739
740        let result = builder.build();
741        assert!(result.is_err());
742        if let Err(e) = result {
743            assert_eq!(e.code, ErrorCode::InvalidArgument);
744            assert!(e.message.contains("vector is required"));
745        }
746    }
747
748    #[test]
749    fn test_vector_query_builder_builder_method() {
750        let builder = SearchQuery::builder();
751        assert!(builder.field_name.is_none());
752        assert!(builder.vector.is_none());
753        assert_eq!(builder.topk, 10);
754    }
755
756    #[test]
757    fn test_vector_query_builder_partial_setters() {
758        let builder = SearchQueryBuilder::new()
759            .field_name("test_field")
760            .vector(&[1.0, 2.0])
761            .filter("status = 'active'");
762        assert_eq!(builder.field_name, Some("test_field".to_string()));
763        assert_eq!(builder.vector, Some(vec![1.0, 2.0]));
764        assert_eq!(builder.filter, Some("status = 'active'".to_string()));
765        assert!(builder.include_vector.is_none());
766        assert!(builder.output_fields.is_none());
767    }
768
769    #[test]
770    fn test_vector_query_builder_empty_vector() {
771        let builder = SearchQueryBuilder::new()
772            .field_name("test_field")
773            .vector(&[]);
774        assert_eq!(builder.vector, Some(vec![]));
775    }
776
777    #[test]
778    fn test_vector_query_builder_empty_output_fields() {
779        let builder = SearchQueryBuilder::new()
780            .field_name("test_field")
781            .vector(&[1.0])
782            .output_fields(&[]);
783        assert_eq!(builder.output_fields, Some(vec![]));
784    }
785
786    #[test]
787    fn test_vector_query_builder_topk_zero() {
788        let builder = SearchQueryBuilder::new()
789            .field_name("test_field")
790            .vector(&[1.0])
791            .topk(0);
792        assert_eq!(builder.topk, 0);
793    }
794
795    #[test]
796    fn test_vector_query_builder_topk_negative() {
797        let builder = SearchQueryBuilder::new()
798            .field_name("test_field")
799            .vector(&[1.0])
800            .topk(-1);
801        assert_eq!(builder.topk, -1);
802    }
803
804    #[test]
805    fn test_vector_query_builder_overwrite_field_name() {
806        let builder = SearchQueryBuilder::new()
807            .field_name("first_field")
808            .field_name("second_field");
809        assert_eq!(builder.field_name, Some("second_field".to_string()));
810    }
811
812    #[test]
813    fn test_vector_query_builder_large_vector() {
814        let large_vector: Vec<f32> = (0..1024).map(|i| i as f32).collect();
815        let builder = SearchQueryBuilder::new()
816            .field_name("test_field")
817            .vector(&large_vector);
818        assert_eq!(builder.vector.as_ref().unwrap().len(), 1024);
819    }
820
821    #[test]
822    fn test_fts_query_no_vector() {
823        // A pure FTS query must build without a query vector; the builder path
824        // (which requires `vector`) cannot express this.
825        let mut fts = Fts::new().expect("create fts payload");
826        fts.set_match_string("hello world")
827            .expect("set match string");
828
829        let query = SearchQuery::fts("content", &fts, 10);
830        assert!(
831            query.is_ok(),
832            "pure FTS query should build without a vector"
833        );
834        assert!(!unsafe { query.unwrap().as_raw() }.is_null());
835    }
836
837    #[test]
838    fn test_diskann_query_params_create_and_getters() {
839        let mut params = DiskannQueryParams::new(200);
840        assert_eq!(params.list_size(), 200);
841        params.set_list_size(300).expect("set list_size");
842        assert_eq!(params.list_size(), 300);
843    }
844}