Skip to main content

zvec_rust/
doc.rs

1use std::ffi::CStr;
2use std::os::raw::c_void;
3
4use crate::error::{check_error, to_cstring, Error, ErrorCode, Result};
5use crate::types::DataType;
6
7/// A document in a zvec collection.
8///
9/// Documents contain typed fields and are used for both writing data to
10/// and reading data from collections.
11pub struct Doc {
12    pub(crate) handle: *mut zvec_rust_sys::zvec_doc_t,
13    owned: bool,
14}
15
16impl Doc {
17    /// Returns the raw FFI handle.
18    ///
19    /// # Safety
20    /// The caller must not use the handle after the `Doc` is dropped.
21    pub unsafe fn as_raw(&self) -> *mut zvec_rust_sys::zvec_doc_t {
22        self.handle
23    }
24
25    /// Creates an owning `Doc` from a raw FFI handle.
26    ///
27    /// # Safety
28    /// The caller must ensure the handle is valid and was created by the zvec C API.
29    /// The `Doc` takes ownership and will call `zvec_doc_destroy` on drop.
30    pub unsafe fn from_raw(handle: *mut zvec_rust_sys::zvec_doc_t) -> Self {
31        Doc {
32            handle,
33            owned: true,
34        }
35    }
36
37    /// Creates a new empty document.
38    pub fn new() -> Result<Self> {
39        let handle = unsafe { zvec_rust_sys::zvec_doc_create() };
40        if handle.is_null() {
41            return Err(Error {
42                code: ErrorCode::InternalError,
43                message: "failed to create document".into(),
44            });
45        }
46        Ok(Doc {
47            handle,
48            owned: true,
49        })
50    }
51
52    /// Creates a non-owning wrapper around an existing handle.
53    #[allow(dead_code)]
54    pub(crate) fn from_borrowed(handle: *mut zvec_rust_sys::zvec_doc_t) -> Self {
55        Doc {
56            handle,
57            owned: false,
58        }
59    }
60
61    /// Sets the primary key.
62    pub fn set_pk(&mut self, pk: &str) {
63        let c_pk = to_cstring(pk).expect("pk must not contain null bytes");
64        unsafe { zvec_rust_sys::zvec_doc_set_pk(self.handle, c_pk.as_ptr()) };
65    }
66
67    /// Returns the primary key, or `None` if not set.
68    pub fn get_pk(&self) -> Option<&str> {
69        unsafe {
70            let ptr = zvec_rust_sys::zvec_doc_get_pk_pointer(self.handle);
71            if ptr.is_null() {
72                None
73            } else {
74                CStr::from_ptr(ptr).to_str().ok()
75            }
76        }
77    }
78
79    /// Returns the document score (set by query results).
80    pub fn get_score(&self) -> f32 {
81        unsafe { zvec_rust_sys::zvec_doc_get_score(self.handle) }
82    }
83
84    #[allow(dead_code)]
85    pub(crate) fn get_doc_id(&self) -> u64 {
86        unsafe { zvec_rust_sys::zvec_doc_get_doc_id(self.handle) }
87    }
88
89    /// Returns the number of fields in the document.
90    pub fn field_count(&self) -> usize {
91        unsafe { zvec_rust_sys::zvec_doc_get_field_count(self.handle) }
92    }
93
94    /// Returns whether the document is empty.
95    pub fn is_empty(&self) -> bool {
96        unsafe { zvec_rust_sys::zvec_doc_is_empty(self.handle) }
97    }
98
99    /// Returns whether the document contains the specified field.
100    pub fn has_field(&self, name: &str) -> bool {
101        let c_name = match to_cstring(name) {
102            Ok(s) => s,
103            Err(_) => return false,
104        };
105        unsafe { zvec_rust_sys::zvec_doc_has_field(self.handle, c_name.as_ptr()) }
106    }
107
108    /// Returns whether the specified field is null.
109    pub fn is_field_null(&self, name: &str) -> bool {
110        let c_name = match to_cstring(name) {
111            Ok(s) => s,
112            Err(_) => return false,
113        };
114        unsafe { zvec_rust_sys::zvec_doc_is_field_null(self.handle, c_name.as_ptr()) }
115    }
116
117    // =========================================================================
118    // Field setters
119    // =========================================================================
120
121    /// Adds a string field.
122    pub fn add_string(&mut self, name: &str, value: &str) -> Result<()> {
123        let c_name = to_cstring(name)?;
124        let c_value = to_cstring(value)?;
125        // The C API `zvec_doc_add_field_by_value` is length-delimited: it stores
126        // exactly `size` bytes. Use `as_bytes()` (without the trailing NUL) so the
127        // stored value is the string itself; sending `as_bytes_with_nul()` would
128        // append a NUL to the stored data and break exact string filters.
129        let bytes = c_value.as_bytes();
130        check_error(unsafe {
131            zvec_rust_sys::zvec_doc_add_field_by_value(
132                self.handle,
133                c_name.as_ptr(),
134                DataType::String as u32,
135                bytes.as_ptr() as *const c_void,
136                bytes.len(),
137            )
138        })
139    }
140
141    /// Adds a boolean field.
142    pub fn add_bool(&mut self, name: &str, value: bool) -> Result<()> {
143        let c_name = to_cstring(name)?;
144        check_error(unsafe {
145            zvec_rust_sys::zvec_doc_add_field_by_value(
146                self.handle,
147                c_name.as_ptr(),
148                DataType::Bool as u32,
149                &value as *const bool as *const c_void,
150                std::mem::size_of::<bool>(),
151            )
152        })
153    }
154
155    /// Adds an i32 field.
156    pub fn add_i32(&mut self, name: &str, value: i32) -> Result<()> {
157        let c_name = to_cstring(name)?;
158        check_error(unsafe {
159            zvec_rust_sys::zvec_doc_add_field_by_value(
160                self.handle,
161                c_name.as_ptr(),
162                DataType::Int32 as u32,
163                &value as *const i32 as *const c_void,
164                std::mem::size_of::<i32>(),
165            )
166        })
167    }
168
169    /// Adds an i64 field.
170    pub fn add_i64(&mut self, name: &str, value: i64) -> Result<()> {
171        let c_name = to_cstring(name)?;
172        check_error(unsafe {
173            zvec_rust_sys::zvec_doc_add_field_by_value(
174                self.handle,
175                c_name.as_ptr(),
176                DataType::Int64 as u32,
177                &value as *const i64 as *const c_void,
178                std::mem::size_of::<i64>(),
179            )
180        })
181    }
182
183    /// Adds a u32 field.
184    pub fn add_u32(&mut self, name: &str, value: u32) -> Result<()> {
185        let c_name = to_cstring(name)?;
186        check_error(unsafe {
187            zvec_rust_sys::zvec_doc_add_field_by_value(
188                self.handle,
189                c_name.as_ptr(),
190                DataType::Uint32 as u32,
191                &value as *const u32 as *const c_void,
192                std::mem::size_of::<u32>(),
193            )
194        })
195    }
196
197    /// Adds a u64 field.
198    pub fn add_u64(&mut self, name: &str, value: u64) -> Result<()> {
199        let c_name = to_cstring(name)?;
200        check_error(unsafe {
201            zvec_rust_sys::zvec_doc_add_field_by_value(
202                self.handle,
203                c_name.as_ptr(),
204                DataType::Uint64 as u32,
205                &value as *const u64 as *const c_void,
206                std::mem::size_of::<u64>(),
207            )
208        })
209    }
210
211    /// Adds an f32 field.
212    pub fn add_f32(&mut self, name: &str, value: f32) -> Result<()> {
213        let c_name = to_cstring(name)?;
214        check_error(unsafe {
215            zvec_rust_sys::zvec_doc_add_field_by_value(
216                self.handle,
217                c_name.as_ptr(),
218                DataType::Float as u32,
219                &value as *const f32 as *const c_void,
220                std::mem::size_of::<f32>(),
221            )
222        })
223    }
224
225    /// Adds an f64 field.
226    pub fn add_f64(&mut self, name: &str, value: f64) -> Result<()> {
227        let c_name = to_cstring(name)?;
228        check_error(unsafe {
229            zvec_rust_sys::zvec_doc_add_field_by_value(
230                self.handle,
231                c_name.as_ptr(),
232                DataType::Double as u32,
233                &value as *const f64 as *const c_void,
234                std::mem::size_of::<f64>(),
235            )
236        })
237    }
238
239    /// Adds a dense FP32 vector field.
240    pub fn add_vector_f32(&mut self, name: &str, vector: &[f32]) -> Result<()> {
241        let c_name = to_cstring(name)?;
242        check_error(unsafe {
243            zvec_rust_sys::zvec_doc_add_field_by_value(
244                self.handle,
245                c_name.as_ptr(),
246                DataType::VectorFp32 as u32,
247                vector.as_ptr() as *const c_void,
248                std::mem::size_of_val(vector),
249            )
250        })
251    }
252
253    /// Adds a dense FP64 vector field.
254    pub fn add_vector_f64(&mut self, name: &str, vector: &[f64]) -> Result<()> {
255        let c_name = to_cstring(name)?;
256        check_error(unsafe {
257            zvec_rust_sys::zvec_doc_add_field_by_value(
258                self.handle,
259                c_name.as_ptr(),
260                DataType::VectorFp64 as u32,
261                vector.as_ptr() as *const c_void,
262                std::mem::size_of_val(vector),
263            )
264        })
265    }
266
267    /// Adds a binary (raw bytes) field.
268    pub fn add_binary(&mut self, name: &str, value: &[u8]) -> Result<()> {
269        let c_name = to_cstring(name)?;
270        check_error(unsafe {
271            zvec_rust_sys::zvec_doc_add_field_by_value(
272                self.handle,
273                c_name.as_ptr(),
274                DataType::Binary as u32,
275                value.as_ptr() as *const c_void,
276                value.len(),
277            )
278        })
279    }
280
281    /// Adds a dense INT8 vector field.
282    pub fn add_vector_i8(&mut self, name: &str, vector: &[i8]) -> Result<()> {
283        let c_name = to_cstring(name)?;
284        check_error(unsafe {
285            zvec_rust_sys::zvec_doc_add_field_by_value(
286                self.handle,
287                c_name.as_ptr(),
288                DataType::VectorInt8 as u32,
289                vector.as_ptr() as *const c_void,
290                std::mem::size_of_val(vector),
291            )
292        })
293    }
294
295    /// Adds a dense INT16 vector field.
296    pub fn add_vector_i16(&mut self, name: &str, vector: &[i16]) -> Result<()> {
297        let c_name = to_cstring(name)?;
298        check_error(unsafe {
299            zvec_rust_sys::zvec_doc_add_field_by_value(
300                self.handle,
301                c_name.as_ptr(),
302                DataType::VectorInt16 as u32,
303                vector.as_ptr() as *const c_void,
304                std::mem::size_of_val(vector),
305            )
306        })
307    }
308
309    // =========================================================================
310    // Array field setters
311    // =========================================================================
312
313    fn add_typed_array<T>(&mut self, name: &str, data_type: DataType, values: &[T]) -> Result<()> {
314        let c_name = to_cstring(name)?;
315        check_error(unsafe {
316            zvec_rust_sys::zvec_doc_add_field_by_value(
317                self.handle,
318                c_name.as_ptr(),
319                data_type as u32,
320                values.as_ptr() as *const c_void,
321                std::mem::size_of_val(values),
322            )
323        })
324    }
325
326    /// Adds an array of i32 values.
327    pub fn add_array_i32(&mut self, name: &str, values: &[i32]) -> Result<()> {
328        self.add_typed_array(name, DataType::ArrayInt32, values)
329    }
330
331    /// Adds an array of i64 values.
332    pub fn add_array_i64(&mut self, name: &str, values: &[i64]) -> Result<()> {
333        self.add_typed_array(name, DataType::ArrayInt64, values)
334    }
335
336    /// Adds an array of u32 values.
337    pub fn add_array_u32(&mut self, name: &str, values: &[u32]) -> Result<()> {
338        self.add_typed_array(name, DataType::ArrayUint32, values)
339    }
340
341    /// Adds an array of u64 values.
342    pub fn add_array_u64(&mut self, name: &str, values: &[u64]) -> Result<()> {
343        self.add_typed_array(name, DataType::ArrayUint64, values)
344    }
345
346    /// Adds an array of f32 values.
347    pub fn add_array_f32(&mut self, name: &str, values: &[f32]) -> Result<()> {
348        self.add_typed_array(name, DataType::ArrayFloat, values)
349    }
350
351    /// Adds an array of f64 values.
352    pub fn add_array_f64(&mut self, name: &str, values: &[f64]) -> Result<()> {
353        self.add_typed_array(name, DataType::ArrayDouble, values)
354    }
355
356    /// Adds an array of boolean values.
357    pub fn add_array_bool(&mut self, name: &str, values: &[bool]) -> Result<()> {
358        self.add_typed_array(name, DataType::ArrayBool, values)
359    }
360
361    /// Sets a field to null.
362    pub fn set_field_null(&mut self, name: &str) -> Result<()> {
363        let c_name = to_cstring(name)?;
364        check_error(unsafe { zvec_rust_sys::zvec_doc_set_field_null(self.handle, c_name.as_ptr()) })
365    }
366
367    /// Removes a field from the document.
368    pub fn remove_field(&mut self, name: &str) -> Result<()> {
369        let c_name = to_cstring(name)?;
370        check_error(unsafe { zvec_rust_sys::zvec_doc_remove_field(self.handle, c_name.as_ptr()) })
371    }
372
373    // =========================================================================
374    // Field getters
375    // =========================================================================
376
377    fn get_basic_field<T: Copy + Default>(
378        &self,
379        name: &str,
380        data_type: DataType,
381    ) -> Result<Option<T>> {
382        if !self.has_field(name) || self.is_field_null(name) {
383            return Ok(None);
384        }
385        let c_name = to_cstring(name)?;
386        let mut value: T = T::default();
387        check_error(unsafe {
388            zvec_rust_sys::zvec_doc_get_field_value_basic(
389                self.handle,
390                c_name.as_ptr(),
391                data_type as u32,
392                &mut value as *mut T as *mut c_void,
393                std::mem::size_of::<T>(),
394            )
395        })?;
396        Ok(Some(value))
397    }
398
399    fn get_pointer_field(
400        &self,
401        name: &str,
402        data_type: DataType,
403    ) -> Result<Option<(*const c_void, usize)>> {
404        let c_name = to_cstring(name)?;
405        let mut value_ptr: *const c_void = std::ptr::null();
406        let mut value_size: usize = 0;
407        check_error(unsafe {
408            zvec_rust_sys::zvec_doc_get_field_value_pointer(
409                self.handle,
410                c_name.as_ptr(),
411                data_type as u32,
412                &mut value_ptr,
413                &mut value_size,
414            )
415        })?;
416        if value_ptr.is_null() || value_size == 0 {
417            return Ok(None);
418        }
419        Ok(Some((value_ptr, value_size)))
420    }
421
422    fn get_typed_vec<T: Copy>(&self, name: &str, data_type: DataType) -> Result<Option<Vec<T>>> {
423        let Some((ptr, size)) = self.get_pointer_field(name, data_type)? else {
424            return Ok(None);
425        };
426        let elem_size = std::mem::size_of::<T>();
427        if elem_size > 1 && size % elem_size != 0 {
428            return Err(Error {
429                code: ErrorCode::InternalError,
430                message: format!(
431                    "data size {} is not aligned to element size {}",
432                    size, elem_size
433                ),
434            });
435        }
436        let count = size / elem_size;
437        let slice = unsafe { std::slice::from_raw_parts(ptr as *const T, count) };
438        Ok(Some(slice.to_vec()))
439    }
440
441    /// Gets a string field value. Returns `Ok(None)` if the field does not exist or is null.
442    pub fn get_string(&self, name: &str) -> Result<Option<String>> {
443        let Some((ptr, _size)) = self.get_pointer_field(name, DataType::String)? else {
444            return Ok(None);
445        };
446        unsafe {
447            let cstr = CStr::from_ptr(ptr as *const std::os::raw::c_char);
448            Ok(Some(cstr.to_string_lossy().into_owned()))
449        }
450    }
451
452    /// Gets a boolean field value. Returns `Ok(None)` if the field does not exist or is null.
453    pub fn get_bool(&self, name: &str) -> Result<Option<bool>> {
454        self.get_basic_field(name, DataType::Bool)
455    }
456
457    /// Gets an i32 field value. Returns `Ok(None)` if the field does not exist or is null.
458    pub fn get_i32(&self, name: &str) -> Result<Option<i32>> {
459        self.get_basic_field(name, DataType::Int32)
460    }
461
462    /// Gets an i64 field value. Returns `Ok(None)` if the field does not exist or is null.
463    pub fn get_i64(&self, name: &str) -> Result<Option<i64>> {
464        self.get_basic_field(name, DataType::Int64)
465    }
466
467    /// Gets a u32 field value. Returns `Ok(None)` if the field does not exist or is null.
468    pub fn get_u32(&self, name: &str) -> Result<Option<u32>> {
469        self.get_basic_field(name, DataType::Uint32)
470    }
471
472    /// Gets a u64 field value. Returns `Ok(None)` if the field does not exist or is null.
473    pub fn get_u64(&self, name: &str) -> Result<Option<u64>> {
474        self.get_basic_field(name, DataType::Uint64)
475    }
476
477    /// Gets an f32 field value. Returns `Ok(None)` if the field does not exist or is null.
478    pub fn get_f32(&self, name: &str) -> Result<Option<f32>> {
479        self.get_basic_field(name, DataType::Float)
480    }
481
482    /// Gets an f64 field value. Returns `Ok(None)` if the field does not exist or is null.
483    pub fn get_f64(&self, name: &str) -> Result<Option<f64>> {
484        self.get_basic_field(name, DataType::Double)
485    }
486
487    /// Gets a dense FP32 vector field value. Returns `Ok(None)` if the field does not exist or is null.
488    pub fn get_vector_f32(&self, name: &str) -> Result<Option<Vec<f32>>> {
489        self.get_typed_vec(name, DataType::VectorFp32)
490    }
491
492    /// Gets a dense FP64 vector field value. Returns `Ok(None)` if the field does not exist or is null.
493    pub fn get_vector_f64(&self, name: &str) -> Result<Option<Vec<f64>>> {
494        self.get_typed_vec(name, DataType::VectorFp64)
495    }
496
497    /// Gets a binary (raw bytes) field value. Returns `Ok(None)` if the field does not exist or is null.
498    pub fn get_binary(&self, name: &str) -> Result<Option<Vec<u8>>> {
499        self.get_typed_vec(name, DataType::Binary)
500    }
501
502    /// Gets a dense INT8 vector field value. Returns `Ok(None)` if the field does not exist or is null.
503    pub fn get_vector_i8(&self, name: &str) -> Result<Option<Vec<i8>>> {
504        self.get_typed_vec(name, DataType::VectorInt8)
505    }
506
507    /// Gets a dense INT16 vector field value. Returns `Ok(None)` if the field does not exist or is null.
508    pub fn get_vector_i16(&self, name: &str) -> Result<Option<Vec<i16>>> {
509        self.get_typed_vec(name, DataType::VectorInt16)
510    }
511
512    // =========================================================================
513    // Array field getters
514    // =========================================================================
515
516    /// Gets an array of i32 values. Returns `Ok(None)` if the field does not exist or is null.
517    pub fn get_array_i32(&self, name: &str) -> Result<Option<Vec<i32>>> {
518        self.get_typed_vec(name, DataType::ArrayInt32)
519    }
520
521    /// Gets an array of i64 values. Returns `Ok(None)` if the field does not exist or is null.
522    pub fn get_array_i64(&self, name: &str) -> Result<Option<Vec<i64>>> {
523        self.get_typed_vec(name, DataType::ArrayInt64)
524    }
525
526    /// Gets an array of u32 values. Returns `Ok(None)` if the field does not exist or is null.
527    pub fn get_array_u32(&self, name: &str) -> Result<Option<Vec<u32>>> {
528        self.get_typed_vec(name, DataType::ArrayUint32)
529    }
530
531    /// Gets an array of u64 values. Returns `Ok(None)` if the field does not exist or is null.
532    pub fn get_array_u64(&self, name: &str) -> Result<Option<Vec<u64>>> {
533        self.get_typed_vec(name, DataType::ArrayUint64)
534    }
535
536    /// Gets an array of f32 values. Returns `Ok(None)` if the field does not exist or is null.
537    pub fn get_array_f32(&self, name: &str) -> Result<Option<Vec<f32>>> {
538        self.get_typed_vec(name, DataType::ArrayFloat)
539    }
540
541    /// Gets an array of f64 values. Returns `Ok(None)` if the field does not exist or is null.
542    pub fn get_array_f64(&self, name: &str) -> Result<Option<Vec<f64>>> {
543        self.get_typed_vec(name, DataType::ArrayDouble)
544    }
545
546    /// Gets an array of boolean values. Returns `Ok(None)` if the field does not exist or is null.
547    pub fn get_array_bool(&self, name: &str) -> Result<Option<Vec<bool>>> {
548        self.get_typed_vec(name, DataType::ArrayBool)
549    }
550
551    /// Clears all fields from the document.
552    pub fn clear(&mut self) {
553        unsafe { zvec_rust_sys::zvec_doc_clear(self.handle) };
554    }
555}
556
557impl Drop for Doc {
558    fn drop(&mut self) {
559        if self.owned && !self.handle.is_null() {
560            unsafe { zvec_rust_sys::zvec_doc_destroy(self.handle) };
561        }
562    }
563}
564
565/// Frees a vector of documents returned by query/fetch operations.
566pub fn free_docs(docs: Vec<Doc>) {
567    // Documents are freed individually via their Drop implementations
568    drop(docs);
569}
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574
575    #[test]
576    fn from_borrowed_does_not_own() {
577        // A borrowed doc should not destroy the handle on drop
578        let doc = Doc::from_borrowed(std::ptr::null_mut());
579        assert!(!doc.owned);
580        assert!(doc.handle.is_null());
581    }
582
583    #[test]
584    fn from_raw_takes_ownership() {
585        let doc = unsafe { Doc::from_raw(std::ptr::null_mut()) };
586        assert!(doc.owned);
587        // Drop with null handle is safe (no-op)
588    }
589}