1use std::ffi::CStr;
2use std::marker::PhantomData;
3use std::ptr;
4
5use crate::doc::Doc;
6use crate::error::{check_error, to_cstring, Error, ErrorCode, Result};
7use crate::multi_query::MultiQuery;
8use crate::query::SearchQuery;
9use crate::schema::{CollectionSchema, FieldSchema, IndexParams};
10
11pub struct CollectionOptions {
13 pub(crate) handle: *mut zvec_rust_sys::zvec_collection_options_t,
14}
15
16impl CollectionOptions {
17 pub fn new() -> Result<Self> {
19 let handle = unsafe { zvec_rust_sys::zvec_collection_options_create() };
20 if handle.is_null() {
21 return Err(Error {
22 code: ErrorCode::InternalError,
23 message: "failed to create collection options".into(),
24 });
25 }
26 Ok(CollectionOptions { handle })
27 }
28
29 pub fn set_enable_mmap(&mut self, enable: bool) -> Result<()> {
31 check_error(unsafe {
32 zvec_rust_sys::zvec_collection_options_set_enable_mmap(self.handle, enable)
33 })
34 }
35
36 pub fn enable_mmap(&self) -> bool {
38 unsafe { zvec_rust_sys::zvec_collection_options_get_enable_mmap(self.handle) }
39 }
40
41 pub fn set_max_buffer_size(&mut self, size: u64) -> Result<()> {
43 check_error(unsafe {
44 zvec_rust_sys::zvec_collection_options_set_max_buffer_size(self.handle, size as usize)
45 })
46 }
47
48 pub fn max_buffer_size(&self) -> u64 {
50 unsafe { zvec_rust_sys::zvec_collection_options_get_max_buffer_size(self.handle) as u64 }
51 }
52
53 pub fn set_read_only(&mut self, read_only: bool) -> Result<()> {
55 check_error(unsafe {
56 zvec_rust_sys::zvec_collection_options_set_read_only(self.handle, read_only)
57 })
58 }
59
60 pub fn read_only(&self) -> bool {
62 unsafe { zvec_rust_sys::zvec_collection_options_get_read_only(self.handle) }
63 }
64}
65
66impl Drop for CollectionOptions {
67 fn drop(&mut self) {
68 if !self.handle.is_null() {
69 unsafe { zvec_rust_sys::zvec_collection_options_destroy(self.handle) };
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct IndexStat {
77 pub name: String,
78 pub completeness: f32,
79}
80
81#[derive(Debug, Clone)]
83pub struct CollectionStats {
84 pub doc_count: u64,
85 pub indexes: Vec<IndexStat>,
86}
87
88#[derive(Debug, Clone)]
90pub struct DocWriteResult {
91 pub success: bool,
92 pub code: ErrorCode,
93 pub message: String,
94}
95
96impl DocWriteResult {
97 pub fn is_success(&self) -> bool {
99 self.success
100 }
101}
102
103#[derive(Debug, Clone)]
105pub struct WriteResult {
106 pub success_count: u64,
107 pub error_count: u64,
108 pub results: Vec<DocWriteResult>,
109}
110
111pub struct Collection {
118 handle: *mut zvec_rust_sys::zvec_collection_t,
119}
120
121impl Collection {
122 pub unsafe fn as_raw(&self) -> *mut zvec_rust_sys::zvec_collection_t {
127 self.handle
128 }
129
130 pub unsafe fn from_raw(handle: *mut zvec_rust_sys::zvec_collection_t) -> Self {
136 Collection { handle }
137 }
138
139 pub fn create_and_open(
141 path: &str,
142 schema: &CollectionSchema,
143 options: Option<&CollectionOptions>,
144 ) -> Result<Self> {
145 let c_path = to_cstring(path)?;
146 let c_options = options.map(|o| o.handle as *const _).unwrap_or(ptr::null());
147
148 let mut handle: *mut zvec_rust_sys::zvec_collection_t = ptr::null_mut();
149 check_error(unsafe {
150 zvec_rust_sys::zvec_collection_create_and_open(
151 c_path.as_ptr(),
152 schema.handle,
153 c_options,
154 &mut handle,
155 )
156 })?;
157
158 Ok(Collection { handle })
159 }
160
161 pub fn open(path: &str, options: Option<&CollectionOptions>) -> Result<Self> {
163 let c_path = to_cstring(path)?;
164 let c_options = options.map(|o| o.handle as *const _).unwrap_or(ptr::null());
165
166 let mut handle: *mut zvec_rust_sys::zvec_collection_t = ptr::null_mut();
167 check_error(unsafe {
168 zvec_rust_sys::zvec_collection_open(c_path.as_ptr(), c_options, &mut handle)
169 })?;
170
171 Ok(Collection { handle })
172 }
173
174 pub fn flush(&self) -> Result<()> {
176 check_error(unsafe { zvec_rust_sys::zvec_collection_flush(self.handle) })
177 }
178
179 pub fn schema(&self) -> Result<CollectionSchema> {
181 let mut schema_handle: *mut zvec_rust_sys::zvec_collection_schema_t = ptr::null_mut();
182 check_error(unsafe {
183 zvec_rust_sys::zvec_collection_get_schema(self.handle, &mut schema_handle)
184 })?;
185 Ok(CollectionSchema::from_owned(schema_handle))
186 }
187
188 pub fn stats(&self) -> Result<CollectionStats> {
190 let mut stats_handle: *mut zvec_rust_sys::zvec_collection_stats_t = ptr::null_mut();
191 check_error(unsafe {
192 zvec_rust_sys::zvec_collection_get_stats(self.handle, &mut stats_handle)
193 })?;
194
195 let doc_count = unsafe { zvec_rust_sys::zvec_collection_stats_get_doc_count(stats_handle) };
196 let index_count =
197 unsafe { zvec_rust_sys::zvec_collection_stats_get_index_count(stats_handle) };
198
199 let mut indexes = Vec::with_capacity(index_count);
200
201 for i in 0..index_count {
202 let name_ptr =
203 unsafe { zvec_rust_sys::zvec_collection_stats_get_index_name(stats_handle, i) };
204 let name = if name_ptr.is_null() {
205 String::new()
206 } else {
207 unsafe { CStr::from_ptr(name_ptr).to_string_lossy().into_owned() }
208 };
209
210 let completeness = unsafe {
211 zvec_rust_sys::zvec_collection_stats_get_index_completeness(stats_handle, i)
212 };
213 indexes.push(IndexStat { name, completeness });
214 }
215
216 unsafe { zvec_rust_sys::zvec_collection_stats_destroy(stats_handle) };
217
218 Ok(CollectionStats { doc_count, indexes })
219 }
220
221 pub fn insert(&self, docs: &[&Doc]) -> Result<WriteResult> {
227 let ptrs: Vec<*const zvec_rust_sys::zvec_doc_t> =
228 docs.iter().map(|d| d.handle as *const _).collect();
229 let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
230 let mut result_count: usize = 0;
231
232 check_error(unsafe {
233 zvec_rust_sys::zvec_collection_insert_with_results(
234 self.handle,
235 ptrs.as_ptr(),
236 ptrs.len(),
237 &mut results,
238 &mut result_count,
239 )
240 })?;
241
242 Ok(collect_write_results(results, result_count))
243 }
244
245 pub fn update(&self, docs: &[&Doc]) -> Result<WriteResult> {
247 let ptrs: Vec<*const zvec_rust_sys::zvec_doc_t> =
248 docs.iter().map(|d| d.handle as *const _).collect();
249 let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
250 let mut result_count: usize = 0;
251
252 check_error(unsafe {
253 zvec_rust_sys::zvec_collection_update_with_results(
254 self.handle,
255 ptrs.as_ptr(),
256 ptrs.len(),
257 &mut results,
258 &mut result_count,
259 )
260 })?;
261
262 Ok(collect_write_results(results, result_count))
263 }
264
265 pub fn upsert(&self, docs: &[&Doc]) -> Result<WriteResult> {
267 let ptrs: Vec<*const zvec_rust_sys::zvec_doc_t> =
268 docs.iter().map(|d| d.handle as *const _).collect();
269 let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
270 let mut result_count: usize = 0;
271
272 check_error(unsafe {
273 zvec_rust_sys::zvec_collection_upsert_with_results(
274 self.handle,
275 ptrs.as_ptr(),
276 ptrs.len(),
277 &mut results,
278 &mut result_count,
279 )
280 })?;
281
282 Ok(collect_write_results(results, result_count))
283 }
284
285 pub fn delete(&self, pks: &[&str]) -> Result<WriteResult> {
287 let c_pks: Vec<_> = pks
288 .iter()
289 .map(|pk| to_cstring(pk))
290 .collect::<Result<Vec<_>>>()?;
291 let c_ptrs: Vec<_> = c_pks.iter().map(|pk| pk.as_ptr()).collect();
292 let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
293 let mut result_count: usize = 0;
294
295 check_error(unsafe {
296 zvec_rust_sys::zvec_collection_delete_with_results(
297 self.handle,
298 c_ptrs.as_ptr(),
299 c_ptrs.len(),
300 &mut results,
301 &mut result_count,
302 )
303 })?;
304
305 Ok(collect_write_results(results, result_count))
306 }
307
308 pub fn delete_by_filter(&self, filter: &str) -> Result<()> {
310 let c_filter = to_cstring(filter)?;
311 check_error(unsafe {
312 zvec_rust_sys::zvec_collection_delete_by_filter(self.handle, c_filter.as_ptr())
313 })
314 }
315
316 pub fn query(&self, query: &SearchQuery) -> Result<Vec<Doc>> {
322 let mut results: *mut *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
323 let mut result_count: usize = 0;
324
325 check_error(unsafe {
326 zvec_rust_sys::zvec_collection_query(
327 self.handle,
328 query.handle,
329 &mut results,
330 &mut result_count,
331 )
332 })?;
333
334 let docs = unsafe { collect_docs(results, result_count) };
335 Ok(docs)
336 }
337
338 pub fn multi_query(&self, query: &MultiQuery) -> Result<Vec<Doc>> {
341 let mut results: *mut *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
342 let mut result_count: usize = 0;
343
344 check_error(unsafe {
345 zvec_rust_sys::zvec_collection_multi_query(
346 self.handle,
347 query.handle,
348 &mut results,
349 &mut result_count,
350 )
351 })?;
352
353 let docs = unsafe { collect_docs(results, result_count) };
354 Ok(docs)
355 }
356
357 pub fn fetch(&self, pks: &[&str]) -> Result<Vec<Doc>> {
359 self.fetch_with_options(pks, None, true)
360 }
361
362 pub fn fetch_with_options(
397 &self,
398 pks: &[&str],
399 output_fields: Option<&[&str]>,
400 include_vector: bool,
401 ) -> Result<Vec<Doc>> {
402 let c_pks: Vec<_> = pks
403 .iter()
404 .map(|pk| to_cstring(pk))
405 .collect::<Result<Vec<_>>>()?;
406 let c_pk_ptrs: Vec<_> = c_pks.iter().map(|pk| pk.as_ptr()).collect();
407
408 let c_fields = output_fields
409 .map(|fields| {
410 fields
411 .iter()
412 .map(|f| to_cstring(f))
413 .collect::<Result<Vec<_>>>()
414 })
415 .transpose()?;
416 let c_field_ptrs: Option<Vec<_>> = c_fields
417 .as_ref()
418 .map(|f| f.iter().map(|s| s.as_ptr()).collect());
419 let (fields_ptr, fields_count) = match &c_field_ptrs {
420 Some(ptrs) => (ptrs.as_ptr(), ptrs.len()),
421 None => (ptr::null(), 0),
422 };
423
424 let mut documents: *mut *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
425 let mut found_count: usize = 0;
426
427 check_error(unsafe {
428 zvec_rust_sys::zvec_collection_fetch(
429 self.handle,
430 c_pk_ptrs.as_ptr(),
431 c_pk_ptrs.len(),
432 fields_ptr,
433 fields_count,
434 include_vector,
435 &mut documents,
436 &mut found_count,
437 )
438 })?;
439
440 let docs = unsafe { collect_docs(documents, found_count) };
441 Ok(docs)
442 }
443
444 pub fn iter(&self) -> Result<DocIterator<'_>> {
454 self.iter_with_options(None, true)
455 }
456
457 pub fn iter_with_options(
469 &self,
470 output_fields: Option<&[&str]>,
471 include_vector: bool,
472 ) -> Result<DocIterator<'_>> {
473 let options = unsafe { zvec_rust_sys::zvec_iterator_options_create() };
474 if options.is_null() {
475 return Err(Error {
476 code: ErrorCode::InternalError,
477 message: "failed to create iterator options".into(),
478 });
479 }
480
481 let result = (|| -> Result<()> {
484 let c_fields = output_fields
485 .map(|fields| {
486 fields
487 .iter()
488 .map(|f| to_cstring(f))
489 .collect::<Result<Vec<_>>>()
490 })
491 .transpose()?;
492 let c_field_ptrs: Option<Vec<_>> = c_fields
493 .as_ref()
494 .map(|f| f.iter().map(|s| s.as_ptr()).collect());
495 let (fields_ptr, fields_count) = match &c_field_ptrs {
496 Some(ptrs) => (ptrs.as_ptr(), ptrs.len()),
497 None => (ptr::null(), 0),
498 };
499
500 check_error(unsafe {
501 zvec_rust_sys::zvec_iterator_options_set_output_fields(
502 options,
503 fields_ptr,
504 fields_count,
505 )
506 })?;
507 check_error(unsafe {
508 zvec_rust_sys::zvec_iterator_options_set_include_vector(options, include_vector)
509 })?;
510 Ok(())
511 })();
512 if let Err(e) = result {
513 unsafe { zvec_rust_sys::zvec_iterator_options_destroy(options) };
514 return Err(e);
515 }
516
517 let mut iter: *mut zvec_rust_sys::zvec_doc_iterator_t = ptr::null_mut();
518 let rc = check_error(unsafe {
519 zvec_rust_sys::zvec_collection_create_iterator(self.handle, options, &mut iter)
520 });
521 unsafe { zvec_rust_sys::zvec_iterator_options_destroy(options) };
522 rc?;
523
524 Ok(DocIterator {
525 handle: iter,
526 _collection: PhantomData,
527 })
528 }
529
530 pub fn create_index(&self, field_name: &str, params: &IndexParams) -> Result<()> {
536 let c_name = to_cstring(field_name)?;
537 check_error(unsafe {
538 zvec_rust_sys::zvec_collection_create_index(self.handle, c_name.as_ptr(), params.handle)
539 })
540 }
541
542 pub fn drop_index(&self, field_name: &str) -> Result<()> {
544 let c_name = to_cstring(field_name)?;
545 check_error(unsafe {
546 zvec_rust_sys::zvec_collection_drop_index(self.handle, c_name.as_ptr())
547 })
548 }
549
550 pub fn optimize(&self) -> Result<()> {
552 check_error(unsafe { zvec_rust_sys::zvec_collection_optimize(self.handle) })
553 }
554
555 pub fn add_column(&self, field_schema: &FieldSchema, default_expr: Option<&str>) -> Result<()> {
561 let c_expr_owned = default_expr.map(to_cstring).transpose()?;
562 let c_expr_ptr = c_expr_owned
563 .as_ref()
564 .map(|s| s.as_ptr())
565 .unwrap_or(ptr::null());
566 check_error(unsafe {
567 zvec_rust_sys::zvec_collection_add_column(self.handle, field_schema.handle, c_expr_ptr)
568 })
569 }
570
571 pub fn drop_column(&self, name: &str) -> Result<()> {
573 let c_name = to_cstring(name)?;
574 check_error(unsafe {
575 zvec_rust_sys::zvec_collection_drop_column(self.handle, c_name.as_ptr())
576 })
577 }
578
579 pub fn alter_column(
591 &self,
592 name: &str,
593 new_name: Option<&str>,
594 new_schema: Option<&FieldSchema>,
595 ) -> Result<()> {
596 let c_name = to_cstring(name)?;
597 let c_new_name = new_name.map(to_cstring).transpose()?;
598 let c_new_name_ptr = c_new_name
599 .as_ref()
600 .map(|s| s.as_ptr())
601 .unwrap_or(ptr::null());
602 let new_schema_ptr = new_schema
603 .map(|schema| schema.handle as *const zvec_rust_sys::zvec_field_schema_t)
604 .unwrap_or(ptr::null());
605 check_error(unsafe {
606 zvec_rust_sys::zvec_collection_alter_column(
607 self.handle,
608 c_name.as_ptr(),
609 c_new_name_ptr,
610 new_schema_ptr,
611 )
612 })
613 }
614
615 pub fn close(self) -> Result<()> {
622 drop(self);
624 Ok(())
625 }
626
627 pub fn try_close(mut self) -> Result<()> {
673 let handle = std::mem::replace(&mut self.handle, ptr::null_mut());
676 check_error(unsafe { zvec_rust_sys::zvec_collection_close(handle) })
677 }
678}
679
680impl Drop for Collection {
681 fn drop(&mut self) {
682 if !self.handle.is_null() {
683 let rc = unsafe { zvec_rust_sys::zvec_collection_close(self.handle) };
685 if rc != zvec_rust_sys::ZVEC_OK {
686 eprintln!(
687 "zvec warning: failed to close collection (error code {})",
688 rc
689 );
690 }
691 }
692 }
693}
694
695unsafe impl Send for Collection {}
699unsafe impl Sync for Collection {}
700
701pub struct DocIterator<'a> {
712 handle: *mut zvec_rust_sys::zvec_doc_iterator_t,
713 _collection: PhantomData<&'a Collection>,
714}
715
716impl<'a> Iterator for DocIterator<'a> {
717 type Item = Result<Doc>;
718
719 fn next(&mut self) -> Option<Self::Item> {
720 let mut doc: *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
721 let rc = unsafe { zvec_rust_sys::zvec_doc_iterator_next(self.handle, &mut doc) };
722 if rc != zvec_rust_sys::ZVEC_OK {
723 return Some(Err(Error {
724 code: ErrorCode::from(rc),
725 message: format!("document iterator failed with error code {}", rc),
726 }));
727 }
728 if doc.is_null() {
729 return None; }
731 Some(Ok(unsafe { Doc::from_raw(doc) }))
733 }
734}
735
736impl<'a> Drop for DocIterator<'a> {
737 fn drop(&mut self) {
738 if !self.handle.is_null() {
739 unsafe { zvec_rust_sys::zvec_doc_iterator_close(self.handle) };
740 }
741 }
742}
743
744fn collect_write_results(
746 results: *mut zvec_rust_sys::zvec_write_result_t,
747 count: usize,
748) -> WriteResult {
749 let mut doc_results = Vec::with_capacity(count);
750 let mut success_count: u64 = 0;
751 let mut error_count: u64 = 0;
752
753 if !results.is_null() && count > 0 {
754 for i in 0..count {
755 let wr = unsafe { &*results.add(i) };
756 let is_ok = wr.code == zvec_rust_sys::ZVEC_OK;
757 let message = if wr.message.is_null() {
758 String::new()
759 } else {
760 unsafe { CStr::from_ptr(wr.message).to_string_lossy().into_owned() }
761 };
762 if is_ok {
763 success_count += 1;
764 } else {
765 error_count += 1;
766 }
767 doc_results.push(DocWriteResult {
768 success: is_ok,
769 code: ErrorCode::from(wr.code),
770 message,
771 });
772 }
773 unsafe { zvec_rust_sys::zvec_write_results_free(results, count) };
774 }
775
776 WriteResult {
777 success_count,
778 error_count,
779 results: doc_results,
780 }
781}
782
783unsafe fn collect_docs(results: *mut *mut zvec_rust_sys::zvec_doc_t, count: usize) -> Vec<Doc> {
789 if results.is_null() || count == 0 {
790 return Vec::new();
791 }
792
793 let mut docs = Vec::with_capacity(count);
794 for i in 0..count {
795 let doc_ptr = *results.add(i);
796 if !doc_ptr.is_null() {
797 docs.push(Doc::from_raw(doc_ptr));
799 }
800 }
801
802 zvec_rust_sys::zvec_free(results as *mut std::os::raw::c_void);
805
806 docs
807}
808
809#[cfg(test)]
810mod tests {
811 use super::*;
812
813 #[test]
814 fn collect_docs_handles_null_pointer() {
815 let docs = unsafe { collect_docs(ptr::null_mut(), 0) };
816 assert!(docs.is_empty());
817 }
818
819 #[test]
820 fn collect_docs_handles_zero_count() {
821 let mut fake: *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
823 let docs = unsafe { collect_docs(&mut fake as *mut _, 0) };
824 assert!(docs.is_empty());
825 }
826}