#![allow(clippy::not_unsafe_ptr_arg_deref)]
#![allow(clippy::cast_slice_from_raw_parts)]
use std::ffi::CStr;
use std::os::raw::c_char;
use crate::engine::{close_db, free_tensor, open_db, open_db_with_config, with_db, DbConfig};
use crate::error::{
ERR_GENERIC, ERR_INTERNAL_PANIC, ERR_INVALID_PATH, ERR_KEY_NOT_FOUND, ERR_SUCCESS,
ERR_TYPE_MISMATCH,
};
use crate::types::Atom;
#[no_mangle]
pub extern "C" fn SYNA_open(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let c_str = unsafe { CStr::from_ptr(path) };
let path_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match open_db(path_str) {
Ok(_) => ERR_SUCCESS,
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_open_with_config(path: *const c_char, sync_on_write: i32) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let c_str = unsafe { CStr::from_ptr(path) };
let path_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let config = DbConfig {
sync_on_write: sync_on_write != 0,
..DbConfig::default()
};
match open_db_with_config(path_str, config) {
Ok(_) => ERR_SUCCESS,
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_close(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let c_str = unsafe { CStr::from_ptr(path) };
let path_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match close_db(path_str) {
Ok(_) => ERR_SUCCESS,
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_put_float(path: *const c_char, key: *const c_char, value: f64) -> i64 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() {
return ERR_INVALID_PATH as i64;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
match with_db(path_str, |db| db.append(key_str, Atom::Float(value))) {
Ok(offset) => offset as i64,
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND as i64,
Err(_) => ERR_GENERIC as i64,
}
})
.unwrap_or(ERR_INTERNAL_PANIC as i64)
}
#[no_mangle]
pub extern "C" fn SYNA_put_int(path: *const c_char, key: *const c_char, value: i64) -> i64 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() {
return ERR_INVALID_PATH as i64;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
match with_db(path_str, |db| db.append(key_str, Atom::Int(value))) {
Ok(offset) => offset as i64,
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND as i64,
Err(_) => ERR_GENERIC as i64,
}
})
.unwrap_or(ERR_INTERNAL_PANIC as i64)
}
#[no_mangle]
pub extern "C" fn SYNA_put_text(
path: *const c_char,
key: *const c_char,
value: *const c_char,
) -> i64 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || value.is_null() {
return ERR_INVALID_PATH as i64;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let value_str = match unsafe { CStr::from_ptr(value) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
match with_db(path_str, |db| {
db.append(key_str, Atom::Text(value_str.to_string()))
}) {
Ok(offset) => offset as i64,
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND as i64,
Err(_) => ERR_GENERIC as i64,
}
})
.unwrap_or(ERR_INTERNAL_PANIC as i64)
}
#[no_mangle]
pub extern "C" fn SYNA_put_bytes(
path: *const c_char,
key: *const c_char,
data: *const u8,
len: usize,
) -> i64 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || (data.is_null() && len > 0) {
return ERR_INVALID_PATH as i64;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let bytes = if len == 0 {
Vec::new()
} else {
unsafe { std::slice::from_raw_parts(data, len) }.to_vec()
};
match with_db(path_str, |db| db.append(key_str, Atom::Bytes(bytes))) {
Ok(offset) => offset as i64,
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND as i64,
Err(_) => ERR_GENERIC as i64,
}
})
.unwrap_or(ERR_INTERNAL_PANIC as i64)
}
#[no_mangle]
pub extern "C" fn SYNA_put_floats_batch(
path: *const c_char,
key: *const c_char,
values: *const f64,
count: usize,
) -> i64 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || (values.is_null() && count > 0) {
return ERR_INVALID_PATH as i64;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let slice = if count == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(values, count) }
};
match with_db(path_str, |db| db.append_floats_batch(key_str, slice)) {
Ok(n) => n as i64,
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND as i64,
Err(_) => ERR_GENERIC as i64,
}
})
.unwrap_or(ERR_INTERNAL_PANIC as i64)
}
#[no_mangle]
pub extern "C" fn SYNA_get_float(path: *const c_char, key: *const c_char, out: *mut f64) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || out.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match with_db(path_str, |db| db.get(key_str)) {
Ok(Some(Atom::Float(f))) => {
unsafe { *out = f };
ERR_SUCCESS
}
Ok(Some(_)) => {
ERR_TYPE_MISMATCH
}
Ok(None) => {
ERR_KEY_NOT_FOUND
}
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_get_int(path: *const c_char, key: *const c_char, out: *mut i64) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || out.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match with_db(path_str, |db| db.get(key_str)) {
Ok(Some(Atom::Int(i))) => {
unsafe { *out = i };
ERR_SUCCESS
}
Ok(Some(_)) => {
ERR_TYPE_MISMATCH
}
Ok(None) => {
ERR_KEY_NOT_FOUND
}
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_get_history_tensor(
path: *const c_char,
key: *const c_char,
out_len: *mut usize,
) -> *mut f64 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || out_len.is_null() {
return std::ptr::null_mut();
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
match with_db(path_str, |db| db.get_history_tensor(key_str)) {
Ok((ptr, len)) => {
unsafe { *out_len = len };
ptr
}
Err(_) => {
unsafe { *out_len = 0 };
std::ptr::null_mut()
}
}
})
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn SYNA_free_tensor(ptr: *mut f64, len: usize) {
std::panic::catch_unwind(|| {
unsafe { free_tensor(ptr, len) };
})
.ok(); }
#[no_mangle]
pub extern "C" fn SYNA_delete(path: *const c_char, key: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match with_db(path_str, |db| db.delete(key_str)) {
Ok(_) => ERR_SUCCESS,
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_exists(path: *const c_char, key: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match with_db(path_str, |db| Ok(db.exists(key_str))) {
Ok(true) => 1,
Ok(false) => 0,
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_compact(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match with_db(path_str, |db| db.compact()) {
Ok(_) => ERR_SUCCESS,
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND,
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_keys(path: *const c_char, out_len: *mut usize) -> *mut *mut c_char {
std::panic::catch_unwind(|| {
if path.is_null() || out_len.is_null() {
return std::ptr::null_mut();
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
match with_db(path_str, |db| Ok(db.keys())) {
Ok(keys) => {
let len = keys.len();
if len == 0 {
unsafe { *out_len = 0 };
return std::ptr::null_mut();
}
let mut c_strings: Vec<*mut c_char> = Vec::with_capacity(len);
for key in keys {
let mut bytes = key.into_bytes();
bytes.push(0);
let c_str = bytes.into_boxed_slice();
let ptr = Box::into_raw(c_str) as *mut c_char;
c_strings.push(ptr);
}
unsafe { *out_len = len };
let boxed = c_strings.into_boxed_slice();
Box::into_raw(boxed) as *mut *mut c_char
}
Err(_) => {
unsafe { *out_len = 0 };
std::ptr::null_mut()
}
}
})
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn SYNA_free_keys(keys: *mut *mut c_char, len: usize) {
std::panic::catch_unwind(|| {
if keys.is_null() || len == 0 {
return;
}
unsafe {
let key_slice = std::slice::from_raw_parts_mut(keys, len);
for key_ptr in key_slice.iter() {
if !key_ptr.is_null() {
let c_str = CStr::from_ptr(*key_ptr);
let str_len = c_str.to_bytes_with_nul().len();
let _ =
Box::from_raw(std::slice::from_raw_parts_mut(*key_ptr as *mut u8, str_len));
}
}
let _ = Box::from_raw(std::slice::from_raw_parts_mut(keys, len));
}
})
.ok(); }
#[no_mangle]
pub extern "C" fn SYNA_get_text(
path: *const c_char,
key: *const c_char,
out_len: *mut usize,
) -> *mut c_char {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || out_len.is_null() {
return std::ptr::null_mut();
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
match with_db(path_str, |db| db.get(key_str)) {
Ok(Some(Atom::Text(s))) => {
let len = s.len();
unsafe { *out_len = len };
let mut bytes = s.into_bytes();
bytes.push(0);
let c_str = bytes.into_boxed_slice();
Box::into_raw(c_str) as *mut c_char
}
Ok(Some(_)) => {
unsafe { *out_len = 0 };
std::ptr::null_mut()
}
Ok(None) => {
unsafe { *out_len = 0 };
std::ptr::null_mut()
}
Err(_) => {
unsafe { *out_len = 0 };
std::ptr::null_mut()
}
}
})
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn SYNA_free_text(ptr: *mut c_char, len: usize) {
std::panic::catch_unwind(|| {
if ptr.is_null() {
return;
}
unsafe {
let _ = Box::from_raw(std::slice::from_raw_parts_mut(ptr as *mut u8, len + 1));
}
})
.ok();
}
#[no_mangle]
pub extern "C" fn SYNA_get_bytes(
path: *const c_char,
key: *const c_char,
out_len: *mut usize,
) -> *mut u8 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || out_len.is_null() {
return std::ptr::null_mut();
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
match with_db(path_str, |db| db.get(key_str)) {
Ok(Some(Atom::Bytes(bytes))) => {
let len = bytes.len();
if len == 0 {
unsafe { *out_len = 0 };
return std::ptr::null_mut();
}
unsafe { *out_len = len };
let boxed = bytes.into_boxed_slice();
Box::into_raw(boxed) as *mut u8
}
Ok(Some(_)) => {
unsafe { *out_len = 0 };
std::ptr::null_mut()
}
Ok(None) => {
unsafe { *out_len = 0 };
std::ptr::null_mut()
}
Err(_) => {
unsafe { *out_len = 0 };
std::ptr::null_mut()
}
}
})
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn SYNA_free_bytes(ptr: *mut u8, len: usize) {
std::panic::catch_unwind(|| {
if ptr.is_null() || len == 0 {
return;
}
unsafe {
let _ = Box::from_raw(std::slice::from_raw_parts_mut(ptr, len));
}
})
.ok();
}
#[no_mangle]
pub extern "C" fn SYNA_put_vector(
path: *const c_char,
key: *const c_char,
data: *const f32,
dimensions: u16,
) -> i64 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || (data.is_null() && dimensions > 0) {
return ERR_INVALID_PATH as i64;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let vector = if dimensions == 0 {
Vec::new()
} else {
unsafe { std::slice::from_raw_parts(data, dimensions as usize) }.to_vec()
};
match with_db(path_str, |db| {
db.append(key_str, Atom::Vector(vector, dimensions))
}) {
Ok(offset) => offset as i64,
Err(crate::error::SynaError::NotFound(_)) => crate::error::ERR_DB_NOT_FOUND as i64,
Err(_) => ERR_GENERIC as i64,
}
})
.unwrap_or(ERR_INTERNAL_PANIC as i64)
}
#[no_mangle]
pub extern "C" fn SYNA_get_vector(
path: *const c_char,
key: *const c_char,
out_data: *mut *mut f32,
out_dimensions: *mut u16,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || out_data.is_null() || out_dimensions.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match with_db(path_str, |db| db.get(key_str)) {
Ok(Some(Atom::Vector(vec_data, dims))) => {
unsafe { *out_dimensions = dims };
if vec_data.is_empty() {
unsafe { *out_data = std::ptr::null_mut() };
return ERR_SUCCESS;
}
let boxed = vec_data.into_boxed_slice();
unsafe { *out_data = Box::into_raw(boxed) as *mut f32 };
ERR_SUCCESS
}
Ok(Some(_)) => {
unsafe {
*out_data = std::ptr::null_mut();
*out_dimensions = 0;
}
ERR_TYPE_MISMATCH
}
Ok(None) => {
unsafe {
*out_data = std::ptr::null_mut();
*out_dimensions = 0;
}
ERR_KEY_NOT_FOUND
}
Err(crate::error::SynaError::NotFound(_)) => {
unsafe {
*out_data = std::ptr::null_mut();
*out_dimensions = 0;
}
crate::error::ERR_DB_NOT_FOUND
}
Err(_) => {
unsafe {
*out_data = std::ptr::null_mut();
*out_dimensions = 0;
}
ERR_GENERIC
}
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_free_vector(data: *mut f32, dimensions: u16) {
std::panic::catch_unwind(|| {
if data.is_null() || dimensions == 0 {
return;
}
unsafe {
let _ = Box::from_raw(std::slice::from_raw_parts_mut(data, dimensions as usize));
}
})
.ok(); }
use std::collections::HashMap;
use std::ffi::CString;
use std::path::{Path, PathBuf};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use crate::distance::DistanceMetric;
use crate::vector::{VectorConfig, VectorStore};
static VECTOR_STORE_REGISTRY: Lazy<Mutex<HashMap<String, VectorStore>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
fn canonicalize_vector_path(path: &str) -> Option<String> {
let path_buf = PathBuf::from(path);
if let Ok(canonical) = std::fs::canonicalize(&path_buf) {
return Some(canonical.to_string_lossy().to_string());
}
let parent = path_buf.parent().unwrap_or(Path::new("."));
let filename = path_buf.file_name()?;
let canonical_parent = if parent.as_os_str().is_empty() || parent == Path::new(".") {
std::env::current_dir().ok()?
} else {
std::fs::canonicalize(parent).ok()?
};
let canonical = canonical_parent.join(filename);
Some(canonical.to_string_lossy().to_string())
}
#[no_mangle]
pub extern "C" fn SYNA_vector_store_new(path: *const c_char, dimensions: u16, metric: i32) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let c_str = unsafe { CStr::from_ptr(path) };
let path_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let distance_metric = match metric {
0 => DistanceMetric::Cosine,
1 => DistanceMetric::Euclidean,
2 => DistanceMetric::DotProduct,
_ => DistanceMetric::Cosine, };
let config = VectorConfig {
dimensions,
metric: distance_metric,
..Default::default()
};
let canonical_path = match canonicalize_vector_path(path_str) {
Some(p) => p,
None => return ERR_INVALID_PATH,
};
match VectorStore::new(path_str, config) {
Ok(store) => {
let mut registry = VECTOR_STORE_REGISTRY.lock();
registry.insert(canonical_path, store);
ERR_SUCCESS
}
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_vector_store_new_with_config(
path: *const c_char,
dimensions: u16,
metric: i32,
sync_on_write: i32,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let c_str = unsafe { CStr::from_ptr(path) };
let path_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let distance_metric = match metric {
0 => DistanceMetric::Cosine,
1 => DistanceMetric::Euclidean,
2 => DistanceMetric::DotProduct,
_ => DistanceMetric::Cosine,
};
let config = VectorConfig {
dimensions,
metric: distance_metric,
sync_on_write: sync_on_write != 0,
..Default::default()
};
let canonical_path = match canonicalize_vector_path(path_str) {
Some(p) => p,
None => return ERR_INVALID_PATH,
};
match VectorStore::new(path_str, config) {
Ok(store) => {
let mut registry = VECTOR_STORE_REGISTRY.lock();
registry.insert(canonical_path, store);
ERR_SUCCESS
}
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_vector_store_insert(
path: *const c_char,
key: *const c_char,
data: *const f32,
dimensions: u16,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || (data.is_null() && dimensions > 0) {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let canonical_path = match canonicalize_vector_path(path_str) {
Some(p) => p,
None => return ERR_INVALID_PATH,
};
let vector = if dimensions == 0 {
Vec::new()
} else {
unsafe { std::slice::from_raw_parts(data, dimensions as usize) }.to_vec()
};
let mut registry = VECTOR_STORE_REGISTRY.lock();
match registry.get_mut(&canonical_path) {
Some(store) => match store.insert(key_str, &vector) {
Ok(_) => ERR_SUCCESS,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_vector_store_insert_batch(
path: *const c_char,
keys: *const *const c_char,
data: *const f32,
dimensions: u16,
count: usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || keys.is_null() || (data.is_null() && count > 0 && dimensions > 0) {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let canonical_path = match canonicalize_vector_path(path_str) {
Some(p) => p,
None => return ERR_INVALID_PATH,
};
let keys_slice = unsafe { std::slice::from_raw_parts(keys, count) };
let mut key_strings: Vec<&str> = Vec::with_capacity(count);
for key_ptr in keys_slice {
if key_ptr.is_null() {
return ERR_INVALID_PATH;
}
match unsafe { CStr::from_ptr(*key_ptr) }.to_str() {
Ok(s) => key_strings.push(s),
Err(_) => return ERR_INVALID_PATH,
}
}
let total_floats = count * dimensions as usize;
let data_slice = if total_floats == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(data, total_floats) }
};
let vectors: Vec<&[f32]> = data_slice.chunks(dimensions as usize).collect();
let mut registry = VECTOR_STORE_REGISTRY.lock();
match registry.get_mut(&canonical_path) {
Some(store) => match store.insert_batch(&key_strings, &vectors) {
Ok(n) => n as i32,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_vector_store_insert_batch_fast(
path: *const c_char,
keys: *const *const c_char,
data: *const f32,
dimensions: u16,
count: usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || keys.is_null() || (data.is_null() && count > 0 && dimensions > 0) {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let canonical_path = match canonicalize_vector_path(path_str) {
Some(p) => p,
None => return ERR_INVALID_PATH,
};
let keys_slice = unsafe { std::slice::from_raw_parts(keys, count) };
let mut key_strings: Vec<&str> = Vec::with_capacity(count);
for key_ptr in keys_slice {
if key_ptr.is_null() {
return ERR_INVALID_PATH;
}
match unsafe { CStr::from_ptr(*key_ptr) }.to_str() {
Ok(s) => key_strings.push(s),
Err(_) => return ERR_INVALID_PATH,
}
}
let total_floats = count * dimensions as usize;
let data_slice = if total_floats == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(data, total_floats) }
};
let vectors: Vec<&[f32]> = data_slice.chunks(dimensions as usize).collect();
let mut registry = VECTOR_STORE_REGISTRY.lock();
match registry.get_mut(&canonical_path) {
Some(store) => match store.insert_batch_fast(&key_strings, &vectors, false) {
Ok(n) => n as i32,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_vector_store_search(
path: *const c_char,
query: *const f32,
dimensions: u16,
k: usize,
out_json: *mut *mut c_char,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || query.is_null() || out_json.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let canonical_path = match canonicalize_vector_path(path_str) {
Some(p) => p,
None => return ERR_INVALID_PATH,
};
let query_vec: Vec<f32> =
unsafe { std::slice::from_raw_parts(query, dimensions as usize) }.to_vec();
let mut registry = VECTOR_STORE_REGISTRY.lock();
match registry.get_mut(&canonical_path) {
Some(store) => match store.search(&query_vec, k) {
Ok(results) => {
let json_results: Vec<serde_json::Value> = results
.iter()
.map(|r| {
serde_json::json!({
"key": r.key,
"score": r.score,
"vector": r.vector
})
})
.collect();
let json_str =
serde_json::to_string(&json_results).unwrap_or_else(|_| "[]".to_string());
let result_count = results.len() as i32;
match CString::new(json_str) {
Ok(c_string) => {
unsafe { *out_json = c_string.into_raw() };
result_count
}
Err(_) => ERR_GENERIC,
}
}
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_vector_store_build_index(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let canonical_path = match canonicalize_vector_path(path_str) {
Some(p) => p,
None => return ERR_INVALID_PATH,
};
let mut registry = VECTOR_STORE_REGISTRY.lock();
match registry.get_mut(&canonical_path) {
Some(store) => match store.build_index() {
Ok(()) => crate::error::ERR_SUCCESS,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_vector_store_has_index(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let canonical_path = match canonicalize_vector_path(path_str) {
Some(p) => p,
None => return ERR_INVALID_PATH,
};
let registry = VECTOR_STORE_REGISTRY.lock();
match registry.get(&canonical_path) {
Some(store) => {
if store.has_index() {
1
} else {
0
}
}
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_vector_store_close(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let canonical_path = match std::fs::canonicalize(path_str) {
Ok(p) => p.to_string_lossy().to_string(),
Err(_) => path_str.to_string(),
};
let mut registry = VECTOR_STORE_REGISTRY.lock();
match registry.remove(&canonical_path) {
Some(_store) => {
ERR_SUCCESS
}
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_vector_store_flush(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let canonical_path = match std::fs::canonicalize(path_str) {
Ok(p) => p.to_string_lossy().to_string(),
Err(_) => path_str.to_string(),
};
let mut registry = VECTOR_STORE_REGISTRY.lock();
match registry.get_mut(&canonical_path) {
Some(store) => match store.flush() {
Ok(_) => ERR_SUCCESS,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_free_json(json: *mut c_char) {
std::panic::catch_unwind(|| {
if json.is_null() {
return;
}
unsafe {
let _ = CString::from_raw(json);
}
})
.ok(); }
use crate::model_registry::{ModelRegistry, ModelStage};
static MODEL_REGISTRY: Lazy<Mutex<HashMap<String, ModelRegistry>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
#[no_mangle]
pub extern "C" fn SYNA_model_registry_open(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match ModelRegistry::new(path_str) {
Ok(registry) => {
let mut reg = MODEL_REGISTRY.lock();
reg.insert(path_str.to_string(), registry);
ERR_SUCCESS
}
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_model_save(
path: *const c_char,
name: *const c_char,
data: *const u8,
data_len: usize,
metadata_json: *const c_char,
out_version: *mut u32,
out_checksum: *mut *mut c_char,
out_checksum_len: *mut usize,
) -> i64 {
std::panic::catch_unwind(|| {
if path.is_null() || name.is_null() || (data.is_null() && data_len > 0) {
return ERR_INVALID_PATH as i64;
}
if out_version.is_null() || out_checksum.is_null() || out_checksum_len.is_null() {
return ERR_INVALID_PATH as i64;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let name_str = match unsafe { CStr::from_ptr(name) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let metadata: std::collections::HashMap<String, String> = if metadata_json.is_null() {
std::collections::HashMap::new()
} else {
match unsafe { CStr::from_ptr(metadata_json) }.to_str() {
Ok(json_str) => serde_json::from_str(json_str).unwrap_or_default(),
Err(_) => std::collections::HashMap::new(),
}
};
let model_data = if data_len == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(data, data_len) }
};
let mut reg = MODEL_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(registry) => {
match registry.save_model(name_str, model_data, metadata) {
Ok(version) => {
unsafe { *out_version = version.version };
let checksum_len = version.checksum.len();
unsafe { *out_checksum_len = checksum_len };
let mut bytes = version.checksum.into_bytes();
bytes.push(0);
let c_str = bytes.into_boxed_slice();
unsafe { *out_checksum = Box::into_raw(c_str) as *mut c_char };
ERR_SUCCESS as i64
}
Err(_) => ERR_GENERIC as i64,
}
}
None => crate::error::ERR_DB_NOT_FOUND as i64,
}
})
.unwrap_or(ERR_INTERNAL_PANIC as i64)
}
#[no_mangle]
pub extern "C" fn SYNA_model_load(
path: *const c_char,
name: *const c_char,
version: u32,
out_data: *mut *mut u8,
out_data_len: *mut usize,
out_meta_json: *mut *mut c_char,
out_meta_len: *mut usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || name.is_null() || out_data.is_null() || out_data_len.is_null() {
return ERR_INVALID_PATH;
}
if out_meta_json.is_null() || out_meta_len.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let name_str = match unsafe { CStr::from_ptr(name) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let version_opt = if version == 0 { None } else { Some(version) };
let mut reg = MODEL_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(registry) => {
match registry.load_model(name_str, version_opt) {
Ok((data, version_info)) => {
let data_len = data.len();
unsafe { *out_data_len = data_len };
if data_len > 0 {
let boxed = data.into_boxed_slice();
unsafe { *out_data = Box::into_raw(boxed) as *mut u8 };
} else {
unsafe { *out_data = std::ptr::null_mut() };
}
let meta_json = serde_json::to_string(&version_info)
.unwrap_or_else(|_| "{}".to_string());
let meta_len = meta_json.len();
unsafe { *out_meta_len = meta_len };
let mut bytes = meta_json.into_bytes();
bytes.push(0);
let c_str = bytes.into_boxed_slice();
unsafe { *out_meta_json = Box::into_raw(c_str) as *mut c_char };
ERR_SUCCESS
}
Err(crate::error::SynaError::ModelNotFound(_)) => ERR_KEY_NOT_FOUND,
Err(crate::error::SynaError::ChecksumMismatch { .. }) => ERR_GENERIC,
Err(_) => ERR_GENERIC,
}
}
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_model_list(
path: *const c_char,
name: *const c_char,
out_json: *mut *mut c_char,
out_len: *mut usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || name.is_null() || out_json.is_null() || out_len.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let name_str = match unsafe { CStr::from_ptr(name) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let mut reg = MODEL_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(registry) => match registry.list_versions(name_str) {
Ok(versions) => {
let count = versions.len() as i32;
let json =
serde_json::to_string(&versions).unwrap_or_else(|_| "[]".to_string());
unsafe { *out_len = json.len() };
match CString::new(json) {
Ok(c_string) => {
unsafe { *out_json = c_string.into_raw() };
count
}
Err(_) => ERR_GENERIC,
}
}
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_model_set_stage(
path: *const c_char,
name: *const c_char,
version: u32,
stage: i32,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || name.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let name_str = match unsafe { CStr::from_ptr(name) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let model_stage = match stage {
0 => ModelStage::Development,
1 => ModelStage::Staging,
2 => ModelStage::Production,
3 => ModelStage::Archived,
_ => ModelStage::Development,
};
let mut reg = MODEL_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(registry) => match registry.set_stage(name_str, version, model_stage) {
Ok(_) => ERR_SUCCESS,
Err(crate::error::SynaError::ModelNotFound(_)) => ERR_KEY_NOT_FOUND,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
use crate::experiment::{ExperimentTracker, RunStatus};
static EXPERIMENT_REGISTRY: Lazy<Mutex<HashMap<String, ExperimentTracker>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
#[no_mangle]
pub extern "C" fn SYNA_exp_tracker_open(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match ExperimentTracker::new(path_str) {
Ok(tracker) => {
let mut reg = EXPERIMENT_REGISTRY.lock();
reg.insert(path_str.to_string(), tracker);
ERR_SUCCESS
}
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_exp_start_run(
path: *const c_char,
experiment: *const c_char,
tags_json: *const c_char,
out_run_id: *mut *mut c_char,
out_run_id_len: *mut usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null()
|| experiment.is_null()
|| out_run_id.is_null()
|| out_run_id_len.is_null()
{
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let exp_str = match unsafe { CStr::from_ptr(experiment) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let tags: Vec<String> = if tags_json.is_null() {
Vec::new()
} else {
match unsafe { CStr::from_ptr(tags_json) }.to_str() {
Ok(json_str) => serde_json::from_str(json_str).unwrap_or_default(),
Err(_) => Vec::new(),
}
};
let mut reg = EXPERIMENT_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(tracker) => match tracker.start_run(exp_str, tags) {
Ok(run_id) => {
let run_id_len = run_id.len();
unsafe { *out_run_id_len = run_id_len };
let mut bytes = run_id.into_bytes();
bytes.push(0);
let c_str = bytes.into_boxed_slice();
unsafe { *out_run_id = Box::into_raw(c_str) as *mut c_char };
ERR_SUCCESS
}
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_exp_log_param(
path: *const c_char,
run_id: *const c_char,
key: *const c_char,
value: *const c_char,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || run_id.is_null() || key.is_null() || value.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let run_id_str = match unsafe { CStr::from_ptr(run_id) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let value_str = match unsafe { CStr::from_ptr(value) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let mut reg = EXPERIMENT_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(tracker) => match tracker.log_param(run_id_str, key_str, value_str) {
Ok(_) => ERR_SUCCESS,
Err(crate::error::SynaError::RunNotFound(_)) => ERR_KEY_NOT_FOUND,
Err(crate::error::SynaError::RunAlreadyEnded(_)) => ERR_GENERIC,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_exp_log_metric(
path: *const c_char,
run_id: *const c_char,
key: *const c_char,
value: f64,
step: i64,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || run_id.is_null() || key.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let run_id_str = match unsafe { CStr::from_ptr(run_id) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let step_opt = if step < 0 { None } else { Some(step as u64) };
let mut reg = EXPERIMENT_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(tracker) => match tracker.log_metric(run_id_str, key_str, value, step_opt) {
Ok(_) => ERR_SUCCESS,
Err(crate::error::SynaError::RunNotFound(_)) => ERR_KEY_NOT_FOUND,
Err(crate::error::SynaError::RunAlreadyEnded(_)) => ERR_GENERIC,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_exp_log_artifact(
path: *const c_char,
run_id: *const c_char,
name: *const c_char,
data: *const u8,
data_len: usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || run_id.is_null() || name.is_null() {
return ERR_INVALID_PATH;
}
if data.is_null() && data_len > 0 {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let run_id_str = match unsafe { CStr::from_ptr(run_id) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let name_str = match unsafe { CStr::from_ptr(name) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let artifact_data = if data_len == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(data, data_len) }
};
let mut reg = EXPERIMENT_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(tracker) => match tracker.log_artifact(run_id_str, name_str, artifact_data) {
Ok(_) => ERR_SUCCESS,
Err(crate::error::SynaError::RunNotFound(_)) => ERR_KEY_NOT_FOUND,
Err(crate::error::SynaError::RunAlreadyEnded(_)) => ERR_GENERIC,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_exp_end_run(path: *const c_char, run_id: *const c_char, status: i32) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || run_id.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let run_id_str = match unsafe { CStr::from_ptr(run_id) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let run_status = match status {
0 => RunStatus::Running,
1 => RunStatus::Completed,
2 => RunStatus::Failed,
3 => RunStatus::Killed,
_ => RunStatus::Completed,
};
let mut reg = EXPERIMENT_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(tracker) => match tracker.end_run(run_id_str, run_status) {
Ok(_) => ERR_SUCCESS,
Err(crate::error::SynaError::RunNotFound(_)) => ERR_KEY_NOT_FOUND,
Err(crate::error::SynaError::RunAlreadyEnded(_)) => ERR_GENERIC,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
use crate::mmap_vector::{MmapVectorConfig, MmapVectorStore};
static MMAP_VECTOR_REGISTRY: Lazy<Mutex<HashMap<String, MmapVectorStore>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
#[no_mangle]
pub extern "C" fn SYNA_mmap_vector_store_new(
path: *const c_char,
dimensions: u16,
metric: i32,
initial_capacity: usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let distance_metric = match metric {
0 => crate::distance::DistanceMetric::Cosine,
1 => crate::distance::DistanceMetric::Euclidean,
2 => crate::distance::DistanceMetric::DotProduct,
_ => crate::distance::DistanceMetric::Cosine,
};
let config = MmapVectorConfig {
dimensions,
metric: distance_metric,
initial_capacity,
..Default::default()
};
match MmapVectorStore::new(path_str, config) {
Ok(store) => {
let mut reg = MMAP_VECTOR_REGISTRY.lock();
reg.insert(path_str.to_string(), store);
ERR_SUCCESS
}
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_mmap_vector_store_insert(
path: *const c_char,
key: *const c_char,
vector: *const f32,
dimensions: u16,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || vector.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let vec_slice = unsafe { std::slice::from_raw_parts(vector, dimensions as usize) };
let mut reg = MMAP_VECTOR_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(store) => match store.insert(key_str, vec_slice) {
Ok(_) => ERR_SUCCESS,
Err(crate::error::SynaError::DimensionMismatch { .. }) => ERR_TYPE_MISMATCH,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_mmap_vector_store_insert_batch(
path: *const c_char,
keys: *const *const c_char,
vectors: *const f32,
dimensions: u16,
count: usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || keys.is_null() || vectors.is_null() || count == 0 {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_ptrs = unsafe { std::slice::from_raw_parts(keys, count) };
let mut key_strings: Vec<String> = Vec::with_capacity(count);
for &key_ptr in key_ptrs {
if key_ptr.is_null() {
return ERR_INVALID_PATH;
}
match unsafe { CStr::from_ptr(key_ptr) }.to_str() {
Ok(s) => key_strings.push(s.to_string()),
Err(_) => return ERR_INVALID_PATH,
}
}
let key_refs: Vec<&str> = key_strings.iter().map(|s| s.as_str()).collect();
let dims = dimensions as usize;
let all_vectors = unsafe { std::slice::from_raw_parts(vectors, count * dims) };
let vec_refs: Vec<&[f32]> = (0..count)
.map(|i| &all_vectors[i * dims..(i + 1) * dims])
.collect();
let mut reg = MMAP_VECTOR_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(store) => match store.insert_batch(&key_refs, &vec_refs) {
Ok(inserted) => inserted as i32,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_mmap_vector_store_search(
path: *const c_char,
query: *const f32,
dimensions: u16,
k: usize,
out_json: *mut *mut c_char,
out_len: *mut usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || query.is_null() || out_json.is_null() || out_len.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let query_slice = unsafe { std::slice::from_raw_parts(query, dimensions as usize) };
let reg = MMAP_VECTOR_REGISTRY.lock();
match reg.get(path_str) {
Some(store) => match store.search(query_slice, k) {
Ok(results) => {
let count = results.len() as i32;
let json_results: Vec<serde_json::Value> = results
.iter()
.map(|r| {
serde_json::json!({
"key": r.key,
"score": r.score,
})
})
.collect();
let json =
serde_json::to_string(&json_results).unwrap_or_else(|_| "[]".to_string());
unsafe { *out_len = json.len() };
match CString::new(json) {
Ok(c_string) => {
unsafe { *out_json = c_string.into_raw() };
count
}
Err(_) => ERR_GENERIC,
}
}
Err(crate::error::SynaError::DimensionMismatch { .. }) => ERR_TYPE_MISMATCH,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_mmap_vector_store_build_index(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let mut reg = MMAP_VECTOR_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(store) => match store.build_index() {
Ok(_) => ERR_SUCCESS,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_mmap_vector_store_flush(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let mut reg = MMAP_VECTOR_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(store) => match store.flush() {
Ok(_) => ERR_SUCCESS,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_mmap_vector_store_close(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let mut reg = MMAP_VECTOR_REGISTRY.lock();
match reg.remove(path_str) {
Some(_store) => {
ERR_SUCCESS
}
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_mmap_vector_store_len(path: *const c_char) -> i64 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH as i64;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let reg = MMAP_VECTOR_REGISTRY.lock();
match reg.get(path_str) {
Some(store) => store.len() as i64,
None => crate::error::ERR_DB_NOT_FOUND as i64,
}
})
.unwrap_or(ERR_INTERNAL_PANIC as i64)
}
use crate::gwi::{GravityWellIndex, GwiConfig};
static GWI_REGISTRY: Lazy<Mutex<HashMap<String, GravityWellIndex>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
#[no_mangle]
pub extern "C" fn SYNA_gwi_new(
path: *const c_char,
dimensions: u16,
branching_factor: u16,
num_levels: u8,
initial_capacity: usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let config = GwiConfig {
dimensions,
branching_factor: if branching_factor == 0 {
16
} else {
branching_factor
},
num_levels: if num_levels == 0 { 3 } else { num_levels },
initial_capacity: if initial_capacity == 0 {
10_000
} else {
initial_capacity
},
..Default::default()
};
match GravityWellIndex::new(path_str, config) {
Ok(index) => {
let mut reg = GWI_REGISTRY.lock();
reg.insert(path_str.to_string(), index);
ERR_SUCCESS
}
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_gwi_open(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
match GravityWellIndex::open(path_str) {
Ok(index) => {
let mut reg = GWI_REGISTRY.lock();
reg.insert(path_str.to_string(), index);
ERR_SUCCESS
}
Err(_) => ERR_INVALID_PATH,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_gwi_initialize(
path: *const c_char,
vectors: *const f32,
num_vectors: usize,
dimensions: u16,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || vectors.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let total_floats = num_vectors * dimensions as usize;
let data_slice = unsafe { std::slice::from_raw_parts(vectors, total_floats) };
let sample_vectors: Vec<&[f32]> = data_slice.chunks(dimensions as usize).collect();
let mut reg = GWI_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(index) => match index.initialize_attractors(&sample_vectors) {
Ok(()) => ERR_SUCCESS,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_gwi_insert(
path: *const c_char,
key: *const c_char,
vector: *const f32,
dimensions: u16,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || vector.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let vector_slice = unsafe { std::slice::from_raw_parts(vector, dimensions as usize) };
let mut reg = GWI_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(index) => match index.insert(key_str, vector_slice) {
Ok(()) => ERR_SUCCESS,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_gwi_insert_batch(
path: *const c_char,
keys: *const *const c_char,
vectors: *const f32,
dimensions: u16,
count: usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || keys.is_null() || (vectors.is_null() && count > 0) {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let keys_slice = unsafe { std::slice::from_raw_parts(keys, count) };
let mut key_strings: Vec<&str> = Vec::with_capacity(count);
for key_ptr in keys_slice {
if key_ptr.is_null() {
return ERR_INVALID_PATH;
}
match unsafe { CStr::from_ptr(*key_ptr) }.to_str() {
Ok(s) => key_strings.push(s),
Err(_) => return ERR_INVALID_PATH,
}
}
let total_floats = count * dimensions as usize;
let data_slice = unsafe { std::slice::from_raw_parts(vectors, total_floats) };
let vector_refs: Vec<&[f32]> = data_slice.chunks(dimensions as usize).collect();
let mut reg = GWI_REGISTRY.lock();
match reg.get_mut(path_str) {
Some(index) => match index.insert_batch(&key_strings, &vector_refs) {
Ok(n) => n as i32,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_gwi_search(
path: *const c_char,
query: *const f32,
dimensions: u16,
k: usize,
out_json: *mut *mut c_char,
) -> i32 {
SYNA_gwi_search_nprobe(path, query, dimensions, k, 3, out_json)
}
#[no_mangle]
pub extern "C" fn SYNA_gwi_search_nprobe(
path: *const c_char,
query: *const f32,
dimensions: u16,
k: usize,
nprobe: usize,
out_json: *mut *mut c_char,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || query.is_null() || out_json.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let query_slice = unsafe { std::slice::from_raw_parts(query, dimensions as usize) };
let reg = GWI_REGISTRY.lock();
match reg.get(path_str) {
Some(index) => match index.search_with_nprobe(query_slice, k, nprobe) {
Ok(results) => {
let json_results: Vec<serde_json::Value> = results
.iter()
.map(|r| {
serde_json::json!({
"key": r.key,
"score": r.score,
})
})
.collect();
let json_str = serde_json::to_string(&json_results).unwrap_or_default();
let c_str = CString::new(json_str).unwrap_or_default();
unsafe {
*out_json = c_str.into_raw();
}
results.len() as i32
}
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_gwi_flush(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let reg = GWI_REGISTRY.lock();
match reg.get(path_str) {
Some(index) => match index.flush() {
Ok(()) => ERR_SUCCESS,
Err(_) => ERR_GENERIC,
},
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_gwi_close(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let mut reg = GWI_REGISTRY.lock();
match reg.remove(path_str) {
Some(mut index) => {
let _ = index.close();
ERR_SUCCESS
}
None => crate::error::ERR_DB_NOT_FOUND,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_gwi_len(path: *const c_char) -> i64 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH as i64;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH as i64,
};
let reg = GWI_REGISTRY.lock();
match reg.get(path_str) {
Some(index) => index.len() as i64,
None => crate::error::ERR_DB_NOT_FOUND as i64,
}
})
.unwrap_or(ERR_INTERNAL_PANIC as i64)
}
use crate::cascade::{CascadeConfig, CascadeIndex};
static CASCADE_REGISTRY: Lazy<Mutex<HashMap<String, CascadeIndex>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
#[no_mangle]
pub extern "C" fn SYNA_cascade_new(path: *const c_char, dimensions: u16) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let c_str = unsafe { CStr::from_ptr(path) };
let path_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let config = CascadeConfig {
dimensions,
..Default::default()
};
match CascadeIndex::new(path_str, config) {
Ok(index) => {
let mut registry = CASCADE_REGISTRY.lock();
registry.insert(path_str.to_string(), index);
ERR_SUCCESS
}
Err(_) => ERR_GENERIC,
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_cascade_insert(
path: *const c_char,
key: *const c_char,
vector: *const f32,
dimensions: u16,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || key.is_null() || vector.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let key_str = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let vec_slice = unsafe { std::slice::from_raw_parts(vector, dimensions as usize) };
let mut registry = CASCADE_REGISTRY.lock();
if let Some(index) = registry.get_mut(path_str) {
match index.insert(key_str, vec_slice) {
Ok(_) => ERR_SUCCESS,
Err(_) => ERR_GENERIC,
}
} else {
ERR_GENERIC
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_cascade_insert_batch(
path: *const c_char,
keys: *const *const c_char,
vectors: *const f32,
dimensions: u16,
count: usize,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || keys.is_null() || vectors.is_null() || count == 0 {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let dims = dimensions as usize;
let mut registry = CASCADE_REGISTRY.lock();
if let Some(index) = registry.get_mut(path_str) {
for i in 0..count {
let key_ptr = unsafe { *keys.add(i) };
if key_ptr.is_null() {
continue;
}
let key_str = match unsafe { CStr::from_ptr(key_ptr) }.to_str() {
Ok(s) => s,
Err(_) => continue,
};
let vec_start = i * dims;
let vec_slice = unsafe { std::slice::from_raw_parts(vectors.add(vec_start), dims) };
if index.insert(key_str, vec_slice).is_err() {
return ERR_GENERIC;
}
}
ERR_SUCCESS
} else {
ERR_GENERIC
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_cascade_search(
path: *const c_char,
query: *const f32,
dimensions: u16,
k: usize,
out_json: *mut *mut c_char,
) -> i32 {
SYNA_cascade_search_params(path, query, dimensions, k, 16, 80, out_json)
}
#[no_mangle]
pub extern "C" fn SYNA_cascade_search_params(
path: *const c_char,
query: *const f32,
dimensions: u16,
k: usize,
num_probes: usize,
ef_search: usize,
out_json: *mut *mut c_char,
) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() || query.is_null() || out_json.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let query_slice = unsafe { std::slice::from_raw_parts(query, dimensions as usize) };
let registry = CASCADE_REGISTRY.lock();
if let Some(index) = registry.get(path_str) {
match index.search_with_params(query_slice, k, num_probes, ef_search) {
Ok(results) => {
let json_results: Vec<serde_json::Value> = results
.iter()
.map(|r| {
serde_json::json!({
"key": r.key,
"score": r.score
})
})
.collect();
let json_str = serde_json::to_string(&json_results).unwrap_or_default();
let c_string = std::ffi::CString::new(json_str).unwrap_or_default();
unsafe {
*out_json = c_string.into_raw();
}
ERR_SUCCESS
}
Err(_) => ERR_GENERIC,
}
} else {
ERR_GENERIC
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_cascade_flush(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let registry = CASCADE_REGISTRY.lock();
if let Some(index) = registry.get(path_str) {
match index.flush() {
Ok(_) => ERR_SUCCESS,
Err(_) => ERR_GENERIC,
}
} else {
ERR_GENERIC
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_cascade_close(path: *const c_char) -> i32 {
std::panic::catch_unwind(|| {
if path.is_null() {
return ERR_INVALID_PATH;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return ERR_INVALID_PATH,
};
let mut registry = CASCADE_REGISTRY.lock();
if registry.remove(path_str).is_some() {
ERR_SUCCESS
} else {
ERR_GENERIC
}
})
.unwrap_or(ERR_INTERNAL_PANIC)
}
#[no_mangle]
pub extern "C" fn SYNA_cascade_len(path: *const c_char) -> i64 {
std::panic::catch_unwind(|| {
if path.is_null() {
return -1;
}
let path_str = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let registry = CASCADE_REGISTRY.lock();
if let Some(index) = registry.get(path_str) {
index.len() as i64
} else {
-1
}
})
.unwrap_or(-1)
}