1use std::ffi::CStr;
2use std::ptr;
3
4use crate::doc::Doc;
5use crate::error::{check_error, to_cstring, Error, ErrorCode, Result};
6use crate::multi_query::MultiQuery;
7use crate::query::SearchQuery;
8use crate::schema::{CollectionSchema, FieldSchema, IndexParams};
9
10pub struct CollectionOptions {
12 pub(crate) handle: *mut zvec_rust_sys::zvec_collection_options_t,
13}
14
15impl CollectionOptions {
16 pub fn new() -> Result<Self> {
18 let handle = unsafe { zvec_rust_sys::zvec_collection_options_create() };
19 if handle.is_null() {
20 return Err(Error {
21 code: ErrorCode::InternalError,
22 message: "failed to create collection options".into(),
23 });
24 }
25 Ok(CollectionOptions { handle })
26 }
27
28 pub fn set_enable_mmap(&mut self, enable: bool) -> Result<()> {
30 check_error(unsafe {
31 zvec_rust_sys::zvec_collection_options_set_enable_mmap(self.handle, enable)
32 })
33 }
34
35 pub fn enable_mmap(&self) -> bool {
37 unsafe { zvec_rust_sys::zvec_collection_options_get_enable_mmap(self.handle) }
38 }
39
40 pub fn set_max_buffer_size(&mut self, size: u64) -> Result<()> {
42 check_error(unsafe {
43 zvec_rust_sys::zvec_collection_options_set_max_buffer_size(self.handle, size as usize)
44 })
45 }
46
47 pub fn max_buffer_size(&self) -> u64 {
49 unsafe { zvec_rust_sys::zvec_collection_options_get_max_buffer_size(self.handle) as u64 }
50 }
51
52 pub fn set_read_only(&mut self, read_only: bool) -> Result<()> {
54 check_error(unsafe {
55 zvec_rust_sys::zvec_collection_options_set_read_only(self.handle, read_only)
56 })
57 }
58
59 pub fn read_only(&self) -> bool {
61 unsafe { zvec_rust_sys::zvec_collection_options_get_read_only(self.handle) }
62 }
63}
64
65impl Drop for CollectionOptions {
66 fn drop(&mut self) {
67 if !self.handle.is_null() {
68 unsafe { zvec_rust_sys::zvec_collection_options_destroy(self.handle) };
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct IndexStat {
76 pub name: String,
77 pub completeness: f32,
78}
79
80#[derive(Debug, Clone)]
82pub struct CollectionStats {
83 pub doc_count: u64,
84 pub indexes: Vec<IndexStat>,
85}
86
87#[derive(Debug, Clone)]
89pub struct DocWriteResult {
90 pub success: bool,
91 pub code: ErrorCode,
92 pub message: String,
93}
94
95impl DocWriteResult {
96 pub fn is_success(&self) -> bool {
98 self.success
99 }
100}
101
102#[derive(Debug, Clone)]
104pub struct WriteResult {
105 pub success_count: u64,
106 pub error_count: u64,
107 pub results: Vec<DocWriteResult>,
108}
109
110pub struct Collection {
117 handle: *mut zvec_rust_sys::zvec_collection_t,
118}
119
120impl Collection {
121 pub unsafe fn as_raw(&self) -> *mut zvec_rust_sys::zvec_collection_t {
126 self.handle
127 }
128
129 pub unsafe fn from_raw(handle: *mut zvec_rust_sys::zvec_collection_t) -> Self {
135 Collection { handle }
136 }
137
138 pub fn create_and_open(
140 path: &str,
141 schema: &CollectionSchema,
142 options: Option<&CollectionOptions>,
143 ) -> Result<Self> {
144 let c_path = to_cstring(path)?;
145 let c_options = options.map(|o| o.handle as *const _).unwrap_or(ptr::null());
146
147 let mut handle: *mut zvec_rust_sys::zvec_collection_t = ptr::null_mut();
148 check_error(unsafe {
149 zvec_rust_sys::zvec_collection_create_and_open(
150 c_path.as_ptr(),
151 schema.handle,
152 c_options,
153 &mut handle,
154 )
155 })?;
156
157 Ok(Collection { handle })
158 }
159
160 pub fn open(path: &str, options: Option<&CollectionOptions>) -> Result<Self> {
162 let c_path = to_cstring(path)?;
163 let c_options = options.map(|o| o.handle as *const _).unwrap_or(ptr::null());
164
165 let mut handle: *mut zvec_rust_sys::zvec_collection_t = ptr::null_mut();
166 check_error(unsafe {
167 zvec_rust_sys::zvec_collection_open(c_path.as_ptr(), c_options, &mut handle)
168 })?;
169
170 Ok(Collection { handle })
171 }
172
173 pub fn flush(&self) -> Result<()> {
175 check_error(unsafe { zvec_rust_sys::zvec_collection_flush(self.handle) })
176 }
177
178 pub fn schema(&self) -> Result<CollectionSchema> {
180 let mut schema_handle: *mut zvec_rust_sys::zvec_collection_schema_t = ptr::null_mut();
181 check_error(unsafe {
182 zvec_rust_sys::zvec_collection_get_schema(self.handle, &mut schema_handle)
183 })?;
184 Ok(CollectionSchema::from_owned(schema_handle))
185 }
186
187 pub fn stats(&self) -> Result<CollectionStats> {
189 let mut stats_handle: *mut zvec_rust_sys::zvec_collection_stats_t = ptr::null_mut();
190 check_error(unsafe {
191 zvec_rust_sys::zvec_collection_get_stats(self.handle, &mut stats_handle)
192 })?;
193
194 let doc_count = unsafe { zvec_rust_sys::zvec_collection_stats_get_doc_count(stats_handle) };
195 let index_count =
196 unsafe { zvec_rust_sys::zvec_collection_stats_get_index_count(stats_handle) };
197
198 let mut indexes = Vec::with_capacity(index_count);
199
200 for i in 0..index_count {
201 let name_ptr =
202 unsafe { zvec_rust_sys::zvec_collection_stats_get_index_name(stats_handle, i) };
203 let name = if name_ptr.is_null() {
204 String::new()
205 } else {
206 unsafe { CStr::from_ptr(name_ptr).to_string_lossy().into_owned() }
207 };
208
209 let completeness = unsafe {
210 zvec_rust_sys::zvec_collection_stats_get_index_completeness(stats_handle, i)
211 };
212 indexes.push(IndexStat { name, completeness });
213 }
214
215 unsafe { zvec_rust_sys::zvec_collection_stats_destroy(stats_handle) };
216
217 Ok(CollectionStats { doc_count, indexes })
218 }
219
220 pub fn insert(&self, docs: &[&Doc]) -> Result<WriteResult> {
226 let ptrs: Vec<*const zvec_rust_sys::zvec_doc_t> =
227 docs.iter().map(|d| d.handle as *const _).collect();
228 let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
229 let mut result_count: usize = 0;
230
231 check_error(unsafe {
232 zvec_rust_sys::zvec_collection_insert_with_results(
233 self.handle,
234 ptrs.as_ptr(),
235 ptrs.len(),
236 &mut results,
237 &mut result_count,
238 )
239 })?;
240
241 Ok(collect_write_results(results, result_count))
242 }
243
244 pub fn update(&self, docs: &[&Doc]) -> Result<WriteResult> {
246 let ptrs: Vec<*const zvec_rust_sys::zvec_doc_t> =
247 docs.iter().map(|d| d.handle as *const _).collect();
248 let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
249 let mut result_count: usize = 0;
250
251 check_error(unsafe {
252 zvec_rust_sys::zvec_collection_update_with_results(
253 self.handle,
254 ptrs.as_ptr(),
255 ptrs.len(),
256 &mut results,
257 &mut result_count,
258 )
259 })?;
260
261 Ok(collect_write_results(results, result_count))
262 }
263
264 pub fn upsert(&self, docs: &[&Doc]) -> Result<WriteResult> {
266 let ptrs: Vec<*const zvec_rust_sys::zvec_doc_t> =
267 docs.iter().map(|d| d.handle as *const _).collect();
268 let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
269 let mut result_count: usize = 0;
270
271 check_error(unsafe {
272 zvec_rust_sys::zvec_collection_upsert_with_results(
273 self.handle,
274 ptrs.as_ptr(),
275 ptrs.len(),
276 &mut results,
277 &mut result_count,
278 )
279 })?;
280
281 Ok(collect_write_results(results, result_count))
282 }
283
284 pub fn delete(&self, pks: &[&str]) -> Result<WriteResult> {
286 let c_pks: Vec<_> = pks
287 .iter()
288 .map(|pk| to_cstring(pk))
289 .collect::<Result<Vec<_>>>()?;
290 let c_ptrs: Vec<_> = c_pks.iter().map(|pk| pk.as_ptr()).collect();
291 let mut results: *mut zvec_rust_sys::zvec_write_result_t = ptr::null_mut();
292 let mut result_count: usize = 0;
293
294 check_error(unsafe {
295 zvec_rust_sys::zvec_collection_delete_with_results(
296 self.handle,
297 c_ptrs.as_ptr(),
298 c_ptrs.len(),
299 &mut results,
300 &mut result_count,
301 )
302 })?;
303
304 Ok(collect_write_results(results, result_count))
305 }
306
307 pub fn delete_by_filter(&self, filter: &str) -> Result<()> {
309 let c_filter = to_cstring(filter)?;
310 check_error(unsafe {
311 zvec_rust_sys::zvec_collection_delete_by_filter(self.handle, c_filter.as_ptr())
312 })
313 }
314
315 pub fn query(&self, query: &SearchQuery) -> Result<Vec<Doc>> {
321 let mut results: *mut *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
322 let mut result_count: usize = 0;
323
324 check_error(unsafe {
325 zvec_rust_sys::zvec_collection_query(
326 self.handle,
327 query.handle,
328 &mut results,
329 &mut result_count,
330 )
331 })?;
332
333 let docs = unsafe { collect_docs(results, result_count) };
334 Ok(docs)
335 }
336
337 pub fn multi_query(&self, query: &MultiQuery) -> Result<Vec<Doc>> {
340 let mut results: *mut *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
341 let mut result_count: usize = 0;
342
343 check_error(unsafe {
344 zvec_rust_sys::zvec_collection_multi_query(
345 self.handle,
346 query.handle,
347 &mut results,
348 &mut result_count,
349 )
350 })?;
351
352 let docs = unsafe { collect_docs(results, result_count) };
353 Ok(docs)
354 }
355
356 pub fn fetch(&self, pks: &[&str]) -> Result<Vec<Doc>> {
358 self.fetch_with_options(pks, None, true)
359 }
360
361 pub fn fetch_with_options(
363 &self,
364 pks: &[&str],
365 output_fields: Option<&[&str]>,
366 include_vector: bool,
367 ) -> Result<Vec<Doc>> {
368 let c_pks: Vec<_> = pks
369 .iter()
370 .map(|pk| to_cstring(pk))
371 .collect::<Result<Vec<_>>>()?;
372 let c_pk_ptrs: Vec<_> = c_pks.iter().map(|pk| pk.as_ptr()).collect();
373
374 let c_fields = output_fields
375 .map(|fields| {
376 fields
377 .iter()
378 .map(|f| to_cstring(f))
379 .collect::<Result<Vec<_>>>()
380 })
381 .transpose()?;
382 let c_field_ptrs: Option<Vec<_>> = c_fields
383 .as_ref()
384 .map(|f| f.iter().map(|s| s.as_ptr()).collect());
385 let (fields_ptr, fields_count) = match &c_field_ptrs {
386 Some(ptrs) => (ptrs.as_ptr(), ptrs.len()),
387 None => (ptr::null(), 0),
388 };
389
390 let mut documents: *mut *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
391 let mut found_count: usize = 0;
392
393 check_error(unsafe {
394 zvec_rust_sys::zvec_collection_fetch(
395 self.handle,
396 c_pk_ptrs.as_ptr(),
397 c_pk_ptrs.len(),
398 fields_ptr,
399 fields_count,
400 include_vector,
401 &mut documents,
402 &mut found_count,
403 )
404 })?;
405
406 let docs = unsafe { collect_docs(documents, found_count) };
407 Ok(docs)
408 }
409
410 pub fn create_index(&self, field_name: &str, params: &IndexParams) -> Result<()> {
416 let c_name = to_cstring(field_name)?;
417 check_error(unsafe {
418 zvec_rust_sys::zvec_collection_create_index(self.handle, c_name.as_ptr(), params.handle)
419 })
420 }
421
422 pub fn drop_index(&self, field_name: &str) -> Result<()> {
424 let c_name = to_cstring(field_name)?;
425 check_error(unsafe {
426 zvec_rust_sys::zvec_collection_drop_index(self.handle, c_name.as_ptr())
427 })
428 }
429
430 pub fn optimize(&self) -> Result<()> {
432 check_error(unsafe { zvec_rust_sys::zvec_collection_optimize(self.handle) })
433 }
434
435 pub fn add_column(&self, field_schema: &FieldSchema, default_expr: Option<&str>) -> Result<()> {
441 let c_expr_owned = default_expr.map(to_cstring).transpose()?;
442 let c_expr_ptr = c_expr_owned
443 .as_ref()
444 .map(|s| s.as_ptr())
445 .unwrap_or(ptr::null());
446 check_error(unsafe {
447 zvec_rust_sys::zvec_collection_add_column(self.handle, field_schema.handle, c_expr_ptr)
448 })
449 }
450
451 pub fn drop_column(&self, name: &str) -> Result<()> {
453 let c_name = to_cstring(name)?;
454 check_error(unsafe {
455 zvec_rust_sys::zvec_collection_drop_column(self.handle, c_name.as_ptr())
456 })
457 }
458
459 pub fn close(self) -> Result<()> {
461 drop(self);
463 Ok(())
464 }
465}
466
467impl Drop for Collection {
468 fn drop(&mut self) {
469 if !self.handle.is_null() {
470 let rc = unsafe { zvec_rust_sys::zvec_collection_close(self.handle) };
472 if rc != zvec_rust_sys::ZVEC_OK {
473 eprintln!(
474 "zvec warning: failed to close collection (error code {})",
475 rc
476 );
477 }
478 }
479 }
480}
481
482unsafe impl Send for Collection {}
486unsafe impl Sync for Collection {}
487
488fn collect_write_results(
490 results: *mut zvec_rust_sys::zvec_write_result_t,
491 count: usize,
492) -> WriteResult {
493 let mut doc_results = Vec::with_capacity(count);
494 let mut success_count: u64 = 0;
495 let mut error_count: u64 = 0;
496
497 if !results.is_null() && count > 0 {
498 for i in 0..count {
499 let wr = unsafe { &*results.add(i) };
500 let is_ok = wr.code == zvec_rust_sys::ZVEC_OK;
501 let message = if wr.message.is_null() {
502 String::new()
503 } else {
504 unsafe { CStr::from_ptr(wr.message).to_string_lossy().into_owned() }
505 };
506 if is_ok {
507 success_count += 1;
508 } else {
509 error_count += 1;
510 }
511 doc_results.push(DocWriteResult {
512 success: is_ok,
513 code: ErrorCode::from(wr.code),
514 message,
515 });
516 }
517 unsafe { zvec_rust_sys::zvec_write_results_free(results, count) };
518 }
519
520 WriteResult {
521 success_count,
522 error_count,
523 results: doc_results,
524 }
525}
526
527unsafe fn collect_docs(results: *mut *mut zvec_rust_sys::zvec_doc_t, count: usize) -> Vec<Doc> {
533 if results.is_null() || count == 0 {
534 return Vec::new();
535 }
536
537 let mut docs = Vec::with_capacity(count);
538 for i in 0..count {
539 let doc_ptr = *results.add(i);
540 if !doc_ptr.is_null() {
541 docs.push(Doc::from_raw(doc_ptr));
543 }
544 }
545
546 zvec_rust_sys::zvec_free(results as *mut std::os::raw::c_void);
549
550 docs
551}
552
553#[cfg(test)]
554mod tests {
555 use super::*;
556
557 #[test]
558 fn collect_docs_handles_null_pointer() {
559 let docs = unsafe { collect_docs(ptr::null_mut(), 0) };
560 assert!(docs.is_empty());
561 }
562
563 #[test]
564 fn collect_docs_handles_zero_count() {
565 let mut fake: *mut zvec_rust_sys::zvec_doc_t = ptr::null_mut();
567 let docs = unsafe { collect_docs(&mut fake as *mut _, 0) };
568 assert!(docs.is_empty());
569 }
570}