#![allow(clippy::not_unsafe_ptr_arg_deref)]
use std::collections::BTreeMap;
use std::ffi::{CStr, CString, c_char, c_float, c_int, c_uchar, c_ulonglong};
use std::ptr;
use std::sync::{Arc, Mutex, OnceLock};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use tokio::runtime::Runtime;
use vldb_controller_client::{
ClientRegistration, ControllerClient, ControllerClientConfig, ControllerLanceDbColumnDef,
ControllerLanceDbColumnType, ControllerLanceDbCreateTableResult, ControllerLanceDbDeleteResult,
ControllerLanceDbDropTableResult, ControllerLanceDbEnableRequest, ControllerLanceDbInputFormat,
ControllerLanceDbOutputFormat, ControllerLanceDbSearchResult, ControllerLanceDbUpsertResult,
ControllerProcessMode, ControllerSqliteCustomWordEntry,
ControllerSqliteDictionaryMutationResult, ControllerSqliteEnableRequest,
ControllerSqliteEnsureFtsIndexResult, ControllerSqliteExecuteBatchResult,
ControllerSqliteExecuteResult, ControllerSqliteFtsMutationResult,
ControllerSqliteListCustomWordsResult, ControllerSqliteQueryResult,
ControllerSqliteRebuildFtsIndexResult, ControllerSqliteSearchFtsHit,
ControllerSqliteSearchFtsResult, ControllerSqliteTokenizeResult, ControllerSqliteTokenizerMode,
ControllerSqliteValue, ControllerStatusSnapshot, SpaceBackendStatus, SpaceKind,
SpaceRegistration, SpaceSnapshot,
};
const FFI_STATUS_OK: c_int = 0;
const FFI_STATUS_ERR: c_int = 1;
static FFI_CLIENT_HANDLE_REGISTRY: OnceLock<
Mutex<BTreeMap<usize, Arc<FfiControllerClientHandleState>>>,
> = OnceLock::new();
pub struct FfiControllerClientHandle {
_private: u8,
}
struct FfiControllerClientHandleInner {
runtime: Runtime,
client: ControllerClient,
}
struct FfiControllerClientHandleState {
inner: Mutex<Option<FfiControllerClientHandleInner>>,
}
#[repr(C)]
pub struct FfiControllerClientConfig {
pub endpoint: *const c_char,
pub auto_spawn: c_uchar,
pub spawn_executable: *const c_char,
pub spawn_process_mode: c_int,
pub minimum_uptime_secs: c_ulonglong,
pub idle_timeout_secs: c_ulonglong,
pub default_lease_ttl_secs: c_ulonglong,
pub connect_timeout_secs: c_ulonglong,
pub startup_timeout_secs: c_ulonglong,
pub startup_retry_interval_ms: c_ulonglong,
pub lease_renew_interval_secs: c_ulonglong,
}
#[repr(C)]
pub struct FfiClientRegistration {
pub client_name: *const c_char,
pub host_kind: *const c_char,
pub process_id: u32,
pub process_name: *const c_char,
pub lease_ttl_secs: c_ulonglong,
}
#[repr(C)]
pub struct FfiSpaceRegistration {
pub space_id: *const c_char,
pub space_label: *const c_char,
pub space_kind: c_int,
pub space_root: *const c_char,
}
#[repr(C)]
pub struct FfiControllerSqliteEnableRequest {
pub space_id: *const c_char,
pub db_path: *const c_char,
pub connection_pool_size: c_ulonglong,
pub busy_timeout_ms: c_ulonglong,
pub journal_mode: *const c_char,
pub synchronous: *const c_char,
pub foreign_keys: c_uchar,
pub temp_store: *const c_char,
pub wal_autocheckpoint_pages: u32,
pub cache_size_kib: i64,
pub mmap_size_bytes: c_ulonglong,
pub enforce_db_file_lock: c_uchar,
pub read_only: c_uchar,
pub allow_uri_filenames: c_uchar,
pub trusted_schema: c_uchar,
pub defensive: c_uchar,
}
#[repr(C)]
pub struct FfiControllerLanceDbEnableRequest {
pub space_id: *const c_char,
pub default_db_path: *const c_char,
pub db_root: *const c_char,
pub read_consistency_interval_ms: c_ulonglong,
pub max_upsert_payload: c_ulonglong,
pub max_search_limit: c_ulonglong,
pub max_concurrent_requests: c_ulonglong,
}
#[repr(C)]
pub struct FfiSpaceBackendStatus {
pub enabled: c_uchar,
pub mode: *mut c_char,
pub target: *mut c_char,
}
#[repr(C)]
pub struct FfiSpaceSnapshot {
pub space_id: *mut c_char,
pub space_label: *mut c_char,
pub space_kind: c_int,
pub space_root: *mut c_char,
pub attached_clients: c_ulonglong,
pub sqlite: *mut FfiSpaceBackendStatus,
pub lancedb: *mut FfiSpaceBackendStatus,
}
#[repr(C)]
pub struct FfiSpaceSnapshotArray {
pub items: *mut FfiSpaceSnapshot,
pub len: usize,
}
#[repr(C)]
pub struct FfiControllerStatusSnapshot {
pub process_mode: c_int,
pub bind_addr: *mut c_char,
pub started_at_unix_ms: c_ulonglong,
pub last_request_at_unix_ms: c_ulonglong,
pub minimum_uptime_secs: c_ulonglong,
pub idle_timeout_secs: c_ulonglong,
pub default_lease_ttl_secs: c_ulonglong,
pub active_clients: c_ulonglong,
pub attached_spaces: c_ulonglong,
pub inflight_requests: c_ulonglong,
pub shutdown_candidate: c_uchar,
}
#[repr(C)]
pub struct FfiControllerSqliteExecuteResult {
pub success: c_uchar,
pub message: *mut c_char,
pub rows_changed: i64,
pub last_insert_rowid: i64,
}
#[repr(C)]
pub struct FfiControllerSqliteQueryResult {
pub json_data: *mut c_char,
pub row_count: c_ulonglong,
}
#[repr(C)]
pub struct FfiByteBuffer {
pub data: *mut u8,
pub len: usize,
}
#[repr(C)]
pub struct FfiByteBufferArray {
pub items: *mut FfiByteBuffer,
pub len: usize,
}
#[repr(C)]
pub struct FfiStringArray {
pub items: *const *const c_char,
pub len: usize,
}
#[repr(C)]
pub struct FfiSqliteValue {
pub kind: c_int,
pub int64_value: i64,
pub float64_value: f64,
pub string_value: *const c_char,
pub bytes_value: *const u8,
pub bytes_len: usize,
pub bool_value: c_uchar,
}
#[repr(C)]
pub struct FfiSqliteBatchItem {
pub params: *const FfiSqliteValue,
pub params_len: usize,
}
#[repr(C)]
pub struct FfiControllerSqliteExecuteBatchResult {
pub success: c_uchar,
pub message: *mut c_char,
pub rows_changed: i64,
pub last_insert_rowid: i64,
pub statements_executed: i64,
}
#[repr(C)]
pub struct FfiControllerSqliteQueryStreamResult {
pub chunks: *mut FfiByteBufferArray,
pub row_count: c_ulonglong,
pub chunk_count: c_ulonglong,
pub total_bytes: c_ulonglong,
}
#[repr(C)]
pub struct FfiControllerSqliteTokenizeResult {
pub tokenizer_mode: *mut c_char,
pub normalized_text: *mut c_char,
pub tokens_json: *mut c_char,
pub fts_query: *mut c_char,
}
#[repr(C)]
pub struct FfiControllerSqliteCustomWordEntry {
pub word: *mut c_char,
pub weight: c_ulonglong,
}
#[repr(C)]
pub struct FfiControllerSqliteCustomWordArray {
pub items: *mut FfiControllerSqliteCustomWordEntry,
pub len: usize,
}
#[repr(C)]
pub struct FfiControllerSqliteDictionaryMutationResult {
pub success: c_uchar,
pub message: *mut c_char,
pub affected_rows: c_ulonglong,
}
#[repr(C)]
pub struct FfiControllerSqliteListCustomWordsResult {
pub success: c_uchar,
pub message: *mut c_char,
pub words: *mut FfiControllerSqliteCustomWordArray,
}
#[repr(C)]
pub struct FfiControllerSqliteEnsureFtsIndexResult {
pub success: c_uchar,
pub message: *mut c_char,
pub index_name: *mut c_char,
pub tokenizer_mode: *mut c_char,
}
#[repr(C)]
pub struct FfiControllerSqliteRebuildFtsIndexResult {
pub success: c_uchar,
pub message: *mut c_char,
pub index_name: *mut c_char,
pub tokenizer_mode: *mut c_char,
pub reindexed_rows: c_ulonglong,
}
#[repr(C)]
pub struct FfiControllerSqliteFtsMutationResult {
pub success: c_uchar,
pub message: *mut c_char,
pub affected_rows: c_ulonglong,
pub index_name: *mut c_char,
}
#[repr(C)]
pub struct FfiControllerSqliteSearchFtsHit {
pub id: *mut c_char,
pub file_path: *mut c_char,
pub title: *mut c_char,
pub title_highlight: *mut c_char,
pub content_snippet: *mut c_char,
pub score: f64,
pub rank: c_ulonglong,
pub raw_score: f64,
}
#[repr(C)]
pub struct FfiControllerSqliteSearchFtsHitArray {
pub items: *mut FfiControllerSqliteSearchFtsHit,
pub len: usize,
}
#[repr(C)]
pub struct FfiControllerSqliteSearchFtsResult {
pub success: c_uchar,
pub message: *mut c_char,
pub index_name: *mut c_char,
pub tokenizer_mode: *mut c_char,
pub normalized_query: *mut c_char,
pub fts_query: *mut c_char,
pub source: *mut c_char,
pub query_mode: *mut c_char,
pub total: c_ulonglong,
pub hits: *mut FfiControllerSqliteSearchFtsHitArray,
}
#[repr(C)]
pub struct FfiControllerLanceDbCreateTableResult {
pub message: *mut c_char,
}
#[repr(C)]
pub struct FfiControllerLanceDbUpsertResult {
pub message: *mut c_char,
pub version: c_ulonglong,
pub input_rows: c_ulonglong,
pub inserted_rows: c_ulonglong,
pub updated_rows: c_ulonglong,
pub deleted_rows: c_ulonglong,
}
#[repr(C)]
pub struct FfiControllerLanceDbSearchResult {
pub message: *mut c_char,
pub format: *mut c_char,
pub rows: c_ulonglong,
pub data: *mut u8,
pub data_len: usize,
}
#[repr(C)]
pub struct FfiControllerLanceDbDeleteResult {
pub message: *mut c_char,
pub version: c_ulonglong,
pub deleted_rows: c_ulonglong,
}
#[repr(C)]
pub struct FfiControllerLanceDbDropTableResult {
pub message: *mut c_char,
}
#[repr(C)]
pub struct FfiControllerLanceDbColumnDef {
pub name: *const c_char,
pub column_type: c_int,
pub vector_dim: u32,
pub nullable: c_uchar,
}
#[derive(Deserialize)]
struct CreateClientJsonRequest {
config: ControllerClientConfig,
registration: ClientRegistration,
}
#[derive(Default, Deserialize)]
struct EmptyJsonRequest {}
#[derive(Deserialize)]
struct AttachSpaceJsonRequest {
registration: SpaceRegistration,
}
#[derive(Deserialize)]
struct DetachSpaceJsonRequest {
space_id: String,
}
#[derive(Deserialize)]
struct EnableSqliteJsonRequest {
request: ControllerSqliteEnableRequest,
}
#[derive(Deserialize)]
struct DisableBackendJsonRequest {
space_id: String,
}
#[derive(Deserialize)]
struct ExecuteSqliteScriptJsonRequest {
space_id: String,
sql: String,
#[serde(default)]
params: Vec<JsonValue>,
}
#[derive(Deserialize)]
struct QuerySqliteJsonJsonRequest {
space_id: String,
sql: String,
#[serde(default)]
params: Vec<JsonValue>,
}
#[derive(Deserialize)]
struct ExecuteSqliteBatchJsonRequest {
space_id: String,
sql: String,
#[serde(default)]
batch_params: Vec<Vec<JsonValue>>,
}
#[derive(Deserialize)]
struct QuerySqliteStreamJsonRequest {
space_id: String,
sql: String,
#[serde(default)]
params: Vec<JsonValue>,
target_chunk_size: Option<u64>,
}
#[derive(Deserialize)]
struct TokenizeSqliteTextJsonRequest {
space_id: String,
tokenizer_mode: String,
text: String,
search_mode: bool,
}
#[derive(Deserialize)]
struct ListSqliteCustomWordsJsonRequest {
space_id: String,
}
#[derive(Deserialize)]
struct UpsertSqliteCustomWordJsonRequest {
space_id: String,
word: String,
weight: u32,
}
#[derive(Deserialize)]
struct RemoveSqliteCustomWordJsonRequest {
space_id: String,
word: String,
}
#[derive(Deserialize)]
struct EnsureSqliteFtsIndexJsonRequest {
space_id: String,
index_name: String,
tokenizer_mode: String,
}
#[derive(Deserialize)]
struct RebuildSqliteFtsIndexJsonRequest {
space_id: String,
index_name: String,
tokenizer_mode: String,
}
#[derive(Deserialize)]
struct UpsertSqliteFtsDocumentJsonRequest {
space_id: String,
index_name: String,
tokenizer_mode: String,
id: String,
file_path: String,
title: String,
content: String,
}
#[derive(Deserialize)]
struct DeleteSqliteFtsDocumentJsonRequest {
space_id: String,
index_name: String,
id: String,
}
#[derive(Deserialize)]
struct SearchSqliteFtsJsonRequest {
space_id: String,
index_name: String,
tokenizer_mode: String,
query: String,
limit: u32,
offset: u32,
}
#[derive(Deserialize)]
struct EnableLanceDbJsonRequest {
request: ControllerLanceDbEnableRequest,
}
#[derive(Deserialize)]
struct CreateLanceDbTableJsonRequest {
space_id: String,
table_name: String,
columns: Vec<CreateLanceDbTableJsonColumn>,
#[serde(default)]
overwrite_if_exists: bool,
}
#[derive(Deserialize, Serialize)]
struct CreateLanceDbTableJsonColumn {
name: String,
column_type: String,
#[serde(default)]
vector_dim: u32,
#[serde(default = "default_nullable")]
nullable: bool,
}
#[derive(Deserialize)]
struct UpsertLanceDbJsonRequest {
space_id: String,
table_name: String,
input_format: String,
#[serde(default)]
key_columns: Vec<String>,
data_base64: String,
}
#[derive(Deserialize)]
struct SearchLanceDbJsonRequest {
space_id: String,
table_name: String,
vector: Vec<f32>,
#[serde(default = "default_search_limit")]
limit: u32,
#[serde(default)]
filter: String,
#[serde(default)]
vector_column: String,
#[serde(default)]
output_format: String,
}
#[derive(Deserialize)]
struct DeleteLanceDbJsonRequest {
space_id: String,
table_name: String,
condition: String,
}
#[derive(Deserialize)]
struct JsonSqliteBytesValue {
#[serde(default)]
r#type: String,
#[serde(default)]
__type: String,
base64: String,
}
#[derive(Serialize)]
struct CreateLanceDbTableJsonCompatRequest {
table_name: String,
columns: Vec<CreateLanceDbTableJsonColumn>,
overwrite_if_exists: bool,
}
impl From<CreateLanceDbTableJsonRequest> for CreateLanceDbTableJsonCompatRequest {
fn from(value: CreateLanceDbTableJsonRequest) -> Self {
Self {
table_name: value.table_name,
columns: value.columns,
overwrite_if_exists: value.overwrite_if_exists,
}
}
}
#[derive(Serialize)]
struct UpsertLanceDbJsonCompatRequest {
table_name: String,
input_format: String,
key_columns: Vec<String>,
}
impl From<&UpsertLanceDbJsonRequest> for UpsertLanceDbJsonCompatRequest {
fn from(value: &UpsertLanceDbJsonRequest) -> Self {
Self {
table_name: value.table_name.clone(),
input_format: value.input_format.clone(),
key_columns: value.key_columns.clone(),
}
}
}
#[derive(Serialize)]
struct SearchLanceDbJsonCompatRequest {
table_name: String,
vector: Vec<f32>,
limit: u32,
filter: String,
vector_column: String,
output_format: String,
}
impl From<&SearchLanceDbJsonRequest> for SearchLanceDbJsonCompatRequest {
fn from(value: &SearchLanceDbJsonRequest) -> Self {
Self {
table_name: value.table_name.clone(),
vector: value.vector.clone(),
limit: value.limit,
filter: value.filter.clone(),
vector_column: value.vector_column.clone(),
output_format: value.output_format.clone(),
}
}
}
#[derive(Serialize)]
struct DeleteLanceDbJsonCompatRequest {
table_name: String,
condition: String,
}
impl From<&DeleteLanceDbJsonRequest> for DeleteLanceDbJsonCompatRequest {
fn from(value: &DeleteLanceDbJsonRequest) -> Self {
Self {
table_name: value.table_name.clone(),
condition: value.condition.clone(),
}
}
}
#[derive(Deserialize)]
struct DropLanceDbTableJsonRequest {
space_id: String,
table_name: String,
}
#[derive(Serialize)]
struct SuccessJsonResponse {
ok: bool,
}
#[derive(Serialize)]
struct QuerySqliteStreamJsonResponse {
chunks_base64: Vec<String>,
row_count: u64,
chunk_count: u64,
total_bytes: u64,
}
struct ControllerSqliteQueryStreamCompatResult {
chunks: Vec<Vec<u8>>,
row_count: u64,
chunk_count: u64,
total_bytes: u64,
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_version() -> *mut c_char {
string_into_raw(env!("CARGO_PKG_VERSION").to_string())
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_string_free(value: *mut c_char) {
if !value.is_null() {
unsafe {
drop(CString::from_raw(value));
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_bytes_free(data: *mut u8, len: usize) {
if !data.is_null() {
unsafe {
drop(Vec::from_raw_parts(data, len, len));
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_create(
config: *const FfiControllerClientConfig,
registration: *const FfiClientRegistration,
client_out: *mut *mut FfiControllerClientHandle,
error_out: *mut *mut c_char,
) -> c_int {
if client_out.is_null() {
return ffi_error_status(error_out, "client_out pointer must not be null");
}
clear_out_ptr(client_out);
match native_client_create(config, registration) {
Ok(handle) => {
write_out_ptr(client_out, handle);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_create_json(
request_json: *const c_char,
client_out: *mut *mut FfiControllerClientHandle,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
if client_out.is_null() {
return ffi_error_status(error_out, "client_out pointer must not be null");
}
clear_out_ptr(client_out);
clear_out_ptr(response_out);
let result = (|| -> Result<(*mut FfiControllerClientHandle, String), String> {
let request: CreateClientJsonRequest = parse_json_input(request_json)?;
let handle = build_client_handle(request.config, request.registration)?;
let response = serde_json::to_string(&SuccessJsonResponse { ok: true })
.map_err(|error| format!("failed to serialize create_client_json response: {error}"))?;
Ok((handle, response))
})();
match result {
Ok((handle, response)) => {
write_out_ptr(client_out, handle);
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_free(client: *mut FfiControllerClientHandle) {
let Ok(handle_id) = ffi_client_handle_id_from_ptr(client) else {
return;
};
let state = {
let mut registry = match ffi_client_handle_registry().lock() {
Ok(registry) => registry,
Err(poisoned) => poisoned.into_inner(),
};
registry.remove(&handle_id)
};
if let Some(state) = state {
let mut guard = match state.inner.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
if let Some(inner) = guard.take() {
let _ = inner.runtime.block_on(inner.client.shutdown());
}
unsafe {
drop(Box::from_raw(client));
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_connect(
client: *mut FfiControllerClientHandle,
error_out: *mut *mut c_char,
) -> c_int {
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.connect())
.map_err(error_to_string)
});
match result {
Ok(()) => ffi_ok_status(error_out),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_connect_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let _: EmptyJsonRequest = parse_json_input(request_json)?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.connect())
.map_err(error_to_string)?;
serde_json::to_string(&SuccessJsonResponse { ok: true })
.map_err(|error| format!("failed to serialize connect_json response: {error}"))
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_shutdown(
client: *mut FfiControllerClientHandle,
error_out: *mut *mut c_char,
) -> c_int {
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.shutdown())
.map_err(error_to_string)
});
match result {
Ok(()) => ffi_ok_status(error_out),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_shutdown_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let _: EmptyJsonRequest = parse_json_input(request_json)?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.shutdown())
.map_err(error_to_string)?;
serde_json::to_string(&SuccessJsonResponse { ok: true })
.map_err(|error| format!("failed to serialize shutdown_json response: {error}"))
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_get_status(
client: *mut FfiControllerClientHandle,
status_out: *mut *mut FfiControllerStatusSnapshot,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(status_out);
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.get_status())
.map_err(error_to_string)
});
match result {
Ok(snapshot) => {
write_boxed_out_ptr(status_out, map_status_snapshot(snapshot));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_get_status_json(
client: *mut FfiControllerClientHandle,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = with_client_handle(client, |handle| {
let snapshot = handle
.runtime
.block_on(handle.client.get_status())
.map_err(error_to_string)?;
serde_json::to_string(&snapshot)
.map_err(|error| format!("failed to serialize get_status_json response: {error}"))
});
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_controller_status_free(
status: *mut FfiControllerStatusSnapshot,
) {
if !status.is_null() {
unsafe {
let status = Box::from_raw(status);
vldb_controller_ffi_string_free(status.bind_addr);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_attach_space(
client: *mut FfiControllerClientHandle,
registration: *const FfiSpaceRegistration,
space_out: *mut *mut FfiSpaceSnapshot,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(space_out);
let result = (|| -> Result<SpaceSnapshot, String> {
let registration = ffi_space_registration_to_rust(registration)?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.attach_space(registration))
.map_err(error_to_string)
})
})();
match result {
Ok(snapshot) => {
write_boxed_out_ptr(space_out, map_space_snapshot(snapshot));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_attach_space_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: AttachSpaceJsonRequest = parse_json_input(request_json)?;
let snapshot = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.attach_space(request.registration))
.map_err(error_to_string)
})?;
serde_json::to_string(&snapshot)
.map_err(|error| format!("failed to serialize attach_space_json response: {error}"))
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_detach_space(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
detached_out: *mut c_uchar,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_u8(detached_out);
let result = (|| -> Result<bool, String> {
let space_id = required_c_string(space_id, "space_id")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.detach_space(space_id))
.map_err(error_to_string)
})
})();
match result {
Ok(detached) => {
write_out_u8(detached_out, if detached { 1 } else { 0 });
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_detach_space_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: DetachSpaceJsonRequest = parse_json_input(request_json)?;
let detached = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.detach_space(request.space_id))
.map_err(error_to_string)
})?;
serde_json::to_string(&detached)
.map_err(|error| format!("failed to serialize detach_space_json response: {error}"))
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_list_spaces(
client: *mut FfiControllerClientHandle,
spaces_out: *mut *mut FfiSpaceSnapshotArray,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(spaces_out);
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.list_spaces())
.map_err(error_to_string)
});
match result {
Ok(spaces) => {
let mapped = map_space_snapshot_array(spaces);
write_boxed_out_ptr(spaces_out, mapped);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_list_spaces_json(
client: *mut FfiControllerClientHandle,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = with_client_handle(client, |handle| {
let spaces = handle
.runtime
.block_on(handle.client.list_spaces())
.map_err(error_to_string)?;
serde_json::to_string(&spaces)
.map_err(|error| format!("failed to serialize list_spaces_json response: {error}"))
});
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_space_snapshot_array_free(
spaces: *mut FfiSpaceSnapshotArray,
) {
if spaces.is_null() {
return;
}
unsafe {
let spaces = Box::from_raw(spaces);
if !spaces.items.is_null() {
let items = Vec::from_raw_parts(spaces.items, spaces.len, spaces.len);
for item in items {
free_space_snapshot_fields(item);
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_space_snapshot_free(space: *mut FfiSpaceSnapshot) {
if space.is_null() {
return;
}
unsafe {
let space = Box::from_raw(space);
free_space_snapshot_fields(*space);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_enable_sqlite(
client: *mut FfiControllerClientHandle,
request: *const FfiControllerSqliteEnableRequest,
error_out: *mut *mut c_char,
) -> c_int {
let result = (|| -> Result<(), String> {
let request = ffi_sqlite_enable_request_to_rust(request)?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.enable_sqlite(request))
.map_err(error_to_string)
})
})();
match result {
Ok(()) => ffi_ok_status(error_out),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_enable_sqlite_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: EnableSqliteJsonRequest = parse_json_input(request_json)?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.enable_sqlite(request.request))
.map_err(error_to_string)
})?;
serde_json::to_string(&SuccessJsonResponse { ok: true })
.map_err(|error| format!("failed to serialize enable_sqlite_json response: {error}"))
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_disable_sqlite(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
disabled_out: *mut c_uchar,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_u8(disabled_out);
let result = (|| -> Result<bool, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.disable_sqlite(space_id, binding_id))
.map_err(error_to_string)
})
})();
match result {
Ok(disabled) => {
write_out_u8(disabled_out, if disabled { 1 } else { 0 });
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_disable_sqlite_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: DisableBackendJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let disabled = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.disable_sqlite(request.space_id, binding_id))
.map_err(error_to_string)
})?;
serde_json::to_string(&disabled)
.map_err(|error| format!("failed to serialize disable_sqlite_json response: {error}"))
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_execute_sqlite_script(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
sql: *const c_char,
params: *const FfiSqliteValue,
params_len: usize,
result_out: *mut *mut FfiControllerSqliteExecuteResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteExecuteResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let sql = required_c_string(sql, "sql")?;
let params = read_sqlite_values(params, params_len, "params")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(
handle
.client
.execute_sqlite_script_typed(space_id, binding_id, sql, params),
)
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_execute_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_execute_sqlite_script_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: ExecuteSqliteScriptJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
let params_json = serde_json::to_string(&request.params).map_err(|error| {
format!("failed to serialize execute_sqlite_script_json params: {error}")
})?;
handle
.runtime
.block_on(handle.client.execute_sqlite_script(
request.space_id,
binding_id,
request.sql,
params_json,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize execute_sqlite_script_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_execute_result_free(
result: *mut FfiControllerSqliteExecuteResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_query_sqlite_json(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
sql: *const c_char,
params: *const FfiSqliteValue,
params_len: usize,
result_out: *mut *mut FfiControllerSqliteQueryResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteQueryResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let sql = required_c_string(sql, "sql")?;
let params = read_sqlite_values(params, params_len, "params")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(
handle
.client
.query_sqlite_json_typed(space_id, binding_id, sql, params),
)
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_query_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_query_sqlite_json_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: QuerySqliteJsonJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
let params_json = serde_json::to_string(&request.params).map_err(|error| {
format!("failed to serialize query_sqlite_json_json params: {error}")
})?;
handle
.runtime
.block_on(handle.client.query_sqlite_json(
request.space_id,
binding_id,
request.sql,
params_json,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize query_sqlite_json_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_query_result_free(
result: *mut FfiControllerSqliteQueryResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.json_data);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_execute_sqlite_batch(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
sql: *const c_char,
items: *const FfiSqliteBatchItem,
items_len: usize,
result_out: *mut *mut FfiControllerSqliteExecuteBatchResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteExecuteBatchResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let sql = required_c_string(sql, "sql")?;
let items = read_sqlite_batch_items(items, items_len, "items")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(
handle
.client
.execute_sqlite_batch_typed(space_id, binding_id, sql, items),
)
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_execute_batch_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_execute_sqlite_batch_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: ExecuteSqliteBatchJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
let batch_params_json = request
.batch_params
.iter()
.map(|item| {
serde_json::to_string(item).map_err(|error| {
format!(
"failed to serialize execute_sqlite_batch_json batch_params item: {error}"
)
})
})
.collect::<Result<Vec<_>, _>>()?;
handle
.runtime
.block_on(handle.client.execute_sqlite_batch(
request.space_id,
binding_id,
request.sql,
batch_params_json,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize execute_sqlite_batch_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_execute_batch_result_free(
result: *mut FfiControllerSqliteExecuteBatchResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_query_sqlite_stream(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
sql: *const c_char,
params: *const FfiSqliteValue,
params_len: usize,
target_chunk_size: c_ulonglong,
result_out: *mut *mut FfiControllerSqliteQueryStreamResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteQueryStreamCompatResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let sql = required_c_string(sql, "sql")?;
let params = read_sqlite_values(params, params_len, "params")?;
let chunk_size = if target_chunk_size == 0 {
None
} else {
Some(target_chunk_size)
};
with_client_handle(client, |handle| {
collect_sqlite_query_stream_result(
&handle.runtime,
&handle.client,
space_id,
binding_id,
sql,
params,
chunk_size,
)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_query_stream_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_query_sqlite_stream_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: QuerySqliteStreamJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
let params = request
.params
.iter()
.cloned()
.map(json_to_sqlite_value)
.collect::<Result<Vec<_>, _>>()?;
collect_sqlite_query_stream_result(
&handle.runtime,
&handle.client,
request.space_id,
binding_id,
request.sql,
params,
request.target_chunk_size,
)
})?;
serde_json::to_string(&QuerySqliteStreamJsonResponse {
chunks_base64: result
.chunks
.iter()
.map(|chunk| encode_base64(chunk))
.collect(),
row_count: result.row_count,
chunk_count: result.chunk_count,
total_bytes: result.total_bytes,
})
.map_err(|error| format!("failed to serialize query_sqlite_stream_json response: {error}"))
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_query_stream_result_free(
result: *mut FfiControllerSqliteQueryStreamResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_byte_buffer_array_free(result.chunks);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_byte_buffer_array_free(value: *mut FfiByteBufferArray) {
if value.is_null() {
return;
}
unsafe {
let value = Box::from_raw(value);
if !value.items.is_null() {
let items = Vec::from_raw_parts(value.items, value.len, value.len);
for item in items {
vldb_controller_ffi_bytes_free(item.data, item.len);
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_tokenize_sqlite_text(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
tokenizer_mode: *const c_char,
text: *const c_char,
search_mode: c_uchar,
result_out: *mut *mut FfiControllerSqliteTokenizeResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteTokenizeResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let tokenizer_mode = parse_sqlite_tokenizer_mode_text(tokenizer_mode)?;
let text = required_c_string_preserve(text, "text")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.tokenize_sqlite_text(
space_id,
binding_id,
tokenizer_mode,
text,
search_mode != 0,
))
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_tokenize_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_tokenize_sqlite_text_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: TokenizeSqliteTextJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let tokenizer_mode = parse_sqlite_tokenizer_mode_name(&request.tokenizer_mode)?;
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.tokenize_sqlite_text(
request.space_id,
binding_id,
tokenizer_mode,
request.text,
request.search_mode,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize tokenize_sqlite_text_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_tokenize_result_free(
result: *mut FfiControllerSqliteTokenizeResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.tokenizer_mode);
vldb_controller_ffi_string_free(result.normalized_text);
vldb_controller_ffi_string_free(result.tokens_json);
vldb_controller_ffi_string_free(result.fts_query);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_list_sqlite_custom_words(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
result_out: *mut *mut FfiControllerSqliteListCustomWordsResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteListCustomWordsResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.list_sqlite_custom_words(space_id, binding_id))
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_list_custom_words_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_list_sqlite_custom_words_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: ListSqliteCustomWordsJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(
handle
.client
.list_sqlite_custom_words(request.space_id, binding_id),
)
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize list_sqlite_custom_words_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_custom_word_array_free(
value: *mut FfiControllerSqliteCustomWordArray,
) {
if value.is_null() {
return;
}
unsafe {
let value = Box::from_raw(value);
if !value.items.is_null() {
let items = Vec::from_raw_parts(value.items, value.len, value.len);
for item in items {
vldb_controller_ffi_string_free(item.word);
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_list_custom_words_result_free(
result: *mut FfiControllerSqliteListCustomWordsResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
vldb_controller_ffi_sqlite_custom_word_array_free(result.words);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_upsert_sqlite_custom_word(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
word: *const c_char,
weight: u32,
result_out: *mut *mut FfiControllerSqliteDictionaryMutationResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteDictionaryMutationResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let word = required_c_string(word, "word")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(
handle
.client
.upsert_sqlite_custom_word(space_id, binding_id, word, weight),
)
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_dictionary_mutation_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_upsert_sqlite_custom_word_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: UpsertSqliteCustomWordJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.upsert_sqlite_custom_word(
request.space_id,
binding_id,
request.word,
request.weight,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize upsert_sqlite_custom_word_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_remove_sqlite_custom_word(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
word: *const c_char,
result_out: *mut *mut FfiControllerSqliteDictionaryMutationResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteDictionaryMutationResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let word = required_c_string(word, "word")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(
handle
.client
.remove_sqlite_custom_word(space_id, binding_id, word),
)
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_dictionary_mutation_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_remove_sqlite_custom_word_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: RemoveSqliteCustomWordJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.remove_sqlite_custom_word(
request.space_id,
binding_id,
request.word,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize remove_sqlite_custom_word_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_dictionary_mutation_result_free(
result: *mut FfiControllerSqliteDictionaryMutationResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_ensure_sqlite_fts_index(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
index_name: *const c_char,
tokenizer_mode: *const c_char,
result_out: *mut *mut FfiControllerSqliteEnsureFtsIndexResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteEnsureFtsIndexResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let index_name = required_c_string(index_name, "index_name")?;
let tokenizer_mode = parse_sqlite_tokenizer_mode_text(tokenizer_mode)?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.ensure_sqlite_fts_index(
space_id,
binding_id,
index_name,
tokenizer_mode,
))
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_ensure_fts_index_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_ensure_sqlite_fts_index_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: EnsureSqliteFtsIndexJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let tokenizer_mode = parse_sqlite_tokenizer_mode_name(&request.tokenizer_mode)?;
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.ensure_sqlite_fts_index(
request.space_id,
binding_id,
request.index_name,
tokenizer_mode,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize ensure_sqlite_fts_index_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_ensure_fts_index_result_free(
result: *mut FfiControllerSqliteEnsureFtsIndexResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
vldb_controller_ffi_string_free(result.index_name);
vldb_controller_ffi_string_free(result.tokenizer_mode);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_rebuild_sqlite_fts_index(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
index_name: *const c_char,
tokenizer_mode: *const c_char,
result_out: *mut *mut FfiControllerSqliteRebuildFtsIndexResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteRebuildFtsIndexResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let index_name = required_c_string(index_name, "index_name")?;
let tokenizer_mode = parse_sqlite_tokenizer_mode_text(tokenizer_mode)?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.rebuild_sqlite_fts_index(
space_id,
binding_id,
index_name,
tokenizer_mode,
))
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_rebuild_fts_index_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_rebuild_sqlite_fts_index_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: RebuildSqliteFtsIndexJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let tokenizer_mode = parse_sqlite_tokenizer_mode_name(&request.tokenizer_mode)?;
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.rebuild_sqlite_fts_index(
request.space_id,
binding_id,
request.index_name,
tokenizer_mode,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize rebuild_sqlite_fts_index_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_rebuild_fts_index_result_free(
result: *mut FfiControllerSqliteRebuildFtsIndexResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
vldb_controller_ffi_string_free(result.index_name);
vldb_controller_ffi_string_free(result.tokenizer_mode);
}
}
}
#[unsafe(no_mangle)]
#[allow(clippy::too_many_arguments)]
pub extern "C" fn vldb_controller_ffi_client_upsert_sqlite_fts_document(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
index_name: *const c_char,
tokenizer_mode: *const c_char,
id: *const c_char,
file_path: *const c_char,
title: *const c_char,
content: *const c_char,
result_out: *mut *mut FfiControllerSqliteFtsMutationResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteFtsMutationResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let index_name = required_c_string(index_name, "index_name")?;
let tokenizer_mode = parse_sqlite_tokenizer_mode_text(tokenizer_mode)?;
let id = required_c_string(id, "id")?;
let file_path = required_c_string_preserve(file_path, "file_path")?;
let title = required_c_string_preserve(title, "title")?;
let content = required_c_string_preserve(content, "content")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.upsert_sqlite_fts_document(
space_id,
binding_id,
index_name,
tokenizer_mode,
id,
file_path,
title,
content,
))
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_fts_mutation_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_upsert_sqlite_fts_document_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: UpsertSqliteFtsDocumentJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let tokenizer_mode = parse_sqlite_tokenizer_mode_name(&request.tokenizer_mode)?;
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.upsert_sqlite_fts_document(
request.space_id,
binding_id,
request.index_name,
tokenizer_mode,
request.id,
request.file_path,
request.title,
request.content,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize upsert_sqlite_fts_document_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_delete_sqlite_fts_document(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
index_name: *const c_char,
id: *const c_char,
result_out: *mut *mut FfiControllerSqliteFtsMutationResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteFtsMutationResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let index_name = required_c_string(index_name, "index_name")?;
let id = required_c_string(id, "id")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(
handle
.client
.delete_sqlite_fts_document(space_id, binding_id, index_name, id),
)
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_fts_mutation_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_delete_sqlite_fts_document_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: DeleteSqliteFtsDocumentJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.delete_sqlite_fts_document(
request.space_id,
binding_id,
request.index_name,
request.id,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize delete_sqlite_fts_document_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_fts_mutation_result_free(
result: *mut FfiControllerSqliteFtsMutationResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
vldb_controller_ffi_string_free(result.index_name);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_search_sqlite_fts(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
index_name: *const c_char,
tokenizer_mode: *const c_char,
query: *const c_char,
limit: u32,
offset: u32,
result_out: *mut *mut FfiControllerSqliteSearchFtsResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerSqliteSearchFtsResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let index_name = required_c_string(index_name, "index_name")?;
let tokenizer_mode = parse_sqlite_tokenizer_mode_text(tokenizer_mode)?;
let query = required_c_string(query, "query")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.search_sqlite_fts(
space_id,
binding_id,
index_name,
tokenizer_mode,
query,
limit,
offset,
))
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_sqlite_search_fts_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_search_sqlite_fts_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: SearchSqliteFtsJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let tokenizer_mode = parse_sqlite_tokenizer_mode_name(&request.tokenizer_mode)?;
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.search_sqlite_fts(
request.space_id,
binding_id,
request.index_name,
tokenizer_mode,
request.query,
request.limit,
request.offset,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize search_sqlite_fts_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_search_fts_hit_array_free(
value: *mut FfiControllerSqliteSearchFtsHitArray,
) {
if value.is_null() {
return;
}
unsafe {
let value = Box::from_raw(value);
if !value.items.is_null() {
let items = Vec::from_raw_parts(value.items, value.len, value.len);
for item in items {
vldb_controller_ffi_string_free(item.id);
vldb_controller_ffi_string_free(item.file_path);
vldb_controller_ffi_string_free(item.title);
vldb_controller_ffi_string_free(item.title_highlight);
vldb_controller_ffi_string_free(item.content_snippet);
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_sqlite_search_fts_result_free(
result: *mut FfiControllerSqliteSearchFtsResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
vldb_controller_ffi_string_free(result.index_name);
vldb_controller_ffi_string_free(result.tokenizer_mode);
vldb_controller_ffi_string_free(result.normalized_query);
vldb_controller_ffi_string_free(result.fts_query);
vldb_controller_ffi_string_free(result.source);
vldb_controller_ffi_string_free(result.query_mode);
vldb_controller_ffi_sqlite_search_fts_hit_array_free(result.hits);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_enable_lancedb(
client: *mut FfiControllerClientHandle,
request: *const FfiControllerLanceDbEnableRequest,
error_out: *mut *mut c_char,
) -> c_int {
let result = (|| -> Result<(), String> {
let request = ffi_lancedb_enable_request_to_rust(request)?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.enable_lancedb(request))
.map_err(error_to_string)
})
})();
match result {
Ok(()) => ffi_ok_status(error_out),
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_enable_lancedb_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: EnableLanceDbJsonRequest = parse_json_input(request_json)?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.enable_lancedb(request.request))
.map_err(error_to_string)
})?;
serde_json::to_string(&SuccessJsonResponse { ok: true })
.map_err(|error| format!("failed to serialize enable_lancedb_json response: {error}"))
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_disable_lancedb(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
disabled_out: *mut c_uchar,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_u8(disabled_out);
let result = (|| -> Result<bool, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.disable_lancedb(space_id, binding_id))
.map_err(error_to_string)
})
})();
match result {
Ok(disabled) => {
write_out_u8(disabled_out, if disabled { 1 } else { 0 });
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_disable_lancedb_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: DisableBackendJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let disabled = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.disable_lancedb(request.space_id, binding_id))
.map_err(error_to_string)
})?;
serde_json::to_string(&disabled)
.map_err(|error| format!("failed to serialize disable_lancedb_json response: {error}"))
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_create_lancedb_table(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
table_name: *const c_char,
columns: *const FfiControllerLanceDbColumnDef,
columns_len: usize,
overwrite_if_exists: c_uchar,
result_out: *mut *mut FfiControllerLanceDbCreateTableResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerLanceDbCreateTableResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let table_name = required_c_string(table_name, "table_name")?;
let columns = read_lancedb_columns(columns, columns_len, "columns")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.create_lancedb_table_typed(
space_id,
binding_id,
table_name,
columns,
overwrite_if_exists != 0,
))
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_lancedb_create_table_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_create_lancedb_table_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: CreateLanceDbTableJsonRequest = parse_json_input(request_json)?;
let space_id = request.space_id.clone();
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
let inner_request_json =
serde_json::to_string(&CreateLanceDbTableJsonCompatRequest::from(request))
.map_err(|error| {
format!("failed to serialize create_lancedb_table_json request: {error}")
})?;
handle
.runtime
.block_on(handle.client.create_lancedb_table(
space_id,
binding_id,
inner_request_json,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize create_lancedb_table_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_lancedb_create_table_result_free(
result: *mut FfiControllerLanceDbCreateTableResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_upsert_lancedb(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
table_name: *const c_char,
input_format: c_int,
data: *const u8,
data_len: usize,
key_columns: *const *const c_char,
key_columns_len: usize,
result_out: *mut *mut FfiControllerLanceDbUpsertResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerLanceDbUpsertResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let table_name = required_c_string(table_name, "table_name")?;
let input_format = map_lancedb_input_format_native(input_format)?;
let data = required_bytes(data, data_len, "data")?.to_vec();
let key_columns = optional_string_array(key_columns, key_columns_len, "key_columns")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.upsert_lancedb_typed(
space_id,
binding_id,
table_name,
input_format,
data,
key_columns,
))
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_lancedb_upsert_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_upsert_lancedb_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: UpsertLanceDbJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let data = decode_base64(&request.data_base64)?;
let result = with_client_handle(client, |handle| {
let inner_request_json = serde_json::to_string(&UpsertLanceDbJsonCompatRequest::from(
&request,
))
.map_err(|error| format!("failed to serialize upsert_lancedb_json request: {error}"))?;
handle
.runtime
.block_on(handle.client.upsert_lancedb(
request.space_id,
binding_id,
inner_request_json,
data,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result)
.map_err(|error| format!("failed to serialize upsert_lancedb_json response: {error}"))
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_lancedb_upsert_result_free(
result: *mut FfiControllerLanceDbUpsertResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_search_lancedb(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
table_name: *const c_char,
vector: *const c_float,
vector_len: usize,
limit: u32,
filter: *const c_char,
vector_column: *const c_char,
output_format: c_int,
result_out: *mut *mut FfiControllerLanceDbSearchResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerLanceDbSearchResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let table_name = required_c_string(table_name, "table_name")?;
let vector = required_f32_slice(vector, vector_len, "vector")?.to_vec();
let filter = optional_c_string(filter)?.unwrap_or_default();
let vector_column = optional_c_string(vector_column)?.unwrap_or_default();
let output_format = map_lancedb_output_format_native(output_format)?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.search_lancedb_typed(
space_id,
binding_id,
table_name,
vector,
limit,
filter,
vector_column,
output_format,
))
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_lancedb_search_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_search_lancedb_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: SearchLanceDbJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
let inner_request_json = serde_json::to_string(&SearchLanceDbJsonCompatRequest::from(
&request,
))
.map_err(|error| format!("failed to serialize search_lancedb_json request: {error}"))?;
handle
.runtime
.block_on(handle.client.search_lancedb(
request.space_id,
binding_id,
inner_request_json,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&SearchLanceDbJsonResponse::from(result))
.map_err(|error| format!("failed to serialize search_lancedb_json response: {error}"))
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_lancedb_search_result_free(
result: *mut FfiControllerLanceDbSearchResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
vldb_controller_ffi_string_free(result.format);
vldb_controller_ffi_bytes_free(result.data, result.data_len);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_delete_lancedb(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
table_name: *const c_char,
condition: *const c_char,
result_out: *mut *mut FfiControllerLanceDbDeleteResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerLanceDbDeleteResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let table_name = required_c_string(table_name, "table_name")?;
let condition = required_c_string(condition, "condition")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(
handle
.client
.delete_lancedb_typed(space_id, binding_id, table_name, condition),
)
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_lancedb_delete_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_delete_lancedb_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: DeleteLanceDbJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
let inner_request_json = serde_json::to_string(&DeleteLanceDbJsonCompatRequest::from(
&request,
))
.map_err(|error| format!("failed to serialize delete_lancedb_json request: {error}"))?;
handle
.runtime
.block_on(handle.client.delete_lancedb(
request.space_id,
binding_id,
inner_request_json,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result)
.map_err(|error| format!("failed to serialize delete_lancedb_json response: {error}"))
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_lancedb_delete_result_free(
result: *mut FfiControllerLanceDbDeleteResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_drop_lancedb_table(
client: *mut FfiControllerClientHandle,
space_id: *const c_char,
table_name: *const c_char,
result_out: *mut *mut FfiControllerLanceDbDropTableResult,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(result_out);
let result = (|| -> Result<ControllerLanceDbDropTableResult, String> {
let space_id = required_c_string(space_id, "space_id")?;
let binding_id = space_id.clone();
let table_name = required_c_string(table_name, "table_name")?;
with_client_handle(client, |handle| {
handle
.runtime
.block_on(
handle
.client
.drop_lancedb_table(space_id, binding_id, table_name),
)
.map_err(error_to_string)
})
})();
match result {
Ok(result) => {
write_boxed_out_ptr(result_out, map_lancedb_drop_table_result(result));
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_client_drop_lancedb_table_json(
client: *mut FfiControllerClientHandle,
request_json: *const c_char,
response_out: *mut *mut c_char,
error_out: *mut *mut c_char,
) -> c_int {
clear_out_ptr(response_out);
let result = (|| -> Result<String, String> {
let request: DropLanceDbTableJsonRequest = parse_json_input(request_json)?;
let binding_id = request.space_id.clone();
let result = with_client_handle(client, |handle| {
handle
.runtime
.block_on(handle.client.drop_lancedb_table(
request.space_id,
binding_id,
request.table_name,
))
.map_err(error_to_string)
})?;
serde_json::to_string(&result).map_err(|error| {
format!("failed to serialize drop_lancedb_table_json response: {error}")
})
})();
match result {
Ok(response) => {
write_string_out(response_out, response);
ffi_ok_status(error_out)
}
Err(error) => ffi_error_status(error_out, error),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn vldb_controller_ffi_lancedb_drop_table_result_free(
result: *mut FfiControllerLanceDbDropTableResult,
) {
if !result.is_null() {
unsafe {
let result = Box::from_raw(result);
vldb_controller_ffi_string_free(result.message);
}
}
}
#[derive(Serialize)]
struct SearchLanceDbJsonResponse {
message: String,
format: String,
rows: u64,
data_base64: String,
}
impl From<ControllerLanceDbSearchResult> for SearchLanceDbJsonResponse {
fn from(value: ControllerLanceDbSearchResult) -> Self {
Self {
message: value.message,
format: value.format,
rows: value.rows,
data_base64: encode_base64(&value.data),
}
}
}
fn build_client_handle(
config: ControllerClientConfig,
registration: ClientRegistration,
) -> Result<*mut FfiControllerClientHandle, String> {
let runtime =
Runtime::new().map_err(|error| format!("failed to create tokio runtime: {error}"))?;
let client = ControllerClient::new(config, registration);
register_client_handle(FfiControllerClientHandleInner { runtime, client })
}
fn native_client_create(
config: *const FfiControllerClientConfig,
registration: *const FfiClientRegistration,
) -> Result<*mut FfiControllerClientHandle, String> {
let config = ffi_client_config_to_rust(config)?;
let registration = ffi_client_registration_to_rust(registration)?;
build_client_handle(config, registration)
}
fn ffi_client_handle_registry()
-> &'static Mutex<BTreeMap<usize, Arc<FfiControllerClientHandleState>>> {
FFI_CLIENT_HANDLE_REGISTRY.get_or_init(|| Mutex::new(BTreeMap::new()))
}
fn ffi_client_handle_id_from_ptr(client: *mut FfiControllerClientHandle) -> Result<usize, String> {
if client.is_null() {
return Err("client handle pointer must not be null".to_string());
}
Ok(client as usize)
}
fn register_client_handle(
inner: FfiControllerClientHandleInner,
) -> Result<*mut FfiControllerClientHandle, String> {
let state = Arc::new(FfiControllerClientHandleState {
inner: Mutex::new(Some(inner)),
});
let mut registry = ffi_client_handle_registry()
.lock()
.map_err(|poisoned| format!("ffi client handle registry lock poisoned: {poisoned}"))?;
let handle_ptr = Box::into_raw(Box::new(FfiControllerClientHandle { _private: 0 }));
let handle_id = handle_ptr as usize;
registry.insert(handle_id, state);
Ok(handle_ptr)
}
fn ffi_client_config_to_rust(
config: *const FfiControllerClientConfig,
) -> Result<ControllerClientConfig, String> {
if config.is_null() {
return Err("config pointer must not be null".to_string());
}
let config = unsafe { &*config };
let defaults = ControllerClientConfig::default();
Ok(ControllerClientConfig {
endpoint: optional_c_string(config.endpoint)?.unwrap_or(defaults.endpoint),
auto_spawn: config.auto_spawn != 0,
spawn_executable: optional_c_string(config.spawn_executable)?,
spawn_process_mode: map_process_mode_native(config.spawn_process_mode)?,
minimum_uptime_secs: default_u64(config.minimum_uptime_secs, defaults.minimum_uptime_secs),
idle_timeout_secs: default_u64(config.idle_timeout_secs, defaults.idle_timeout_secs),
default_lease_ttl_secs: default_u64(
config.default_lease_ttl_secs,
defaults.default_lease_ttl_secs,
),
connect_timeout_secs: default_u64(
config.connect_timeout_secs,
defaults.connect_timeout_secs,
),
startup_timeout_secs: default_u64(
config.startup_timeout_secs,
defaults.startup_timeout_secs,
),
startup_retry_interval_ms: default_u64(
config.startup_retry_interval_ms,
defaults.startup_retry_interval_ms,
),
lease_renew_interval_secs: default_u64(
config.lease_renew_interval_secs,
defaults.lease_renew_interval_secs,
),
})
}
fn ffi_client_registration_to_rust(
registration: *const FfiClientRegistration,
) -> Result<ClientRegistration, String> {
if registration.is_null() {
return Err("registration pointer must not be null".to_string());
}
let registration = unsafe { &*registration };
Ok(ClientRegistration {
client_name: required_c_string(registration.client_name, "client_name")?,
host_kind: required_c_string(registration.host_kind, "host_kind")?,
process_id: registration.process_id,
process_name: required_c_string(registration.process_name, "process_name")?,
lease_ttl_secs: optional_nonzero_u64(registration.lease_ttl_secs),
})
}
fn ffi_space_registration_to_rust(
registration: *const FfiSpaceRegistration,
) -> Result<SpaceRegistration, String> {
if registration.is_null() {
return Err("registration pointer must not be null".to_string());
}
let registration = unsafe { &*registration };
Ok(SpaceRegistration {
space_id: required_c_string(registration.space_id, "space_id")?,
space_label: required_c_string(registration.space_label, "space_label")?,
space_kind: map_space_kind_native(registration.space_kind)?,
space_root: required_c_string(registration.space_root, "space_root")?,
})
}
fn ffi_sqlite_enable_request_to_rust(
request: *const FfiControllerSqliteEnableRequest,
) -> Result<ControllerSqliteEnableRequest, String> {
if request.is_null() {
return Err("sqlite enable request pointer must not be null".to_string());
}
let request = unsafe { &*request };
let defaults = ControllerSqliteEnableRequest::default();
let space_id = required_c_string(request.space_id, "space_id")?;
Ok(ControllerSqliteEnableRequest {
space_id: space_id.clone(),
binding_id: space_id,
db_path: required_c_string(request.db_path, "db_path")?,
connection_pool_size: default_u64(
request.connection_pool_size,
defaults.connection_pool_size as u64,
) as usize,
busy_timeout_ms: default_u64(request.busy_timeout_ms, defaults.busy_timeout_ms),
journal_mode: optional_c_string(request.journal_mode)?.unwrap_or(defaults.journal_mode),
synchronous: optional_c_string(request.synchronous)?.unwrap_or(defaults.synchronous),
foreign_keys: request.foreign_keys != 0,
temp_store: optional_c_string(request.temp_store)?.unwrap_or(defaults.temp_store),
wal_autocheckpoint_pages: if request.wal_autocheckpoint_pages == 0 {
defaults.wal_autocheckpoint_pages
} else {
request.wal_autocheckpoint_pages
},
cache_size_kib: if request.cache_size_kib == 0 {
defaults.cache_size_kib
} else {
request.cache_size_kib
},
mmap_size_bytes: default_u64(request.mmap_size_bytes, defaults.mmap_size_bytes),
enforce_db_file_lock: request.enforce_db_file_lock != 0,
read_only: request.read_only != 0,
allow_uri_filenames: request.allow_uri_filenames != 0,
trusted_schema: request.trusted_schema != 0,
defensive: request.defensive != 0,
})
}
fn ffi_lancedb_enable_request_to_rust(
request: *const FfiControllerLanceDbEnableRequest,
) -> Result<ControllerLanceDbEnableRequest, String> {
if request.is_null() {
return Err("lancedb enable request pointer must not be null".to_string());
}
let request = unsafe { &*request };
let defaults = ControllerLanceDbEnableRequest::default();
let space_id = required_c_string(request.space_id, "space_id")?;
Ok(ControllerLanceDbEnableRequest {
space_id: space_id.clone(),
binding_id: space_id,
default_db_path: required_c_string(request.default_db_path, "default_db_path")?,
db_root: optional_c_string(request.db_root)?,
read_consistency_interval_ms: optional_nonzero_u64(request.read_consistency_interval_ms),
max_upsert_payload: default_u64(
request.max_upsert_payload,
defaults.max_upsert_payload as u64,
) as usize,
max_search_limit: default_u64(request.max_search_limit, defaults.max_search_limit as u64)
as usize,
max_concurrent_requests: default_u64(
request.max_concurrent_requests,
defaults.max_concurrent_requests as u64,
) as usize,
})
}
fn map_status_snapshot(snapshot: ControllerStatusSnapshot) -> FfiControllerStatusSnapshot {
FfiControllerStatusSnapshot {
process_mode: map_process_mode_to_native(snapshot.process_mode),
bind_addr: string_into_raw(snapshot.bind_addr),
started_at_unix_ms: snapshot.started_at_unix_ms,
last_request_at_unix_ms: snapshot.last_request_at_unix_ms,
minimum_uptime_secs: snapshot.minimum_uptime_secs,
idle_timeout_secs: snapshot.idle_timeout_secs,
default_lease_ttl_secs: snapshot.default_lease_ttl_secs,
active_clients: snapshot.active_clients as c_ulonglong,
attached_spaces: snapshot.attached_spaces as c_ulonglong,
inflight_requests: snapshot.inflight_requests as c_ulonglong,
shutdown_candidate: if snapshot.shutdown_candidate { 1 } else { 0 },
}
}
fn map_space_snapshot(snapshot: SpaceSnapshot) -> FfiSpaceSnapshot {
FfiSpaceSnapshot {
space_id: string_into_raw(snapshot.space_id),
space_label: string_into_raw(snapshot.space_label),
space_kind: map_space_kind_to_native(snapshot.space_kind),
space_root: string_into_raw(snapshot.space_root),
attached_clients: snapshot.attached_clients as c_ulonglong,
sqlite: map_backend_status_option(snapshot.sqlite),
lancedb: map_backend_status_option(snapshot.lancedb),
}
}
fn map_space_snapshot_array(spaces: Vec<SpaceSnapshot>) -> FfiSpaceSnapshotArray {
let mut mapped: Vec<FfiSpaceSnapshot> = spaces.into_iter().map(map_space_snapshot).collect();
let items = mapped.as_mut_ptr();
let len = mapped.len();
std::mem::forget(mapped);
FfiSpaceSnapshotArray { items, len }
}
fn map_backend_status_option(status: Option<SpaceBackendStatus>) -> *mut FfiSpaceBackendStatus {
status
.map(|status| {
Box::into_raw(Box::new(FfiSpaceBackendStatus {
enabled: if status.enabled { 1 } else { 0 },
mode: string_into_raw(status.mode),
target: string_into_raw(status.target),
}))
})
.unwrap_or(ptr::null_mut())
}
fn map_sqlite_execute_result(
result: ControllerSqliteExecuteResult,
) -> FfiControllerSqliteExecuteResult {
FfiControllerSqliteExecuteResult {
success: if result.success { 1 } else { 0 },
message: string_into_raw(result.message),
rows_changed: result.rows_changed,
last_insert_rowid: result.last_insert_rowid,
}
}
fn map_sqlite_query_result(result: ControllerSqliteQueryResult) -> FfiControllerSqliteQueryResult {
FfiControllerSqliteQueryResult {
json_data: string_into_raw(result.json_data),
row_count: result.row_count,
}
}
fn map_sqlite_execute_batch_result(
result: ControllerSqliteExecuteBatchResult,
) -> FfiControllerSqliteExecuteBatchResult {
FfiControllerSqliteExecuteBatchResult {
success: if result.success { 1 } else { 0 },
message: string_into_raw(result.message),
rows_changed: result.rows_changed,
last_insert_rowid: result.last_insert_rowid,
statements_executed: result.statements_executed,
}
}
fn map_sqlite_query_stream_result(
result: ControllerSqliteQueryStreamCompatResult,
) -> FfiControllerSqliteQueryStreamResult {
let chunks = map_byte_buffer_array(result.chunks);
FfiControllerSqliteQueryStreamResult {
chunks: Box::into_raw(Box::new(chunks)),
row_count: result.row_count,
chunk_count: result.chunk_count,
total_bytes: result.total_bytes,
}
}
fn collect_sqlite_query_stream_result(
runtime: &Runtime,
client: &ControllerClient,
space_id: String,
binding_id: String,
sql: String,
params: Vec<ControllerSqliteValue>,
chunk_size: Option<u64>,
) -> Result<ControllerSqliteQueryStreamCompatResult, String> {
let stream = runtime
.block_on(
client.open_sqlite_query_stream_typed(space_id, binding_id, sql, params, chunk_size),
)
.map_err(error_to_string)?;
let stream_id = stream.stream_id;
let collected = (|| -> Result<ControllerSqliteQueryStreamCompatResult, String> {
let metrics = runtime
.block_on(client.wait_sqlite_query_stream_metrics(stream_id))
.map_err(error_to_string)?;
let mut chunks = Vec::with_capacity(metrics.chunk_count as usize);
for index in 0..metrics.chunk_count {
let chunk = runtime
.block_on(client.read_sqlite_query_stream_chunk(stream_id, index))
.map_err(error_to_string)?;
chunks.push(chunk);
}
Ok(ControllerSqliteQueryStreamCompatResult {
chunks,
row_count: metrics.row_count,
chunk_count: metrics.chunk_count,
total_bytes: metrics.total_bytes,
})
})();
let _ = runtime.block_on(client.close_sqlite_query_stream(stream_id));
collected
}
fn map_sqlite_tokenize_result(
result: ControllerSqliteTokenizeResult,
) -> FfiControllerSqliteTokenizeResult {
FfiControllerSqliteTokenizeResult {
tokenizer_mode: string_into_raw(result.tokenizer_mode),
normalized_text: string_into_raw(result.normalized_text),
tokens_json: string_into_raw(
serde_json::to_string(&result.tokens).expect("token list serialization must not fail"),
),
fts_query: string_into_raw(result.fts_query),
}
}
fn map_sqlite_list_custom_words_result(
result: ControllerSqliteListCustomWordsResult,
) -> FfiControllerSqliteListCustomWordsResult {
FfiControllerSqliteListCustomWordsResult {
success: if result.success { 1 } else { 0 },
message: string_into_raw(result.message),
words: Box::into_raw(Box::new(map_custom_word_array(result.words))),
}
}
fn map_sqlite_dictionary_mutation_result(
result: ControllerSqliteDictionaryMutationResult,
) -> FfiControllerSqliteDictionaryMutationResult {
FfiControllerSqliteDictionaryMutationResult {
success: if result.success { 1 } else { 0 },
message: string_into_raw(result.message),
affected_rows: result.affected_rows,
}
}
fn map_sqlite_ensure_fts_index_result(
result: ControllerSqliteEnsureFtsIndexResult,
) -> FfiControllerSqliteEnsureFtsIndexResult {
FfiControllerSqliteEnsureFtsIndexResult {
success: if result.success { 1 } else { 0 },
message: string_into_raw(result.message),
index_name: string_into_raw(result.index_name),
tokenizer_mode: string_into_raw(result.tokenizer_mode),
}
}
fn map_sqlite_rebuild_fts_index_result(
result: ControllerSqliteRebuildFtsIndexResult,
) -> FfiControllerSqliteRebuildFtsIndexResult {
FfiControllerSqliteRebuildFtsIndexResult {
success: if result.success { 1 } else { 0 },
message: string_into_raw(result.message),
index_name: string_into_raw(result.index_name),
tokenizer_mode: string_into_raw(result.tokenizer_mode),
reindexed_rows: result.reindexed_rows,
}
}
fn map_sqlite_fts_mutation_result(
result: ControllerSqliteFtsMutationResult,
) -> FfiControllerSqliteFtsMutationResult {
FfiControllerSqliteFtsMutationResult {
success: if result.success { 1 } else { 0 },
message: string_into_raw(result.message),
affected_rows: result.affected_rows,
index_name: string_into_raw(result.index_name),
}
}
fn map_sqlite_search_fts_result(
result: ControllerSqliteSearchFtsResult,
) -> FfiControllerSqliteSearchFtsResult {
FfiControllerSqliteSearchFtsResult {
success: if result.success { 1 } else { 0 },
message: string_into_raw(result.message),
index_name: string_into_raw(result.index_name),
tokenizer_mode: string_into_raw(result.tokenizer_mode),
normalized_query: string_into_raw(result.normalized_query),
fts_query: string_into_raw(result.fts_query),
source: string_into_raw(result.source),
query_mode: string_into_raw(result.query_mode),
total: result.total,
hits: Box::into_raw(Box::new(map_search_fts_hit_array(result.hits))),
}
}
fn map_lancedb_create_table_result(
result: ControllerLanceDbCreateTableResult,
) -> FfiControllerLanceDbCreateTableResult {
FfiControllerLanceDbCreateTableResult {
message: string_into_raw(result.message),
}
}
fn map_lancedb_upsert_result(
result: ControllerLanceDbUpsertResult,
) -> FfiControllerLanceDbUpsertResult {
FfiControllerLanceDbUpsertResult {
message: string_into_raw(result.message),
version: result.version,
input_rows: result.input_rows,
inserted_rows: result.inserted_rows,
updated_rows: result.updated_rows,
deleted_rows: result.deleted_rows,
}
}
fn map_lancedb_search_result(
result: ControllerLanceDbSearchResult,
) -> FfiControllerLanceDbSearchResult {
let (data, data_len) = bytes_into_raw(result.data);
FfiControllerLanceDbSearchResult {
message: string_into_raw(result.message),
format: string_into_raw(result.format),
rows: result.rows,
data,
data_len,
}
}
fn map_lancedb_delete_result(
result: ControllerLanceDbDeleteResult,
) -> FfiControllerLanceDbDeleteResult {
FfiControllerLanceDbDeleteResult {
message: string_into_raw(result.message),
version: result.version,
deleted_rows: result.deleted_rows,
}
}
fn map_lancedb_drop_table_result(
result: ControllerLanceDbDropTableResult,
) -> FfiControllerLanceDbDropTableResult {
FfiControllerLanceDbDropTableResult {
message: string_into_raw(result.message),
}
}
fn map_byte_buffer_array(chunks: Vec<Vec<u8>>) -> FfiByteBufferArray {
let mut items: Vec<FfiByteBuffer> = chunks
.into_iter()
.map(|chunk| {
let (data, len) = bytes_into_raw(chunk);
FfiByteBuffer { data, len }
})
.collect();
let ptr = items.as_mut_ptr();
let len = items.len();
std::mem::forget(items);
FfiByteBufferArray { items: ptr, len }
}
fn map_custom_word_array(
words: Vec<ControllerSqliteCustomWordEntry>,
) -> FfiControllerSqliteCustomWordArray {
let mut items: Vec<FfiControllerSqliteCustomWordEntry> = words
.into_iter()
.map(|entry| FfiControllerSqliteCustomWordEntry {
word: string_into_raw(entry.word),
weight: entry.weight as c_ulonglong,
})
.collect();
let ptr = items.as_mut_ptr();
let len = items.len();
std::mem::forget(items);
FfiControllerSqliteCustomWordArray { items: ptr, len }
}
fn map_search_fts_hit_array(
hits: Vec<ControllerSqliteSearchFtsHit>,
) -> FfiControllerSqliteSearchFtsHitArray {
let mut items: Vec<FfiControllerSqliteSearchFtsHit> = hits
.into_iter()
.map(|hit| FfiControllerSqliteSearchFtsHit {
id: string_into_raw(hit.id),
file_path: string_into_raw(hit.file_path),
title: string_into_raw(hit.title),
title_highlight: string_into_raw(hit.title_highlight),
content_snippet: string_into_raw(hit.content_snippet),
score: hit.score,
rank: hit.rank,
raw_score: hit.raw_score,
})
.collect();
let ptr = items.as_mut_ptr();
let len = items.len();
std::mem::forget(items);
FfiControllerSqliteSearchFtsHitArray { items: ptr, len }
}
fn required_c_string(value: *const c_char, field_name: &str) -> Result<String, String> {
if value.is_null() {
return Err(format!("{field_name} pointer must not be null"));
}
let value = unsafe { CStr::from_ptr(value) };
let text = value
.to_str()
.map_err(|error| format!("{field_name} must be valid UTF-8: {error}"))?
.trim()
.to_string();
if text.is_empty() {
return Err(format!("{field_name} must not be empty"));
}
Ok(text)
}
fn required_c_string_preserve(value: *const c_char, field_name: &str) -> Result<String, String> {
if value.is_null() {
return Err(format!("{field_name} pointer must not be null"));
}
let value = unsafe { CStr::from_ptr(value) };
value
.to_str()
.map(|text| text.to_string())
.map_err(|error| format!("{field_name} must be valid UTF-8: {error}"))
}
fn required_string_array(
items: *const *const c_char,
len: usize,
field_name: &str,
) -> Result<Vec<String>, String> {
if items.is_null() {
return Err(format!("{field_name} pointer must not be null"));
}
let slice = unsafe { std::slice::from_raw_parts(items, len) };
slice
.iter()
.enumerate()
.map(|(index, item)| required_c_string(*item, &format!("{field_name}[{index}]")))
.collect()
}
fn optional_c_string(value: *const c_char) -> Result<Option<String>, String> {
if value.is_null() {
return Ok(None);
}
let value = unsafe { CStr::from_ptr(value) };
let text = value
.to_str()
.map_err(|error| format!("optional string must be valid UTF-8: {error}"))?
.trim()
.to_string();
if text.is_empty() {
Ok(None)
} else {
Ok(Some(text))
}
}
fn parse_sqlite_tokenizer_mode_text(
value: *const c_char,
) -> Result<ControllerSqliteTokenizerMode, String> {
let value = required_c_string(value, "tokenizer_mode")?;
parse_sqlite_tokenizer_mode_name(&value)
}
fn parse_sqlite_tokenizer_mode_name(value: &str) -> Result<ControllerSqliteTokenizerMode, String> {
match value.trim().to_ascii_lowercase().as_str() {
"" | "none" => Ok(ControllerSqliteTokenizerMode::None),
"jieba" => Ok(ControllerSqliteTokenizerMode::Jieba),
other => Err(format!("unsupported tokenizer_mode: {other}")),
}
}
fn json_to_sqlite_value(value: JsonValue) -> Result<ControllerSqliteValue, String> {
match value {
JsonValue::Null => Ok(ControllerSqliteValue::Null),
JsonValue::Bool(value) => Ok(ControllerSqliteValue::Bool(value)),
JsonValue::Number(value) => {
if let Some(value) = value.as_i64() {
Ok(ControllerSqliteValue::Int64(value))
} else if let Some(value) = value.as_u64() {
Ok(ControllerSqliteValue::Int64(i64::try_from(value).map_err(
|_| "params contains an unsigned integer larger than i64".to_string(),
)?))
} else if let Some(value) = value.as_f64() {
Ok(ControllerSqliteValue::Float64(value))
} else {
Err("params contains an unsupported numeric value".to_string())
}
}
JsonValue::String(value) => Ok(ControllerSqliteValue::String(value)),
JsonValue::Object(value) => {
let wrapper = serde_json::from_value::<JsonSqliteBytesValue>(JsonValue::Object(value))
.map_err(|_| {
"params object values must use {\"type\":\"bytes_base64\",\"base64\":\"...\"} or {\"__type\":\"bytes_base64\",\"base64\":\"...\"}".to_string()
})?;
let wrapper_type = if !wrapper.r#type.trim().is_empty() {
wrapper.r#type.trim()
} else {
wrapper.__type.trim()
};
if wrapper_type != "bytes_base64" {
return Err(
"params object values only support the bytes_base64 wrapper type".to_string(),
);
}
Ok(ControllerSqliteValue::Bytes(decode_base64(
&wrapper.base64,
)?))
}
JsonValue::Array(_) => {
Err("params only supports scalar JSON values or bytes wrapper objects".to_string())
}
}
}
fn required_bytes<'a>(data: *const u8, len: usize, field_name: &str) -> Result<&'a [u8], String> {
if data.is_null() {
return Err(format!("{field_name} pointer must not be null"));
}
if len == 0 {
return Err(format!("{field_name} length must be greater than zero"));
}
Ok(unsafe { std::slice::from_raw_parts(data, len) })
}
fn optional_bytes<'a>(data: *const u8, len: usize, field_name: &str) -> Result<&'a [u8], String> {
if len == 0 {
return Ok(&[]);
}
if data.is_null() {
return Err(format!(
"{field_name} pointer must not be null when len > 0"
));
}
Ok(unsafe { std::slice::from_raw_parts(data, len) })
}
fn optional_string_array(
items: *const *const c_char,
len: usize,
field_name: &str,
) -> Result<Vec<String>, String> {
if len == 0 {
return Ok(Vec::new());
}
required_string_array(items, len, field_name)
}
fn required_f32_slice<'a>(
data: *const c_float,
len: usize,
field_name: &str,
) -> Result<&'a [c_float], String> {
if data.is_null() {
return Err(format!("{field_name} pointer must not be null"));
}
if len == 0 {
return Err(format!("{field_name} length must be greater than zero"));
}
Ok(unsafe { std::slice::from_raw_parts(data, len) })
}
fn read_sqlite_values(
values: *const FfiSqliteValue,
len: usize,
field_name: &str,
) -> Result<Vec<ControllerSqliteValue>, String> {
if len == 0 {
return Ok(Vec::new());
}
if values.is_null() {
return Err(format!("{field_name} pointer must not be null"));
}
let slice = unsafe { std::slice::from_raw_parts(values, len) };
slice
.iter()
.enumerate()
.map(|(index, value)| map_sqlite_value_native(value, &format!("{field_name}[{index}]")))
.collect()
}
fn read_sqlite_batch_items(
items: *const FfiSqliteBatchItem,
len: usize,
field_name: &str,
) -> Result<Vec<Vec<ControllerSqliteValue>>, String> {
if len == 0 {
return Ok(Vec::new());
}
if items.is_null() {
return Err(format!("{field_name} pointer must not be null"));
}
let slice = unsafe { std::slice::from_raw_parts(items, len) };
slice
.iter()
.enumerate()
.map(|(index, item)| {
read_sqlite_values(
item.params,
item.params_len,
&format!("{field_name}[{index}].params"),
)
})
.collect()
}
fn read_lancedb_columns(
columns: *const FfiControllerLanceDbColumnDef,
len: usize,
field_name: &str,
) -> Result<Vec<ControllerLanceDbColumnDef>, String> {
if columns.is_null() {
return Err(format!("{field_name} pointer must not be null"));
}
if len == 0 {
return Err(format!("{field_name} length must be greater than zero"));
}
let slice = unsafe { std::slice::from_raw_parts(columns, len) };
slice
.iter()
.enumerate()
.map(|(index, column)| {
Ok(ControllerLanceDbColumnDef {
name: required_c_string(column.name, &format!("{field_name}[{index}].name"))?,
column_type: map_lancedb_column_type_native(column.column_type)?,
vector_dim: column.vector_dim,
nullable: column.nullable != 0,
})
})
.collect()
}
fn map_sqlite_value_native(
value: &FfiSqliteValue,
field_name: &str,
) -> Result<ControllerSqliteValue, String> {
match value.kind {
0 => Ok(ControllerSqliteValue::Int64(value.int64_value)),
1 => Ok(ControllerSqliteValue::Float64(value.float64_value)),
2 => Ok(ControllerSqliteValue::String(required_c_string_preserve(
value.string_value,
&format!("{field_name}.string_value"),
)?)),
3 => Ok(ControllerSqliteValue::Bytes(
optional_bytes(
value.bytes_value,
value.bytes_len,
&format!("{field_name}.bytes_value"),
)?
.to_vec(),
)),
4 => Ok(ControllerSqliteValue::Bool(value.bool_value != 0)),
5 => Ok(ControllerSqliteValue::Null),
other => Err(format!("unsupported sqlite value kind `{other}`")),
}
}
fn default_nullable() -> bool {
true
}
fn default_search_limit() -> u32 {
10
}
fn parse_json_input<T>(json: *const c_char) -> Result<T, String>
where
T: for<'de> Deserialize<'de>,
{
let json = required_c_string(json, "json")?;
serde_json::from_str(&json).map_err(|error| format!("failed to parse json input: {error}"))
}
fn string_into_raw(value: String) -> *mut c_char {
let sanitized = if value.contains('\0') {
value.replace('\0', "\u{FFFD}")
} else {
value
};
CString::new(sanitized)
.expect("sanitized string must not contain interior null bytes")
.into_raw()
}
fn bytes_into_raw(mut value: Vec<u8>) -> (*mut u8, usize) {
let ptr = value.as_mut_ptr();
let len = value.len();
std::mem::forget(value);
(ptr, len)
}
fn with_client_handle<T>(
client: *mut FfiControllerClientHandle,
func: impl FnOnce(&mut FfiControllerClientHandleInner) -> Result<T, String>,
) -> Result<T, String> {
let handle_id = ffi_client_handle_id_from_ptr(client)?;
let state = {
let registry = ffi_client_handle_registry()
.lock()
.map_err(|poisoned| format!("ffi client handle registry lock poisoned: {poisoned}"))?;
registry
.get(&handle_id)
.cloned()
.ok_or_else(|| "client handle is invalid or has been freed".to_string())?
};
let mut guard = match state.inner.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
let inner = guard
.as_mut()
.ok_or_else(|| "client handle is invalid or has been freed".to_string())?;
func(inner)
}
fn map_process_mode_native(raw: c_int) -> Result<ControllerProcessMode, String> {
match raw {
0 => Ok(ControllerProcessMode::Service),
1 => Ok(ControllerProcessMode::Managed),
_ => Err(format!("unsupported process mode value `{raw}`")),
}
}
fn map_process_mode_to_native(mode: ControllerProcessMode) -> c_int {
match mode {
ControllerProcessMode::Service => 0,
ControllerProcessMode::Managed => 1,
}
}
fn map_space_kind_native(raw: c_int) -> Result<SpaceKind, String> {
match raw {
0 => Ok(SpaceKind::Root),
1 => Ok(SpaceKind::User),
2 => Ok(SpaceKind::Project),
_ => Err(format!("unsupported space kind value `{raw}`")),
}
}
fn map_space_kind_to_native(kind: SpaceKind) -> c_int {
match kind {
SpaceKind::Root => 0,
SpaceKind::User => 1,
SpaceKind::Project => 2,
}
}
fn map_lancedb_column_type_native(raw: c_int) -> Result<ControllerLanceDbColumnType, String> {
match raw {
0 => Ok(ControllerLanceDbColumnType::Unspecified),
1 => Ok(ControllerLanceDbColumnType::String),
2 => Ok(ControllerLanceDbColumnType::Int64),
3 => Ok(ControllerLanceDbColumnType::Float64),
4 => Ok(ControllerLanceDbColumnType::Bool),
5 => Ok(ControllerLanceDbColumnType::VectorFloat32),
6 => Ok(ControllerLanceDbColumnType::Float32),
7 => Ok(ControllerLanceDbColumnType::Uint64),
8 => Ok(ControllerLanceDbColumnType::Int32),
9 => Ok(ControllerLanceDbColumnType::Uint32),
other => Err(format!("unsupported lancedb column type `{other}`")),
}
}
fn map_lancedb_input_format_native(raw: c_int) -> Result<ControllerLanceDbInputFormat, String> {
match raw {
0 => Ok(ControllerLanceDbInputFormat::Unspecified),
1 => Ok(ControllerLanceDbInputFormat::JsonRows),
2 => Ok(ControllerLanceDbInputFormat::ArrowIpc),
other => Err(format!("unsupported lancedb input format `{other}`")),
}
}
fn map_lancedb_output_format_native(raw: c_int) -> Result<ControllerLanceDbOutputFormat, String> {
match raw {
0 => Ok(ControllerLanceDbOutputFormat::Unspecified),
1 => Ok(ControllerLanceDbOutputFormat::ArrowIpc),
2 => Ok(ControllerLanceDbOutputFormat::JsonRows),
other => Err(format!("unsupported lancedb output format `{other}`")),
}
}
fn default_u64(value: c_ulonglong, fallback: u64) -> u64 {
if value == 0 { fallback } else { value }
}
fn optional_nonzero_u64(value: c_ulonglong) -> Option<u64> {
if value == 0 { None } else { Some(value) }
}
fn write_out_ptr<T>(slot: *mut *mut T, value: *mut T) {
if !slot.is_null() {
unsafe {
*slot = value;
}
}
}
fn write_boxed_out_ptr<T>(slot: *mut *mut T, value: T) {
if !slot.is_null() {
unsafe {
*slot = Box::into_raw(Box::new(value));
}
}
}
fn clear_out_ptr<T>(slot: *mut *mut T) {
if !slot.is_null() {
unsafe {
*slot = ptr::null_mut();
}
}
}
fn write_out_u8(slot: *mut c_uchar, value: c_uchar) {
if !slot.is_null() {
unsafe {
*slot = value;
}
}
}
fn clear_out_u8(slot: *mut c_uchar) {
if !slot.is_null() {
unsafe {
*slot = 0;
}
}
}
fn clear_error_out(error_out: *mut *mut c_char) {
clear_out_ptr(error_out);
}
fn write_string_out(slot: *mut *mut c_char, value: String) {
if !slot.is_null() {
unsafe {
*slot = string_into_raw(value);
}
}
}
fn ffi_ok_status(error_out: *mut *mut c_char) -> c_int {
clear_error_out(error_out);
FFI_STATUS_OK
}
fn ffi_error_status(error_out: *mut *mut c_char, message: impl Into<String>) -> c_int {
clear_error_out(error_out);
if !error_out.is_null() {
unsafe {
*error_out = string_into_raw(message.into());
}
}
FFI_STATUS_ERR
}
fn error_to_string(error: impl std::fmt::Display) -> String {
error.to_string()
}
fn free_space_snapshot_fields(snapshot: FfiSpaceSnapshot) {
vldb_controller_ffi_string_free(snapshot.space_id);
vldb_controller_ffi_string_free(snapshot.space_label);
vldb_controller_ffi_string_free(snapshot.space_root);
free_backend_status_ptr(snapshot.sqlite);
free_backend_status_ptr(snapshot.lancedb);
}
fn free_backend_status_ptr(status: *mut FfiSpaceBackendStatus) {
if !status.is_null() {
unsafe {
let status = Box::from_raw(status);
vldb_controller_ffi_string_free(status.mode);
vldb_controller_ffi_string_free(status.target);
}
}
}
fn encode_base64(data: &[u8]) -> String {
const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut output = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let a = chunk[0];
let b = *chunk.get(1).unwrap_or(&0);
let c = *chunk.get(2).unwrap_or(&0);
output.push(TABLE[(a >> 2) as usize] as char);
output.push(TABLE[(((a & 0x03) << 4) | (b >> 4)) as usize] as char);
if chunk.len() > 1 {
output.push(TABLE[(((b & 0x0f) << 2) | (c >> 6)) as usize] as char);
} else {
output.push('=');
}
if chunk.len() > 2 {
output.push(TABLE[(c & 0x3f) as usize] as char);
} else {
output.push('=');
}
}
output
}
fn decode_base64(input: &str) -> Result<Vec<u8>, String> {
let bytes = input.as_bytes();
if !bytes.len().is_multiple_of(4) {
return Err("base64 input length must be a multiple of 4".to_string());
}
let mut output = Vec::with_capacity(bytes.len() / 4 * 3);
for chunk in bytes.chunks(4) {
let a = decode_base64_char(chunk[0])?;
let b = decode_base64_char(chunk[1])?;
let c = if chunk[2] == b'=' {
64
} else {
decode_base64_char(chunk[2])?
};
let d = if chunk[3] == b'=' {
64
} else {
decode_base64_char(chunk[3])?
};
output.push((a << 2) | (b >> 4));
if c != 64 {
output.push(((b & 0x0f) << 4) | (c >> 2));
}
if d != 64 {
output.push(((c & 0x03) << 6) | d);
}
}
Ok(output)
}
fn decode_base64_char(value: u8) -> Result<u8, String> {
match value {
b'A'..=b'Z' => Ok(value - b'A'),
b'a'..=b'z' => Ok(value - b'a' + 26),
b'0'..=b'9' => Ok(value - b'0' + 52),
b'+' => Ok(62),
b'/' => Ok(63),
_ => Err(format!("invalid base64 character `{}`", value as char)),
}
}
#[cfg(test)]
mod tests {
use super::{
CreateLanceDbTableJsonRequest, DeleteLanceDbJsonRequest, ExecuteSqliteBatchJsonRequest,
ExecuteSqliteScriptJsonRequest, FFI_STATUS_ERR, FFI_STATUS_OK, FfiClientRegistration,
FfiControllerClientConfig, FfiControllerClientHandle, FfiSqliteValue,
QuerySqliteJsonJsonRequest, QuerySqliteStreamJsonRequest, SearchLanceDbJsonRequest,
UpsertLanceDbJsonRequest, build_client_handle, map_sqlite_value_native,
required_c_string_preserve, vldb_controller_ffi_client_create,
vldb_controller_ffi_client_free, with_client_handle,
};
use serde_json::json;
use std::ffi::CString;
use vldb_controller_client::{
ClientRegistration, ControllerClientConfig, ControllerSqliteValue,
};
#[test]
fn sqlite_native_string_preserves_whitespace_and_allows_empty() {
let spaced = CString::new(" a ").expect("cstring should build");
let empty = CString::new("").expect("cstring should build");
let spaced_value = FfiSqliteValue {
kind: 2,
int64_value: 0,
float64_value: 0.0,
string_value: spaced.as_ptr(),
bytes_value: std::ptr::null(),
bytes_len: 0,
bool_value: 0,
};
let empty_value = FfiSqliteValue {
kind: 2,
int64_value: 0,
float64_value: 0.0,
string_value: empty.as_ptr(),
bytes_value: std::ptr::null(),
bytes_len: 0,
bool_value: 0,
};
assert_eq!(
map_sqlite_value_native(&spaced_value, "value").expect("string should parse"),
ControllerSqliteValue::String(" a ".to_string())
);
assert_eq!(
map_sqlite_value_native(&empty_value, "value").expect("empty string should parse"),
ControllerSqliteValue::String(String::new())
);
assert_eq!(
required_c_string_preserve(empty.as_ptr(), "value").expect("empty string should read"),
String::new()
);
}
#[test]
fn sqlite_native_bytes_allow_empty_blob() {
let value = FfiSqliteValue {
kind: 3,
int64_value: 0,
float64_value: 0.0,
string_value: std::ptr::null(),
bytes_value: std::ptr::null(),
bytes_len: 0,
bool_value: 0,
};
assert_eq!(
map_sqlite_value_native(&value, "value").expect("empty blob should parse"),
ControllerSqliteValue::Bytes(Vec::new())
);
}
#[test]
fn preserve_string_reader_keeps_whitespace_for_text_payloads() {
let spaced = CString::new(" keep ").expect("cstring should build");
assert_eq!(
required_c_string_preserve(spaced.as_ptr(), "text").expect("text should read"),
" keep ".to_string()
);
}
#[test]
fn preserve_string_reader_allows_empty_text_payloads() {
let empty = CString::new("").expect("cstring should build");
assert_eq!(
required_c_string_preserve(empty.as_ptr(), "content")
.expect("empty content should read"),
String::new()
);
}
#[test]
fn sqlite_json_requests_accept_nested_params_without_inner_json_strings() {
let execute: ExecuteSqliteScriptJsonRequest = serde_json::from_value(json!({
"space_id": "ROOT",
"sql": "SELECT ?",
"params": [1, {"type": "bytes_base64", "base64": "AQID"}]
}))
.expect("execute request should parse");
assert_eq!(execute.params.len(), 2);
let query: QuerySqliteJsonJsonRequest = serde_json::from_value(json!({
"space_id": "ROOT",
"sql": "SELECT ?",
"params": [" keep "]
}))
.expect("query request should parse");
assert_eq!(query.params.len(), 1);
let batch: ExecuteSqliteBatchJsonRequest = serde_json::from_value(json!({
"space_id": "ROOT",
"sql": "INSERT INTO demo VALUES (?)",
"batch_params": [[1], [2], [{"__type": "bytes_base64", "base64": "AQ=="}]]
}))
.expect("batch request should parse");
assert_eq!(batch.batch_params.len(), 3);
let stream: QuerySqliteStreamJsonRequest = serde_json::from_value(json!({
"space_id": "ROOT",
"sql": "SELECT ?",
"params": [],
"target_chunk_size": 1024
}))
.expect("stream request should parse");
assert_eq!(stream.target_chunk_size, Some(1024));
}
#[test]
fn lancedb_json_requests_accept_nested_objects_without_request_json() {
let create: CreateLanceDbTableJsonRequest = serde_json::from_value(json!({
"space_id": "ROOT",
"table_name": "memory",
"columns": [
{"name": "id", "column_type": "string", "nullable": false},
{"name": "vector", "column_type": "vector", "vector_dim": 3, "nullable": false}
],
"overwrite_if_exists": true
}))
.expect("create request should parse");
assert_eq!(create.columns.len(), 2);
let upsert: UpsertLanceDbJsonRequest = serde_json::from_value(json!({
"space_id": "ROOT",
"table_name": "memory",
"input_format": "json_rows",
"key_columns": ["id"],
"data_base64": "W10="
}))
.expect("upsert request should parse");
assert_eq!(upsert.key_columns, vec!["id".to_string()]);
let search: SearchLanceDbJsonRequest = serde_json::from_value(json!({
"space_id": "ROOT",
"table_name": "memory",
"vector": [0.1, 0.2, 0.3],
"limit": 5,
"filter": "kind = 'note'",
"vector_column": "vector",
"output_format": "json_rows"
}))
.expect("search request should parse");
assert_eq!(search.vector.len(), 3);
let delete: DeleteLanceDbJsonRequest = serde_json::from_value(json!({
"space_id": "ROOT",
"table_name": "memory",
"condition": "id = 'a'"
}))
.expect("delete request should parse");
assert_eq!(delete.condition, "id = 'a'");
}
#[test]
fn freed_handle_rejects_follow_up_calls_safely() {
let handle_ptr = build_client_handle(
ControllerClientConfig::default(),
ClientRegistration {
client_name: "ffi-test-client".to_string(),
host_kind: "test".to_string(),
process_id: 7,
process_name: "ffi-test".to_string(),
lease_ttl_secs: Some(60),
},
)
.expect("handle should build");
with_client_handle(handle_ptr, |_handle| Ok(())).expect("live handle should work");
vldb_controller_ffi_client_free(handle_ptr);
let error = with_client_handle(handle_ptr, |_handle| Ok(()))
.expect_err("freed handle should reject later calls");
assert!(
error.contains("freed"),
"error should mention freed state, got: {error}"
);
vldb_controller_ffi_client_free(handle_ptr);
}
#[test]
fn native_create_keeps_error_out_optional_and_clears_reused_slots() {
let client_name = CString::new("ffi-create-client").expect("client_name should build");
let host_kind = CString::new("test").expect("host_kind should build");
let process_name = CString::new("ffi-create").expect("process_name should build");
let config = FfiControllerClientConfig {
endpoint: std::ptr::null(),
auto_spawn: 0,
spawn_executable: std::ptr::null(),
spawn_process_mode: 0,
minimum_uptime_secs: 0,
idle_timeout_secs: 0,
default_lease_ttl_secs: 0,
connect_timeout_secs: 0,
startup_timeout_secs: 0,
startup_retry_interval_ms: 0,
lease_renew_interval_secs: 0,
};
let registration = FfiClientRegistration {
client_name: client_name.as_ptr(),
host_kind: host_kind.as_ptr(),
process_id: 9,
process_name: process_name.as_ptr(),
lease_ttl_secs: 0,
};
let mut handle_ptr = std::ptr::null_mut::<FfiControllerClientHandle>();
let create_status = vldb_controller_ffi_client_create(
&config,
®istration,
&mut handle_ptr,
std::ptr::null_mut(),
);
assert_eq!(create_status, FFI_STATUS_OK);
assert!(
!handle_ptr.is_null(),
"create should still succeed without error_out"
);
vldb_controller_ffi_client_free(handle_ptr);
let mut stale_handle_ptr = std::ptr::dangling_mut::<FfiControllerClientHandle>();
let failure_status = vldb_controller_ffi_client_create(
std::ptr::null(),
®istration,
&mut stale_handle_ptr,
std::ptr::null_mut(),
);
assert_eq!(failure_status, FFI_STATUS_ERR);
assert!(
stale_handle_ptr.is_null(),
"client_out should be cleared before returning an error"
);
}
#[test]
fn forged_handle_pointer_is_rejected_even_when_other_handles_are_live() {
let handle_ptr = build_client_handle(
ControllerClientConfig::default(),
ClientRegistration {
client_name: "ffi-forge-client".to_string(),
host_kind: "test".to_string(),
process_id: 11,
process_name: "ffi-forge".to_string(),
lease_ttl_secs: Some(60),
},
)
.expect("handle should build");
let forged_handle_ptr = std::ptr::dangling_mut::<FfiControllerClientHandle>();
assert_ne!(
handle_ptr, forged_handle_ptr,
"forged handle pointer must differ from the live registered handle"
);
let error = with_client_handle(forged_handle_ptr, |_handle| Ok(()))
.expect_err("forged handle pointer should be rejected");
assert!(
error.contains("invalid") || error.contains("freed"),
"error should mention invalid or freed handle state, got: {error}"
);
with_client_handle(handle_ptr, |_handle| Ok(())).expect("live handle should still work");
vldb_controller_ffi_client_free(handle_ptr);
}
}