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    ///
63    /// # Panics
64    ///
65    /// Panics if `pk` contains an interior null byte. Use [`Doc::try_set_pk`] to
66    /// get an error instead.
67    pub fn set_pk(&mut self, pk: &str) {
68        self.try_set_pk(pk).expect("pk must not contain null bytes");
69    }
70
71    /// Sets the primary key.
72    ///
73    /// Returns [`ErrorCode::InvalidArgument`] instead of panicking when `pk`
74    /// contains an interior null byte.
75    pub fn try_set_pk(&mut self, pk: &str) -> Result<()> {
76        let c_pk = to_cstring(pk)?;
77        unsafe { zvec_rust_sys::zvec_doc_set_pk(self.handle, c_pk.as_ptr()) };
78        Ok(())
79    }
80
81    /// Returns the primary key, or `None` if not set.
82    pub fn get_pk(&self) -> Option<&str> {
83        unsafe {
84            let ptr = zvec_rust_sys::zvec_doc_get_pk_pointer(self.handle);
85            if ptr.is_null() {
86                None
87            } else {
88                CStr::from_ptr(ptr).to_str().ok()
89            }
90        }
91    }
92
93    /// Returns the document score (set by query results).
94    pub fn get_score(&self) -> f32 {
95        unsafe { zvec_rust_sys::zvec_doc_get_score(self.handle) }
96    }
97
98    #[allow(dead_code)]
99    pub(crate) fn get_doc_id(&self) -> u64 {
100        unsafe { zvec_rust_sys::zvec_doc_get_doc_id(self.handle) }
101    }
102
103    /// Returns the number of fields in the document.
104    pub fn field_count(&self) -> usize {
105        unsafe { zvec_rust_sys::zvec_doc_get_field_count(self.handle) }
106    }
107
108    /// Returns whether the document is empty.
109    pub fn is_empty(&self) -> bool {
110        unsafe { zvec_rust_sys::zvec_doc_is_empty(self.handle) }
111    }
112
113    /// Returns whether the document contains the specified field.
114    pub fn has_field(&self, name: &str) -> bool {
115        let c_name = match to_cstring(name) {
116            Ok(s) => s,
117            Err(_) => return false,
118        };
119        unsafe { zvec_rust_sys::zvec_doc_has_field(self.handle, c_name.as_ptr()) }
120    }
121
122    /// Returns whether the specified field is null.
123    pub fn is_field_null(&self, name: &str) -> bool {
124        let c_name = match to_cstring(name) {
125            Ok(s) => s,
126            Err(_) => return false,
127        };
128        unsafe { zvec_rust_sys::zvec_doc_is_field_null(self.handle, c_name.as_ptr()) }
129    }
130
131    // =========================================================================
132    // Field setters
133    // =========================================================================
134
135    /// Adds a string field.
136    pub fn add_string(&mut self, name: &str, value: &str) -> Result<()> {
137        let c_name = to_cstring(name)?;
138        let c_value = to_cstring(value)?;
139        // The C API `zvec_doc_add_field_by_value` is length-delimited: it stores
140        // exactly `size` bytes. Use `as_bytes()` (without the trailing NUL) so the
141        // stored value is the string itself; sending `as_bytes_with_nul()` would
142        // append a NUL to the stored data and break exact string filters.
143        let bytes = c_value.as_bytes();
144        check_error(unsafe {
145            zvec_rust_sys::zvec_doc_add_field_by_value(
146                self.handle,
147                c_name.as_ptr(),
148                DataType::String as u32,
149                bytes.as_ptr() as *const c_void,
150                bytes.len(),
151            )
152        })
153    }
154
155    /// Adds a boolean field.
156    pub fn add_bool(&mut self, name: &str, value: bool) -> 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::Bool as u32,
163                &value as *const bool as *const c_void,
164                std::mem::size_of::<bool>(),
165            )
166        })
167    }
168
169    /// Adds an i32 field.
170    pub fn add_i32(&mut self, name: &str, value: i32) -> 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::Int32 as u32,
177                &value as *const i32 as *const c_void,
178                std::mem::size_of::<i32>(),
179            )
180        })
181    }
182
183    /// Adds an i64 field.
184    pub fn add_i64(&mut self, name: &str, value: i64) -> 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::Int64 as u32,
191                &value as *const i64 as *const c_void,
192                std::mem::size_of::<i64>(),
193            )
194        })
195    }
196
197    /// Adds a u32 field.
198    pub fn add_u32(&mut self, name: &str, value: u32) -> 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::Uint32 as u32,
205                &value as *const u32 as *const c_void,
206                std::mem::size_of::<u32>(),
207            )
208        })
209    }
210
211    /// Adds a u64 field.
212    pub fn add_u64(&mut self, name: &str, value: u64) -> 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::Uint64 as u32,
219                &value as *const u64 as *const c_void,
220                std::mem::size_of::<u64>(),
221            )
222        })
223    }
224
225    /// Adds an f32 field.
226    pub fn add_f32(&mut self, name: &str, value: f32) -> 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::Float as u32,
233                &value as *const f32 as *const c_void,
234                std::mem::size_of::<f32>(),
235            )
236        })
237    }
238
239    /// Adds an f64 field.
240    pub fn add_f64(&mut self, name: &str, value: f64) -> 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::Double as u32,
247                &value as *const f64 as *const c_void,
248                std::mem::size_of::<f64>(),
249            )
250        })
251    }
252
253    /// Adds a dense FP32 vector field.
254    pub fn add_vector_f32(&mut self, name: &str, vector: &[f32]) -> 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::VectorFp32 as u32,
261                vector.as_ptr() as *const c_void,
262                std::mem::size_of_val(vector),
263            )
264        })
265    }
266
267    /// Adds a dense FP64 vector field.
268    pub fn add_vector_f64(&mut self, name: &str, vector: &[f64]) -> 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::VectorFp64 as u32,
275                vector.as_ptr() as *const c_void,
276                std::mem::size_of_val(vector),
277            )
278        })
279    }
280
281    /// Adds a binary (raw bytes) field.
282    pub fn add_binary(&mut self, name: &str, value: &[u8]) -> 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::Binary as u32,
289                value.as_ptr() as *const c_void,
290                value.len(),
291            )
292        })
293    }
294
295    /// Adds a dense INT8 vector field.
296    pub fn add_vector_i8(&mut self, name: &str, vector: &[i8]) -> 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::VectorInt8 as u32,
303                vector.as_ptr() as *const c_void,
304                std::mem::size_of_val(vector),
305            )
306        })
307    }
308
309    /// Adds a dense INT16 vector field.
310    pub fn add_vector_i16(&mut self, name: &str, vector: &[i16]) -> Result<()> {
311        let c_name = to_cstring(name)?;
312        check_error(unsafe {
313            zvec_rust_sys::zvec_doc_add_field_by_value(
314                self.handle,
315                c_name.as_ptr(),
316                DataType::VectorInt16 as u32,
317                vector.as_ptr() as *const c_void,
318                std::mem::size_of_val(vector),
319            )
320        })
321    }
322
323    // =========================================================================
324    // Array field setters
325    // =========================================================================
326
327    fn add_typed_array<T>(&mut self, name: &str, data_type: DataType, values: &[T]) -> Result<()> {
328        let c_name = to_cstring(name)?;
329        check_error(unsafe {
330            zvec_rust_sys::zvec_doc_add_field_by_value(
331                self.handle,
332                c_name.as_ptr(),
333                data_type as u32,
334                values.as_ptr() as *const c_void,
335                std::mem::size_of_val(values),
336            )
337        })
338    }
339
340    /// Adds an array of i32 values.
341    pub fn add_array_i32(&mut self, name: &str, values: &[i32]) -> Result<()> {
342        self.add_typed_array(name, DataType::ArrayInt32, values)
343    }
344
345    /// Adds an array of i64 values.
346    pub fn add_array_i64(&mut self, name: &str, values: &[i64]) -> Result<()> {
347        self.add_typed_array(name, DataType::ArrayInt64, values)
348    }
349
350    /// Adds an array of u32 values.
351    pub fn add_array_u32(&mut self, name: &str, values: &[u32]) -> Result<()> {
352        self.add_typed_array(name, DataType::ArrayUint32, values)
353    }
354
355    /// Adds an array of u64 values.
356    pub fn add_array_u64(&mut self, name: &str, values: &[u64]) -> Result<()> {
357        self.add_typed_array(name, DataType::ArrayUint64, values)
358    }
359
360    /// Adds an array of f32 values.
361    pub fn add_array_f32(&mut self, name: &str, values: &[f32]) -> Result<()> {
362        self.add_typed_array(name, DataType::ArrayFloat, values)
363    }
364
365    /// Adds an array of f64 values.
366    pub fn add_array_f64(&mut self, name: &str, values: &[f64]) -> Result<()> {
367        self.add_typed_array(name, DataType::ArrayDouble, values)
368    }
369
370    /// Adds an array of boolean values.
371    pub fn add_array_bool(&mut self, name: &str, values: &[bool]) -> Result<()> {
372        self.add_typed_array(name, DataType::ArrayBool, values)
373    }
374
375    /// Sets a field to null.
376    pub fn set_field_null(&mut self, name: &str) -> Result<()> {
377        let c_name = to_cstring(name)?;
378        check_error(unsafe { zvec_rust_sys::zvec_doc_set_field_null(self.handle, c_name.as_ptr()) })
379    }
380
381    /// Removes a field from the document.
382    pub fn remove_field(&mut self, name: &str) -> Result<()> {
383        let c_name = to_cstring(name)?;
384        check_error(unsafe { zvec_rust_sys::zvec_doc_remove_field(self.handle, c_name.as_ptr()) })
385    }
386
387    // =========================================================================
388    // Field getters
389    // =========================================================================
390
391    fn get_basic_field<T: Copy + Default>(
392        &self,
393        name: &str,
394        data_type: DataType,
395    ) -> Result<Option<T>> {
396        if !self.has_field(name) || self.is_field_null(name) {
397            return Ok(None);
398        }
399        let c_name = to_cstring(name)?;
400        let mut value: T = T::default();
401        check_error(unsafe {
402            zvec_rust_sys::zvec_doc_get_field_value_basic(
403                self.handle,
404                c_name.as_ptr(),
405                data_type as u32,
406                &mut value as *mut T as *mut c_void,
407                std::mem::size_of::<T>(),
408            )
409        })?;
410        Ok(Some(value))
411    }
412
413    fn get_pointer_field(
414        &self,
415        name: &str,
416        data_type: DataType,
417    ) -> Result<Option<(*const c_void, usize)>> {
418        let c_name = to_cstring(name)?;
419        let mut value_ptr: *const c_void = std::ptr::null();
420        let mut value_size: usize = 0;
421        check_error(unsafe {
422            zvec_rust_sys::zvec_doc_get_field_value_pointer(
423                self.handle,
424                c_name.as_ptr(),
425                data_type as u32,
426                &mut value_ptr,
427                &mut value_size,
428            )
429        })?;
430        if value_ptr.is_null() || value_size == 0 {
431            return Ok(None);
432        }
433        Ok(Some((value_ptr, value_size)))
434    }
435
436    fn get_typed_vec<T: Copy>(&self, name: &str, data_type: DataType) -> Result<Option<Vec<T>>> {
437        let Some((ptr, size)) = self.get_pointer_field(name, data_type)? else {
438            return Ok(None);
439        };
440        let elem_size = std::mem::size_of::<T>();
441        if elem_size > 1 && size % elem_size != 0 {
442            return Err(Error {
443                code: ErrorCode::InternalError,
444                message: format!(
445                    "data size {} is not aligned to element size {}",
446                    size, elem_size
447                ),
448            });
449        }
450        let count = size / elem_size;
451        let slice = unsafe { std::slice::from_raw_parts(ptr as *const T, count) };
452        Ok(Some(slice.to_vec()))
453    }
454
455    /// Gets a string field value. Returns `Ok(None)` if the field does not exist or is null.
456    pub fn get_string(&self, name: &str) -> Result<Option<String>> {
457        let Some((ptr, _size)) = self.get_pointer_field(name, DataType::String)? else {
458            return Ok(None);
459        };
460        unsafe {
461            let cstr = CStr::from_ptr(ptr as *const std::os::raw::c_char);
462            Ok(Some(cstr.to_string_lossy().into_owned()))
463        }
464    }
465
466    /// Gets a boolean field value. Returns `Ok(None)` if the field does not exist or is null.
467    pub fn get_bool(&self, name: &str) -> Result<Option<bool>> {
468        self.get_basic_field(name, DataType::Bool)
469    }
470
471    /// Gets an i32 field value. Returns `Ok(None)` if the field does not exist or is null.
472    pub fn get_i32(&self, name: &str) -> Result<Option<i32>> {
473        self.get_basic_field(name, DataType::Int32)
474    }
475
476    /// Gets an i64 field value. Returns `Ok(None)` if the field does not exist or is null.
477    pub fn get_i64(&self, name: &str) -> Result<Option<i64>> {
478        self.get_basic_field(name, DataType::Int64)
479    }
480
481    /// Gets a u32 field value. Returns `Ok(None)` if the field does not exist or is null.
482    pub fn get_u32(&self, name: &str) -> Result<Option<u32>> {
483        self.get_basic_field(name, DataType::Uint32)
484    }
485
486    /// Gets a u64 field value. Returns `Ok(None)` if the field does not exist or is null.
487    pub fn get_u64(&self, name: &str) -> Result<Option<u64>> {
488        self.get_basic_field(name, DataType::Uint64)
489    }
490
491    /// Gets an f32 field value. Returns `Ok(None)` if the field does not exist or is null.
492    pub fn get_f32(&self, name: &str) -> Result<Option<f32>> {
493        self.get_basic_field(name, DataType::Float)
494    }
495
496    /// Gets an f64 field value. Returns `Ok(None)` if the field does not exist or is null.
497    pub fn get_f64(&self, name: &str) -> Result<Option<f64>> {
498        self.get_basic_field(name, DataType::Double)
499    }
500
501    /// Gets a dense FP32 vector field value. Returns `Ok(None)` if the field does not exist or is null.
502    pub fn get_vector_f32(&self, name: &str) -> Result<Option<Vec<f32>>> {
503        self.get_typed_vec(name, DataType::VectorFp32)
504    }
505
506    /// Gets a dense FP64 vector field value. Returns `Ok(None)` if the field does not exist or is null.
507    pub fn get_vector_f64(&self, name: &str) -> Result<Option<Vec<f64>>> {
508        self.get_typed_vec(name, DataType::VectorFp64)
509    }
510
511    /// Gets a binary (raw bytes) field value. Returns `Ok(None)` if the field does not exist or is null.
512    pub fn get_binary(&self, name: &str) -> Result<Option<Vec<u8>>> {
513        self.get_typed_vec(name, DataType::Binary)
514    }
515
516    /// Gets a dense INT8 vector field value. Returns `Ok(None)` if the field does not exist or is null.
517    pub fn get_vector_i8(&self, name: &str) -> Result<Option<Vec<i8>>> {
518        self.get_typed_vec(name, DataType::VectorInt8)
519    }
520
521    /// Gets a dense INT16 vector field value. Returns `Ok(None)` if the field does not exist or is null.
522    pub fn get_vector_i16(&self, name: &str) -> Result<Option<Vec<i16>>> {
523        self.get_typed_vec(name, DataType::VectorInt16)
524    }
525
526    // =========================================================================
527    // Array field getters
528    // =========================================================================
529
530    /// Gets an array of i32 values. Returns `Ok(None)` if the field does not exist or is null.
531    pub fn get_array_i32(&self, name: &str) -> Result<Option<Vec<i32>>> {
532        self.get_typed_vec(name, DataType::ArrayInt32)
533    }
534
535    /// Gets an array of i64 values. Returns `Ok(None)` if the field does not exist or is null.
536    pub fn get_array_i64(&self, name: &str) -> Result<Option<Vec<i64>>> {
537        self.get_typed_vec(name, DataType::ArrayInt64)
538    }
539
540    /// Gets an array of u32 values. Returns `Ok(None)` if the field does not exist or is null.
541    pub fn get_array_u32(&self, name: &str) -> Result<Option<Vec<u32>>> {
542        self.get_typed_vec(name, DataType::ArrayUint32)
543    }
544
545    /// Gets an array of u64 values. Returns `Ok(None)` if the field does not exist or is null.
546    pub fn get_array_u64(&self, name: &str) -> Result<Option<Vec<u64>>> {
547        self.get_typed_vec(name, DataType::ArrayUint64)
548    }
549
550    /// Gets an array of f32 values. Returns `Ok(None)` if the field does not exist or is null.
551    pub fn get_array_f32(&self, name: &str) -> Result<Option<Vec<f32>>> {
552        self.get_typed_vec(name, DataType::ArrayFloat)
553    }
554
555    /// Gets an array of f64 values. Returns `Ok(None)` if the field does not exist or is null.
556    pub fn get_array_f64(&self, name: &str) -> Result<Option<Vec<f64>>> {
557        self.get_typed_vec(name, DataType::ArrayDouble)
558    }
559
560    /// Gets an array of boolean values. Returns `Ok(None)` if the field does not exist or is null.
561    pub fn get_array_bool(&self, name: &str) -> Result<Option<Vec<bool>>> {
562        self.get_typed_vec(name, DataType::ArrayBool)
563    }
564
565    /// Clears all fields from the document.
566    pub fn clear(&mut self) {
567        unsafe { zvec_rust_sys::zvec_doc_clear(self.handle) };
568    }
569}
570
571impl Drop for Doc {
572    fn drop(&mut self) {
573        if self.owned && !self.handle.is_null() {
574            unsafe { zvec_rust_sys::zvec_doc_destroy(self.handle) };
575        }
576    }
577}
578
579/// Frees a vector of documents returned by query/fetch operations.
580pub fn free_docs(docs: Vec<Doc>) {
581    // Documents are freed individually via their Drop implementations
582    drop(docs);
583}
584
585#[cfg(test)]
586mod tests {
587    use super::*;
588
589    #[test]
590    fn from_borrowed_does_not_own() {
591        // A borrowed doc should not destroy the handle on drop
592        let doc = Doc::from_borrowed(std::ptr::null_mut());
593        assert!(!doc.owned);
594        assert!(doc.handle.is_null());
595    }
596
597    #[test]
598    fn from_raw_takes_ownership() {
599        let doc = unsafe { Doc::from_raw(std::ptr::null_mut()) };
600        assert!(doc.owned);
601        // Drop with null handle is safe (no-op)
602    }
603
604    #[test]
605    fn try_set_pk_accepts_plain_key() {
606        let mut doc = Doc::new().expect("create doc");
607        doc.try_set_pk("pk-1").expect("plain pk is accepted");
608        assert_eq!(doc.get_pk(), Some("pk-1"));
609    }
610
611    #[test]
612    fn try_set_pk_rejects_interior_nul() {
613        let mut doc = Doc::new().expect("create doc");
614        let err = doc
615            .try_set_pk("pk\0-1")
616            .expect_err("an interior nul byte must be rejected");
617        assert_eq!(err.code, ErrorCode::InvalidArgument);
618    }
619
620    #[test]
621    #[should_panic(expected = "pk must not contain null bytes")]
622    fn set_pk_panics_on_interior_nul() {
623        let mut doc = Doc::new().expect("create doc");
624        doc.set_pk("pk\0-1");
625    }
626}