1use std::cell::RefCell;
36use std::ffi::{CStr, CString};
37use std::os::raw::c_char;
38use std::path::Path;
39use std::ptr;
40
41use omendb::vector::{MetadataFilter, Vector, VectorStore, VectorStoreOptions};
42use serde_json::{json, Value as JsonValue};
43
44thread_local! {
45 static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
46}
47
48fn set_last_error(err: String) {
49 LAST_ERROR.with(|e| {
50 *e.borrow_mut() = CString::new(err).ok();
51 });
52}
53
54fn clear_last_error() {
55 LAST_ERROR.with(|e| *e.borrow_mut() = None);
56}
57
58pub struct OmenDB {
60 store: VectorStore,
61 dimensions: usize,
62}
63
64#[no_mangle]
87pub unsafe extern "C" fn omendb_open(
88 path: *const c_char,
89 dimensions: usize,
90 config_json: *const c_char,
91) -> *mut OmenDB {
92 clear_last_error();
93
94 if path.is_null() {
95 set_last_error("Null path pointer".to_string());
96 return ptr::null_mut();
97 }
98
99 let path = match CStr::from_ptr(path).to_str() {
100 Ok(s) => s,
101 Err(e) => {
102 set_last_error(format!("Invalid path: {e}"));
103 return ptr::null_mut();
104 }
105 };
106
107 let config: Option<JsonValue> = if config_json.is_null() {
109 None
110 } else {
111 let config_str = match CStr::from_ptr(config_json).to_str() {
112 Ok(s) => s,
113 Err(e) => {
114 set_last_error(format!("Invalid config string: {e}"));
115 return ptr::null_mut();
116 }
117 };
118 match serde_json::from_str(config_str) {
119 Ok(v) => Some(v),
120 Err(e) => {
121 set_last_error(format!("Invalid config JSON: {e}"));
122 return ptr::null_mut();
123 }
124 }
125 };
126
127 let result = if let Some(cfg) = config {
129 let mut options = VectorStoreOptions::new().dimensions(dimensions);
130
131 if let Some(m) = cfg.get("m").and_then(JsonValue::as_u64) {
132 options = options.m(m as usize);
133 }
134 if let Some(ef_c) = cfg.get("ef_construction").and_then(JsonValue::as_u64) {
135 options = options.ef_construction(ef_c as usize);
136 }
137 if let Some(ef_s) = cfg.get("ef_search").and_then(JsonValue::as_u64) {
138 options = options.ef_search(ef_s as usize);
139 }
140
141 options.open(Path::new(path))
142 } else {
143 VectorStore::open_with_dimensions(Path::new(path), dimensions)
144 };
145
146 match result {
147 Ok(store) => {
148 Box::into_raw(Box::new(OmenDB {
149 store,
150 dimensions,
151 }))
152 }
153 Err(e) => {
154 set_last_error(format!("Failed to open database: {e}"));
155 ptr::null_mut()
156 }
157 }
158}
159
160#[no_mangle]
166pub unsafe extern "C" fn omendb_close(db: *mut OmenDB) {
167 if !db.is_null() {
168 drop(Box::from_raw(db));
169 }
170}
171
172#[no_mangle]
185pub unsafe extern "C" fn omendb_set(db: *mut OmenDB, items_json: *const c_char) -> i64 {
186 clear_last_error();
187
188 let Some(db) = db.as_mut() else {
189 set_last_error("Null database handle".to_string());
190 return -1;
191 };
192
193 if items_json.is_null() {
194 set_last_error("Null items_json pointer".to_string());
195 return -1;
196 }
197
198 let items_str = match CStr::from_ptr(items_json).to_str() {
199 Ok(s) => s,
200 Err(e) => {
201 set_last_error(format!("Invalid JSON string: {e}"));
202 return -1;
203 }
204 };
205
206 let items: Vec<JsonValue> = match serde_json::from_str(items_str) {
207 Ok(v) => v,
208 Err(e) => {
209 set_last_error(format!("JSON parse error: {e}"));
210 return -1;
211 }
212 };
213
214 let mut count = 0i64;
215 for item in items {
216 let id = if let Some(s) = item.get("id").and_then(|v| v.as_str()) {
217 s.to_string()
218 } else {
219 set_last_error("Item missing 'id' field".to_string());
220 return -1;
221 };
222
223 let vector_data: Vec<f32> = if let Some(arr) = item.get("vector").and_then(|v| v.as_array())
224 {
225 arr.iter()
226 .filter_map(|v| v.as_f64().map(|f| f as f32))
227 .collect()
228 } else {
229 set_last_error("Item missing 'vector' field".to_string());
230 return -1;
231 };
232
233 let metadata = item.get("metadata").cloned().unwrap_or(json!({}));
234
235 let vector = Vector::new(vector_data);
236 if let Err(e) = db.store.set(id, vector, metadata) {
237 set_last_error(format!("Set failed after {count} items: {e}"));
238 return -1;
239 }
240 count += 1;
241 }
242
243 count
244}
245
246#[no_mangle]
261pub unsafe extern "C" fn omendb_get(
262 db: *mut OmenDB,
263 ids_json: *const c_char,
264 result: *mut *mut c_char,
265) -> i32 {
266 clear_last_error();
267
268 let Some(db) = db.as_ref() else {
269 set_last_error("Null database handle".to_string());
270 return -1;
271 };
272
273 if ids_json.is_null() {
274 set_last_error("Null ids_json pointer".to_string());
275 return -1;
276 }
277
278 let ids_str = match CStr::from_ptr(ids_json).to_str() {
279 Ok(s) => s,
280 Err(e) => {
281 set_last_error(format!("Invalid JSON string: {e}"));
282 return -1;
283 }
284 };
285
286 let ids: Vec<String> = match serde_json::from_str(ids_str) {
287 Ok(v) => v,
288 Err(e) => {
289 set_last_error(format!("JSON parse error: {e}"));
290 return -1;
291 }
292 };
293
294 let mut results = Vec::new();
295 for id in ids {
296 if let Some((vector, metadata)) = db.store.get(&id) {
297 results.push(json!({
298 "id": id,
299 "vector": vector.data,
300 "metadata": metadata
301 }));
302 }
303 }
304
305 let json_str = match serde_json::to_string(&results) {
306 Ok(s) => s,
307 Err(e) => {
308 set_last_error(format!("JSON serialize error: {e}"));
309 return -1;
310 }
311 };
312
313 if result.is_null() {
314 set_last_error("Output pointer is NULL".to_string());
315 return -1;
316 }
317
318 match CString::new(json_str) {
319 Ok(cstr) => {
320 *result = cstr.into_raw();
321 0
322 }
323 Err(e) => {
324 set_last_error(format!("CString error: {e}"));
325 -1
326 }
327 }
328}
329
330#[no_mangle]
339pub unsafe extern "C" fn omendb_delete(db: *mut OmenDB, ids_json: *const c_char) -> i64 {
340 clear_last_error();
341
342 let Some(db) = db.as_mut() else {
343 set_last_error("Null database handle".to_string());
344 return -1;
345 };
346
347 if ids_json.is_null() {
348 set_last_error("Null ids_json pointer".to_string());
349 return -1;
350 }
351
352 let ids_str = match CStr::from_ptr(ids_json).to_str() {
353 Ok(s) => s,
354 Err(e) => {
355 set_last_error(format!("Invalid JSON string: {e}"));
356 return -1;
357 }
358 };
359
360 let ids: Vec<String> = match serde_json::from_str(ids_str) {
361 Ok(v) => v,
362 Err(e) => {
363 set_last_error(format!("JSON parse error: {e}"));
364 return -1;
365 }
366 };
367
368 match db.store.delete_batch(&ids) {
369 Ok(count) => {
370 i64::try_from(count).unwrap_or(i64::MAX)
371 }
372 Err(e) => {
373 set_last_error(format!("Delete failed: {e}"));
374 -1
375 }
376 }
377}
378
379#[no_mangle]
398pub unsafe extern "C" fn omendb_search(
399 db: *mut OmenDB,
400 query: *const f32,
401 query_len: usize,
402 k: usize,
403 filter_json: *const c_char,
404 result: *mut *mut c_char,
405) -> i32 {
406 clear_last_error();
407
408 let Some(db) = db.as_mut() else {
409 set_last_error("Null database handle".to_string());
410 return -1;
411 };
412
413 if query.is_null() {
414 set_last_error("Null query pointer".to_string());
415 return -1;
416 }
417
418 if query_len != db.dimensions {
419 set_last_error(format!(
420 "Query dimension mismatch: expected {}, got {query_len}",
421 db.dimensions
422 ));
423 return -1;
424 }
425
426 let query_vec: Vec<f32> = std::slice::from_raw_parts(query, query_len).to_vec();
427 let query = Vector::new(query_vec);
428
429 let filter: Option<MetadataFilter> = if filter_json.is_null() {
431 None
432 } else {
433 let filter_str = match CStr::from_ptr(filter_json).to_str() {
434 Ok(s) => s,
435 Err(e) => {
436 set_last_error(format!("Invalid filter string: {e}"));
437 return -1;
438 }
439 };
440 match serde_json::from_str::<JsonValue>(filter_str) {
441 Ok(v) => match MetadataFilter::from_json(&v) {
442 Ok(f) => Some(f),
443 Err(e) => {
444 set_last_error(format!("Invalid filter format: {e}"));
445 return -1;
446 }
447 },
448 Err(e) => {
449 set_last_error(format!("Invalid filter JSON: {e}"));
450 return -1;
451 }
452 }
453 };
454
455 let results = match db.store.search(&query, k, filter.as_ref()) {
457 Ok(r) => r,
458 Err(e) => {
459 set_last_error(format!("Search failed: {e}"));
460 return -1;
461 }
462 };
463
464 let json_results: Vec<JsonValue> = results
466 .into_iter()
467 .map(|result| {
468 json!({
469 "id": result.id,
470 "distance": result.distance,
471 "metadata": result.metadata
472 })
473 })
474 .collect();
475
476 let json_str = match serde_json::to_string(&json_results) {
477 Ok(s) => s,
478 Err(e) => {
479 set_last_error(format!("JSON serialize error: {e}"));
480 return -1;
481 }
482 };
483
484 if result.is_null() {
485 set_last_error("Output pointer is NULL".to_string());
486 return -1;
487 }
488
489 match CString::new(json_str) {
490 Ok(cstr) => {
491 *result = cstr.into_raw();
492 0
493 }
494 Err(e) => {
495 set_last_error(format!("CString error: {e}"));
496 -1
497 }
498 }
499}
500
501#[no_mangle]
506pub unsafe extern "C" fn omendb_count(db: *const OmenDB) -> i64 {
507 clear_last_error();
508 match db.as_ref() {
509 Some(db) => i64::try_from(db.store.len()).unwrap_or(i64::MAX),
510 None => {
511 set_last_error("Null database handle".to_string());
512 -1
513 }
514 }
515}
516
517#[no_mangle]
522pub unsafe extern "C" fn omendb_save(db: *mut OmenDB) -> i32 {
523 clear_last_error();
524
525 let Some(db) = db.as_mut() else {
526 set_last_error("Null database handle".to_string());
527 return -1;
528 };
529
530 match db.store.flush() {
531 Ok(()) => 0,
532 Err(e) => {
533 set_last_error(format!("Save failed: {e}"));
534 -1
535 }
536 }
537}
538
539#[no_mangle]
544pub extern "C" fn omendb_last_error() -> *const c_char {
545 LAST_ERROR.with(|e| match &*e.borrow() {
546 Some(cstr) => cstr.as_ptr(),
547 None => ptr::null(),
548 })
549}
550
551#[no_mangle]
557pub unsafe extern "C" fn omendb_free_string(s: *mut c_char) {
558 if !s.is_null() {
559 drop(CString::from_raw(s));
560 }
561}
562
563#[no_mangle]
565pub extern "C" fn omendb_version() -> *const c_char {
566 concat!(env!("CARGO_PKG_VERSION"), "\0")
568 .as_ptr()
569 .cast::<c_char>()
570}
571
572#[no_mangle]
584pub unsafe extern "C" fn omendb_enable_text_search(db: *mut OmenDB) -> i32 {
585 clear_last_error();
586
587 let Some(db) = db.as_mut() else {
588 set_last_error("Null database handle".to_string());
589 return -1;
590 };
591
592 match db.store.enable_text_search() {
593 Ok(()) => 0,
594 Err(e) => {
595 set_last_error(format!("Failed to enable text search: {e}"));
596 -1
597 }
598 }
599}
600
601#[no_mangle]
609pub unsafe extern "C" fn omendb_has_text_search(db: *const OmenDB) -> i32 {
610 clear_last_error();
611 let Some(db) = db.as_ref() else {
612 set_last_error("Null database handle".to_string());
613 return -1;
614 };
615 i32::from(db.store.has_text_search())
616}
617
618#[no_mangle]
631pub unsafe extern "C" fn omendb_set_with_text(db: *mut OmenDB, items_json: *const c_char) -> i64 {
632 clear_last_error();
633
634 let Some(db) = db.as_mut() else {
635 set_last_error("Null database handle".to_string());
636 return -1;
637 };
638
639 if !db.store.has_text_search() {
640 set_last_error(
641 "Text search not enabled. Call omendb_enable_text_search first.".to_string(),
642 );
643 return -1;
644 }
645
646 if items_json.is_null() {
647 set_last_error("Null items_json pointer".to_string());
648 return -1;
649 }
650
651 let items_str = match CStr::from_ptr(items_json).to_str() {
652 Ok(s) => s,
653 Err(e) => {
654 set_last_error(format!("Invalid JSON string: {e}"));
655 return -1;
656 }
657 };
658
659 let items: Vec<JsonValue> = match serde_json::from_str(items_str) {
660 Ok(v) => v,
661 Err(e) => {
662 set_last_error(format!("JSON parse error: {e}"));
663 return -1;
664 }
665 };
666
667 let mut count = 0i64;
668 for item in items {
669 let id = if let Some(s) = item.get("id").and_then(|v| v.as_str()) {
670 s.to_string()
671 } else {
672 set_last_error("Item missing 'id' field".to_string());
673 return -1;
674 };
675
676 let vector_data: Vec<f32> = if let Some(arr) = item.get("vector").and_then(|v| v.as_array())
677 {
678 arr.iter()
679 .filter_map(|v| v.as_f64().map(|f| f as f32))
680 .collect()
681 } else {
682 set_last_error("Item missing 'vector' field".to_string());
683 return -1;
684 };
685
686 let text = if let Some(s) = item.get("text").and_then(|v| v.as_str()) {
687 s
688 } else {
689 set_last_error("Item missing 'text' field".to_string());
690 return -1;
691 };
692
693 let metadata = item.get("metadata").cloned().unwrap_or(json!({}));
694
695 let vector = Vector::new(vector_data);
696 if let Err(e) = db.store.set_with_text(id, vector, text, metadata) {
697 set_last_error(format!("Set with text failed after {count} items: {e}"));
698 return -1;
699 }
700 count += 1;
701 }
702
703 count
704}
705
706#[no_mangle]
720pub unsafe extern "C" fn omendb_text_search(
721 db: *mut OmenDB,
722 query: *const c_char,
723 k: usize,
724 result: *mut *mut c_char,
725) -> i32 {
726 clear_last_error();
727
728 let Some(db) = db.as_ref() else {
729 set_last_error("Null database handle".to_string());
730 return -1;
731 };
732
733 if query.is_null() {
734 set_last_error("Null query pointer".to_string());
735 return -1;
736 }
737
738 let query_str = match CStr::from_ptr(query).to_str() {
739 Ok(s) => s,
740 Err(e) => {
741 set_last_error(format!("Invalid query string: {e}"));
742 return -1;
743 }
744 };
745
746 let search_results = match db.store.text_search(query_str, k) {
747 Ok(r) => r,
748 Err(e) => {
749 set_last_error(format!("Text search failed: {e}"));
750 return -1;
751 }
752 };
753
754 let json_results: Vec<JsonValue> = search_results
755 .into_iter()
756 .map(|(id, score)| json!({"id": id, "score": score}))
757 .collect();
758
759 let json_str = match serde_json::to_string(&json_results) {
760 Ok(s) => s,
761 Err(e) => {
762 set_last_error(format!("JSON serialize error: {e}"));
763 return -1;
764 }
765 };
766
767 if result.is_null() {
768 set_last_error("Output pointer is NULL".to_string());
769 return -1;
770 }
771
772 match CString::new(json_str) {
773 Ok(cstr) => {
774 *result = cstr.into_raw();
775 0
776 }
777 Err(e) => {
778 set_last_error(format!("CString error: {e}"));
779 -1
780 }
781 }
782}
783
784#[no_mangle]
805pub unsafe extern "C" fn omendb_hybrid_search(
806 db: *mut OmenDB,
807 query_vector: *const f32,
808 query_len: usize,
809 query_text: *const c_char,
810 k: usize,
811 alpha: f32,
812 rrf_k: usize,
813 filter_json: *const c_char,
814 result: *mut *mut c_char,
815) -> i32 {
816 clear_last_error();
817
818 let Some(db) = db.as_mut() else {
819 set_last_error("Null database handle".to_string());
820 return -1;
821 };
822
823 if query_vector.is_null() {
824 set_last_error("Null query_vector pointer".to_string());
825 return -1;
826 }
827
828 if query_text.is_null() {
829 set_last_error("Null query_text pointer".to_string());
830 return -1;
831 }
832
833 if query_len != db.dimensions {
834 set_last_error(format!(
835 "Query dimension mismatch: expected {}, got {query_len}",
836 db.dimensions
837 ));
838 return -1;
839 }
840
841 let query_vec: Vec<f32> = std::slice::from_raw_parts(query_vector, query_len).to_vec();
842 let vector = Vector::new(query_vec);
843
844 let text_str = match CStr::from_ptr(query_text).to_str() {
845 Ok(s) => s,
846 Err(e) => {
847 set_last_error(format!("Invalid text query: {e}"));
848 return -1;
849 }
850 };
851
852 let alpha_opt = if alpha < 0.0 { None } else { Some(alpha) };
854 let rrf_k_opt = if rrf_k == 0 { None } else { Some(rrf_k) };
855
856 let filter = if filter_json.is_null() {
858 None
859 } else {
860 let filter_str = match CStr::from_ptr(filter_json).to_str() {
861 Ok(s) => s,
862 Err(e) => {
863 set_last_error(format!("Invalid filter string: {e}"));
864 return -1;
865 }
866 };
867 match serde_json::from_str::<JsonValue>(filter_str) {
868 Ok(v) => match MetadataFilter::from_json(&v) {
869 Ok(f) => Some(f),
870 Err(e) => {
871 set_last_error(format!("Invalid filter format: {e}"));
872 return -1;
873 }
874 },
875 Err(e) => {
876 set_last_error(format!("Invalid filter JSON: {e}"));
877 return -1;
878 }
879 }
880 };
881
882 let search_results = if let Some(f) = filter {
883 match db
884 .store
885 .hybrid_search_with_filter_rrf_k(&vector, text_str, k, &f, alpha_opt, rrf_k_opt)
886 {
887 Ok(r) => r,
888 Err(e) => {
889 set_last_error(format!("Hybrid search failed: {e}"));
890 return -1;
891 }
892 }
893 } else {
894 match db
895 .store
896 .hybrid_search_with_rrf_k(&vector, text_str, k, alpha_opt, rrf_k_opt)
897 {
898 Ok(r) => r,
899 Err(e) => {
900 set_last_error(format!("Hybrid search failed: {e}"));
901 return -1;
902 }
903 }
904 };
905
906 let json_results: Vec<JsonValue> = search_results
907 .into_iter()
908 .map(|(id, score, metadata)| json!({"id": id, "score": score, "metadata": metadata}))
909 .collect();
910
911 let json_str = match serde_json::to_string(&json_results) {
912 Ok(s) => s,
913 Err(e) => {
914 set_last_error(format!("JSON serialize error: {e}"));
915 return -1;
916 }
917 };
918
919 if result.is_null() {
920 set_last_error("Output pointer is NULL".to_string());
921 return -1;
922 }
923
924 match CString::new(json_str) {
925 Ok(cstr) => {
926 *result = cstr.into_raw();
927 0
928 }
929 Err(e) => {
930 set_last_error(format!("CString error: {e}"));
931 -1
932 }
933 }
934}
935
936#[no_mangle]
941pub unsafe extern "C" fn omendb_flush(db: *mut OmenDB) -> i32 {
942 clear_last_error();
943
944 let Some(db) = db.as_mut() else {
945 set_last_error("Null database handle".to_string());
946 return -1;
947 };
948
949 match db.store.flush() {
950 Ok(()) => 0,
951 Err(e) => {
952 set_last_error(format!("Flush failed: {e}"));
953 -1
954 }
955 }
956}