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 close(self) -> Result<()> {
548 drop(self);
550 Ok(())
551 }
552}
553
554impl Drop for Collection {
555 fn drop(&mut self) {
556 if !self.handle.is_null() {
557 let rc = unsafe { zvec_rust_sys::zvec_collection_close(self.handle) };
559 if rc != zvec_rust_sys::ZVEC_OK {
560 eprintln!(
561 "zvec warning: failed to close collection (error code {})",
562 rc
563 );
564 }
565 }
566 }
567}
568
569unsafe impl Send for Collection {}
573unsafe impl Sync for Collection {}
574
575pub struct DocIterator<'a> {
586 handle: *mut zvec_rust_sys::zvec_doc_iterator_t,
587 _collection: PhantomData<&'a Collection>,
588}
589
590impl<'a> Iterator for DocIterator<'a> {
591 type Item = Result<Doc>;
592
593 fn next(&mut self) -> Option<Self::Item> {
594 let mut doc: *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
595 let rc = unsafe { zvec_rust_sys::zvec_doc_iterator_next(self.handle, &mut doc) };
596 if rc != zvec_rust_sys::ZVEC_OK {
597 return Some(Err(Error {
598 code: ErrorCode::from(rc),
599 message: format!("document iterator failed with error code {}", rc),
600 }));
601 }
602 if doc.is_null() {
603 return None; }
605 Some(Ok(unsafe { Doc::from_raw(doc) }))
607 }
608}
609
610impl<'a> Drop for DocIterator<'a> {
611 fn drop(&mut self) {
612 if !self.handle.is_null() {
613 unsafe { zvec_rust_sys::zvec_doc_iterator_close(self.handle) };
614 }
615 }
616}
617
618fn collect_write_results(
620 results: *mut zvec_rust_sys::zvec_write_result_t,
621 count: usize,
622) -> WriteResult {
623 let mut doc_results = Vec::with_capacity(count);
624 let mut success_count: u64 = 0;
625 let mut error_count: u64 = 0;
626
627 if !results.is_null() && count > 0 {
628 for i in 0..count {
629 let wr = unsafe { &*results.add(i) };
630 let is_ok = wr.code == zvec_rust_sys::ZVEC_OK;
631 let message = if wr.message.is_null() {
632 String::new()
633 } else {
634 unsafe { CStr::from_ptr(wr.message).to_string_lossy().into_owned() }
635 };
636 if is_ok {
637 success_count += 1;
638 } else {
639 error_count += 1;
640 }
641 doc_results.push(DocWriteResult {
642 success: is_ok,
643 code: ErrorCode::from(wr.code),
644 message,
645 });
646 }
647 unsafe { zvec_rust_sys::zvec_write_results_free(results, count) };
648 }
649
650 WriteResult {
651 success_count,
652 error_count,
653 results: doc_results,
654 }
655}
656
657unsafe fn collect_docs(results: *mut *mut zvec_rust_sys::zvec_doc_t, count: usize) -> Vec<Doc> {
663 if results.is_null() || count == 0 {
664 return Vec::new();
665 }
666
667 let mut docs = Vec::with_capacity(count);
668 for i in 0..count {
669 let doc_ptr = *results.add(i);
670 if !doc_ptr.is_null() {
671 docs.push(Doc::from_raw(doc_ptr));
673 }
674 }
675
676 zvec_rust_sys::zvec_free(results as *mut std::os::raw::c_void);
679
680 docs
681}
682
683#[cfg(test)]
684mod tests {
685 use super::*;
686
687 #[test]
688 fn collect_docs_handles_null_pointer() {
689 let docs = unsafe { collect_docs(ptr::null_mut(), 0) };
690 assert!(docs.is_empty());
691 }
692
693 #[test]
694 fn collect_docs_handles_zero_count() {
695 let mut fake: *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
697 let docs = unsafe { collect_docs(&mut fake as *mut _, 0) };
698 assert!(docs.is_empty());
699 }
700}