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(
364 &self,
365 pks: &[&str],
366 output_fields: Option<&[&str]>,
367 include_vector: bool,
368 ) -> Result<Vec<Doc>> {
369 let c_pks: Vec<_> = pks
370 .iter()
371 .map(|pk| to_cstring(pk))
372 .collect::<Result<Vec<_>>>()?;
373 let c_pk_ptrs: Vec<_> = c_pks.iter().map(|pk| pk.as_ptr()).collect();
374
375 let c_fields = output_fields
376 .map(|fields| {
377 fields
378 .iter()
379 .map(|f| to_cstring(f))
380 .collect::<Result<Vec<_>>>()
381 })
382 .transpose()?;
383 let c_field_ptrs: Option<Vec<_>> = c_fields
384 .as_ref()
385 .map(|f| f.iter().map(|s| s.as_ptr()).collect());
386 let (fields_ptr, fields_count) = match &c_field_ptrs {
387 Some(ptrs) => (ptrs.as_ptr(), ptrs.len()),
388 None => (ptr::null(), 0),
389 };
390
391 let mut documents: *mut *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
392 let mut found_count: usize = 0;
393
394 check_error(unsafe {
395 zvec_rust_sys::zvec_collection_fetch(
396 self.handle,
397 c_pk_ptrs.as_ptr(),
398 c_pk_ptrs.len(),
399 fields_ptr,
400 fields_count,
401 include_vector,
402 &mut documents,
403 &mut found_count,
404 )
405 })?;
406
407 let docs = unsafe { collect_docs(documents, found_count) };
408 Ok(docs)
409 }
410
411 pub fn iter(&self) -> Result<DocIterator<'_>> {
421 self.iter_with_options(None, true)
422 }
423
424 pub fn iter_with_options(
436 &self,
437 output_fields: Option<&[&str]>,
438 include_vector: bool,
439 ) -> Result<DocIterator<'_>> {
440 let options = unsafe { zvec_rust_sys::zvec_iterator_options_create() };
441 if options.is_null() {
442 return Err(Error {
443 code: ErrorCode::InternalError,
444 message: "failed to create iterator options".into(),
445 });
446 }
447
448 let result = (|| -> Result<()> {
451 let c_fields = output_fields
452 .map(|fields| {
453 fields
454 .iter()
455 .map(|f| to_cstring(f))
456 .collect::<Result<Vec<_>>>()
457 })
458 .transpose()?;
459 let c_field_ptrs: Option<Vec<_>> = c_fields
460 .as_ref()
461 .map(|f| f.iter().map(|s| s.as_ptr()).collect());
462 let (fields_ptr, fields_count) = match &c_field_ptrs {
463 Some(ptrs) => (ptrs.as_ptr(), ptrs.len()),
464 None => (ptr::null(), 0),
465 };
466
467 check_error(unsafe {
468 zvec_rust_sys::zvec_iterator_options_set_output_fields(
469 options,
470 fields_ptr,
471 fields_count,
472 )
473 })?;
474 check_error(unsafe {
475 zvec_rust_sys::zvec_iterator_options_set_include_vector(options, include_vector)
476 })?;
477 Ok(())
478 })();
479 if let Err(e) = result {
480 unsafe { zvec_rust_sys::zvec_iterator_options_destroy(options) };
481 return Err(e);
482 }
483
484 let mut iter: *mut zvec_rust_sys::zvec_doc_iterator_t = ptr::null_mut();
485 let rc = check_error(unsafe {
486 zvec_rust_sys::zvec_collection_create_iterator(self.handle, options, &mut iter)
487 });
488 unsafe { zvec_rust_sys::zvec_iterator_options_destroy(options) };
489 rc?;
490
491 Ok(DocIterator {
492 handle: iter,
493 _collection: PhantomData,
494 })
495 }
496
497 pub fn create_index(&self, field_name: &str, params: &IndexParams) -> Result<()> {
503 let c_name = to_cstring(field_name)?;
504 check_error(unsafe {
505 zvec_rust_sys::zvec_collection_create_index(self.handle, c_name.as_ptr(), params.handle)
506 })
507 }
508
509 pub fn drop_index(&self, field_name: &str) -> Result<()> {
511 let c_name = to_cstring(field_name)?;
512 check_error(unsafe {
513 zvec_rust_sys::zvec_collection_drop_index(self.handle, c_name.as_ptr())
514 })
515 }
516
517 pub fn optimize(&self) -> Result<()> {
519 check_error(unsafe { zvec_rust_sys::zvec_collection_optimize(self.handle) })
520 }
521
522 pub fn add_column(&self, field_schema: &FieldSchema, default_expr: Option<&str>) -> Result<()> {
528 let c_expr_owned = default_expr.map(to_cstring).transpose()?;
529 let c_expr_ptr = c_expr_owned
530 .as_ref()
531 .map(|s| s.as_ptr())
532 .unwrap_or(ptr::null());
533 check_error(unsafe {
534 zvec_rust_sys::zvec_collection_add_column(self.handle, field_schema.handle, c_expr_ptr)
535 })
536 }
537
538 pub fn drop_column(&self, name: &str) -> Result<()> {
540 let c_name = to_cstring(name)?;
541 check_error(unsafe {
542 zvec_rust_sys::zvec_collection_drop_column(self.handle, c_name.as_ptr())
543 })
544 }
545
546 pub fn alter_column(
558 &self,
559 name: &str,
560 new_name: Option<&str>,
561 new_schema: Option<&FieldSchema>,
562 ) -> Result<()> {
563 let c_name = to_cstring(name)?;
564 let c_new_name = new_name.map(to_cstring).transpose()?;
565 let c_new_name_ptr = c_new_name
566 .as_ref()
567 .map(|s| s.as_ptr())
568 .unwrap_or(ptr::null());
569 let new_schema_ptr = new_schema
570 .map(|schema| schema.handle as *const zvec_rust_sys::zvec_field_schema_t)
571 .unwrap_or(ptr::null());
572 check_error(unsafe {
573 zvec_rust_sys::zvec_collection_alter_column(
574 self.handle,
575 c_name.as_ptr(),
576 c_new_name_ptr,
577 new_schema_ptr,
578 )
579 })
580 }
581
582 pub fn close(self) -> Result<()> {
584 drop(self);
586 Ok(())
587 }
588}
589
590impl Drop for Collection {
591 fn drop(&mut self) {
592 if !self.handle.is_null() {
593 let rc = unsafe { zvec_rust_sys::zvec_collection_close(self.handle) };
595 if rc != zvec_rust_sys::ZVEC_OK {
596 eprintln!(
597 "zvec warning: failed to close collection (error code {})",
598 rc
599 );
600 }
601 }
602 }
603}
604
605unsafe impl Send for Collection {}
609unsafe impl Sync for Collection {}
610
611pub struct DocIterator<'a> {
622 handle: *mut zvec_rust_sys::zvec_doc_iterator_t,
623 _collection: PhantomData<&'a Collection>,
624}
625
626impl<'a> Iterator for DocIterator<'a> {
627 type Item = Result<Doc>;
628
629 fn next(&mut self) -> Option<Self::Item> {
630 let mut doc: *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
631 let rc = unsafe { zvec_rust_sys::zvec_doc_iterator_next(self.handle, &mut doc) };
632 if rc != zvec_rust_sys::ZVEC_OK {
633 return Some(Err(Error {
634 code: ErrorCode::from(rc),
635 message: format!("document iterator failed with error code {}", rc),
636 }));
637 }
638 if doc.is_null() {
639 return None; }
641 Some(Ok(unsafe { Doc::from_raw(doc) }))
643 }
644}
645
646impl<'a> Drop for DocIterator<'a> {
647 fn drop(&mut self) {
648 if !self.handle.is_null() {
649 unsafe { zvec_rust_sys::zvec_doc_iterator_close(self.handle) };
650 }
651 }
652}
653
654fn collect_write_results(
656 results: *mut zvec_rust_sys::zvec_write_result_t,
657 count: usize,
658) -> WriteResult {
659 let mut doc_results = Vec::with_capacity(count);
660 let mut success_count: u64 = 0;
661 let mut error_count: u64 = 0;
662
663 if !results.is_null() && count > 0 {
664 for i in 0..count {
665 let wr = unsafe { &*results.add(i) };
666 let is_ok = wr.code == zvec_rust_sys::ZVEC_OK;
667 let message = if wr.message.is_null() {
668 String::new()
669 } else {
670 unsafe { CStr::from_ptr(wr.message).to_string_lossy().into_owned() }
671 };
672 if is_ok {
673 success_count += 1;
674 } else {
675 error_count += 1;
676 }
677 doc_results.push(DocWriteResult {
678 success: is_ok,
679 code: ErrorCode::from(wr.code),
680 message,
681 });
682 }
683 unsafe { zvec_rust_sys::zvec_write_results_free(results, count) };
684 }
685
686 WriteResult {
687 success_count,
688 error_count,
689 results: doc_results,
690 }
691}
692
693unsafe fn collect_docs(results: *mut *mut zvec_rust_sys::zvec_doc_t, count: usize) -> Vec<Doc> {
699 if results.is_null() || count == 0 {
700 return Vec::new();
701 }
702
703 let mut docs = Vec::with_capacity(count);
704 for i in 0..count {
705 let doc_ptr = *results.add(i);
706 if !doc_ptr.is_null() {
707 docs.push(Doc::from_raw(doc_ptr));
709 }
710 }
711
712 zvec_rust_sys::zvec_free(results as *mut std::os::raw::c_void);
715
716 docs
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722
723 #[test]
724 fn collect_docs_handles_null_pointer() {
725 let docs = unsafe { collect_docs(ptr::null_mut(), 0) };
726 assert!(docs.is_empty());
727 }
728
729 #[test]
730 fn collect_docs_handles_zero_count() {
731 let mut fake: *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
733 let docs = unsafe { collect_docs(&mut fake as *mut _, 0) };
734 assert!(docs.is_empty());
735 }
736}